@deepseek-harness-tui/dsh-tui 0.9.2 → 0.9.3

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 (214) hide show
  1. package/README.md +4 -4
  2. package/bin/dsh-tui.js +26 -4
  3. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
  5. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
  7. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
  8. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
  9. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
  11. package/dsh-ecosystem-spec/registry/README.md +14 -14
  12. package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
  13. package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
  14. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
  15. package/lib/types/commands.d.ts.map +1 -1
  16. package/lib/types/commands.js +7 -8
  17. package/lib/types/components/BalanceReportRow.d.ts +24 -0
  18. package/lib/types/components/BalanceReportRow.d.ts.map +1 -0
  19. package/lib/types/components/BalanceReportRow.js +87 -0
  20. package/lib/types/components/HistorySearchDialog.js +1 -1
  21. package/lib/types/components/MessageList.d.ts +3 -1
  22. package/lib/types/components/MessageList.d.ts.map +1 -1
  23. package/lib/types/components/MessageList.js +11 -7
  24. package/lib/types/components/PluginSceneBoundary.d.ts +1 -1
  25. package/lib/types/components/PluginSceneBoundary.js +1 -1
  26. package/lib/types/components/PromptInput.d.ts.map +1 -1
  27. package/lib/types/components/PromptInput.js +16 -0
  28. package/lib/types/components/Whale.d.ts +15 -4
  29. package/lib/types/components/Whale.d.ts.map +1 -1
  30. package/lib/types/components/Whale.js +9 -8
  31. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +7 -1
  32. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  33. package/lib/types/components/messages/AssistantToolUseMessage.js +34 -4
  34. package/lib/types/deepseekBalance.d.ts +53 -0
  35. package/lib/types/deepseekBalance.d.ts.map +1 -0
  36. package/lib/types/deepseekBalance.js +86 -0
  37. package/lib/types/deepseekPricing.d.ts +82 -0
  38. package/lib/types/deepseekPricing.d.ts.map +1 -0
  39. package/lib/types/deepseekPricing.js +133 -0
  40. package/lib/types/dsh-adapter/channel.d.ts +46 -1
  41. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  42. package/lib/types/dsh-adapter/channel.js +84 -10
  43. package/lib/types/dsh-adapter/index.d.ts +5 -0
  44. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  45. package/lib/types/dsh-adapter/index.js +1 -0
  46. package/lib/types/dsh-adapter/packaged-skills.d.ts +9 -0
  47. package/lib/types/dsh-adapter/packaged-skills.d.ts.map +1 -1
  48. package/lib/types/dsh-adapter/packaged-skills.js +19 -4
  49. package/lib/types/dsh-adapter/plugin.d.ts +6 -0
  50. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  51. package/lib/types/dsh-adapter/plugin.js +122 -18
  52. package/lib/types/dsh-adapter/settings-sections.d.ts +1 -0
  53. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/settings-sections.js +35 -2
  55. package/lib/types/history.d.ts +4 -3
  56. package/lib/types/history.d.ts.map +1 -1
  57. package/lib/types/history.js +66 -18
  58. package/lib/types/i18n.d.ts +122 -55
  59. package/lib/types/i18n.d.ts.map +1 -1
  60. package/lib/types/i18n.js +39 -18
  61. package/lib/types/ink/ink.d.ts.map +1 -1
  62. package/lib/types/ink/ink.js +67 -17
  63. package/lib/types/ink/root.d.ts +16 -0
  64. package/lib/types/ink/root.d.ts.map +1 -1
  65. package/lib/types/ink/root.js +2 -0
  66. package/lib/types/ink/terminal-querier.d.ts +8 -0
  67. package/lib/types/ink/terminal-querier.d.ts.map +1 -1
  68. package/lib/types/ink/terminal-querier.js +13 -0
  69. package/lib/types/ink/terminal.d.ts +42 -0
  70. package/lib/types/ink/terminal.d.ts.map +1 -1
  71. package/lib/types/ink/terminal.js +52 -2
  72. package/lib/types/plugin-spec/registry.d.ts +1 -1
  73. package/lib/types/plugin-spec/registry.js +1 -1
  74. package/lib/types/screens/Chat.d.ts.map +1 -1
  75. package/lib/types/screens/Chat.js +72 -37
  76. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  77. package/lib/types/screens/SessionBrowser.js +28 -13
  78. package/lib/types/screens/SessionTree.d.ts.map +1 -1
  79. package/lib/types/screens/SessionTree.js +33 -9
  80. package/lib/types/screens/Settings.d.ts +7 -5
  81. package/lib/types/screens/Settings.d.ts.map +1 -1
  82. package/lib/types/screens/Settings.js +206 -99
  83. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  84. package/lib/types/screens/StatusLine.js +32 -1
  85. package/lib/types/sessionHistory.d.ts +11 -0
  86. package/lib/types/sessionHistory.d.ts.map +1 -1
  87. package/lib/types/sessionHistory.js +29 -0
  88. package/lib/types/sessions/format.d.ts.map +1 -1
  89. package/lib/types/sessions/format.js +14 -4
  90. package/lib/types/tips.js +2 -2
  91. package/lib/types/tuiDisplayPrefs.d.ts +3 -0
  92. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  93. package/lib/types/tuiDisplayPrefs.js +1 -0
  94. package/lib/types/update.d.ts +46 -0
  95. package/lib/types/update.d.ts.map +1 -1
  96. package/lib/types/update.js +232 -3
  97. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -21
  98. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -44
  99. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
  100. package/node_modules/@dsh-std/command/LICENSE +21 -21
  101. package/node_modules/@dsh-std/command/README.md +26 -26
  102. package/node_modules/@dsh-std/command/README.zh.md +28 -28
  103. package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
  104. package/node_modules/@dsh-std/command/package.json +22 -22
  105. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  106. package/node_modules/@dsh-std/connection/LICENSE +21 -21
  107. package/node_modules/@dsh-std/connection/README.md +42 -42
  108. package/node_modules/@dsh-std/connection/README.zh.md +42 -42
  109. package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  110. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  111. package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  112. package/node_modules/@dsh-std/connection/package.json +20 -20
  113. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  114. package/node_modules/@dsh-std/core/LICENSE +21 -21
  115. package/node_modules/@dsh-std/core/README.md +13 -13
  116. package/node_modules/@dsh-std/core/README.zh.md +13 -13
  117. package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  118. package/node_modules/@dsh-std/core/package.json +21 -21
  119. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  120. package/node_modules/@dsh-std/manifest/LICENSE +21 -21
  121. package/node_modules/@dsh-std/manifest/README.md +5 -5
  122. package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  123. package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  124. package/node_modules/@dsh-std/manifest/package.json +20 -20
  125. package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  126. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
  127. package/node_modules/@dsh-std/messages/LICENSE +21 -21
  128. package/node_modules/@dsh-std/messages/README.md +5 -5
  129. package/node_modules/@dsh-std/messages/README.zh.md +7 -7
  130. package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
  131. package/node_modules/@dsh-std/messages/package.json +20 -20
  132. package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
  133. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  134. package/node_modules/@dsh-std/presentation/LICENSE +21 -21
  135. package/node_modules/@dsh-std/presentation/README.md +27 -27
  136. package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  137. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  138. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  139. package/node_modules/@dsh-std/presentation/package.json +25 -25
  140. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
  141. package/node_modules/@dsh-std/storage/LICENSE +21 -21
  142. package/node_modules/@dsh-std/storage/README.md +5 -5
  143. package/node_modules/@dsh-std/storage/README.zh.md +7 -7
  144. package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
  145. package/node_modules/@dsh-std/storage/package.json +20 -20
  146. package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
  147. package/package.json +95 -5
  148. package/skills/pr-comments/SKILL.md +1 -1
  149. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  150. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
  151. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
  152. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
  153. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  154. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  155. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  156. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
  157. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  158. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
  159. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
  160. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
  161. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  162. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
  163. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  164. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
  165. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
  166. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  167. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  168. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
  169. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  170. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  171. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
  172. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
  173. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  174. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  175. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  176. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
  177. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  178. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
  179. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
  180. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
  181. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  182. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
  183. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  184. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
  185. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
  186. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
  187. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  188. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
  189. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  190. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
  191. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
  192. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
  193. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  194. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
  195. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  196. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
  197. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
  198. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
  199. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  200. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  201. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  202. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
  203. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  204. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
  205. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
  206. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
  207. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  208. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
  209. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  210. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
  211. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
  212. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
  213. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  214. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
@@ -26,6 +26,8 @@ export const renderSync = (node, options) => {
26
26
  instance.unmount();
27
27
  },
28
28
  waitUntilExit: instance.waitUntilExit,
29
+ detachForShutdown: () => instance.detachForShutdown(),
30
+ detachStdinForHandoff: () => instance.detachStdinForHandoff(),
29
31
  cleanup: () => instances.delete(inkOptions.stdout),
30
32
  };
31
33
  };
@@ -112,6 +112,14 @@ export declare class TerminalQuerier {
112
112
  * before it — concurrent batches from independent callers stay isolated.
113
113
  */
114
114
  private queue;
115
+ /**
116
+ * Set by dispose(). Post-dispose send()/flush() must not touch the
117
+ * terminal: they would re-enable raw mode (holdRawMode) and emit query
118
+ * bytes after the exit funnel's cleanup has already restored cooked
119
+ * mode — the replies then leak into the shell (#507). Resolving as
120
+ * "no answer" keeps callers' .then() chains inert instead of pending.
121
+ */
122
+ private disposed;
115
123
  constructor(stdout: NodeJS.WriteStream, setRawMode?: ((enabled: boolean) => void) | undefined);
116
124
  private holdRawMode;
117
125
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-querier.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal-querier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAI3D;;4CAE4C;AAC5C,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB,IAAI;IACzE,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,6DAA6D;IAC7D,KAAK,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,CAAC,IAAI,CAAC,CAAA;CACvC,CAAA;AAED,KAAK,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAA;AACnE,KAAK,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAC7D,KAAK,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAC7D,KAAK,aAAa,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,CAAA;AACzE,KAAK,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAAA;AAC9E,KAAK,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAC7D,KAAK,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAA;AAIzE;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,cAAc,CAAC,CAKlE;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,IAAI,aAAa,CAAC,WAAW,CAAC,CAKhD;AAED;;;GAGG;AACH,wBAAgB,GAAG,IAAI,aAAa,CAAC,WAAW,CAAC,CAKhD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAAC,aAAa,CAAC,CAK5D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,aAAa,CAAC,iBAAiB,CAAC,CAKjE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAKjE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAAC,iBAAiB,CAAC,CAK5D;AAgBD;;;GAGG;AACH,qBAAa,eAAe;IASxB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,UAAU,CAAC;IATrB;;;;OAIG;IACH,OAAO,CAAC,KAAK,CAAgB;gBAGnB,MAAM,EAAE,MAAM,CAAC,WAAW,EAC1B,UAAU,CAAC,GAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,aAAA;IAGjD,OAAO,CAAC,WAAW;IAKnB;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,CAAC,SAAS,gBAAgB,EAC7B,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAYzB;;;;;;;;OAQG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAWtB,8EAA8E;IAC9E,OAAO,IAAI,IAAI;IAQf;;;;;;;;;;;;;;;;;;OAkBG;IACH,UAAU,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI;CAqBtC"}
1
+ {"version":3,"file":"terminal-querier.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal-querier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AAI3D;;4CAE4C;AAC5C,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,gBAAgB,GAAG,gBAAgB,IAAI;IACzE,yCAAyC;IACzC,OAAO,EAAE,MAAM,CAAA;IACf,6DAA6D;IAC7D,KAAK,EAAE,CAAC,CAAC,EAAE,gBAAgB,KAAK,CAAC,IAAI,CAAC,CAAA;CACvC,CAAA;AAED,KAAK,cAAc,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,QAAQ,CAAA;CAAE,CAAC,CAAA;AACnE,KAAK,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAC7D,KAAK,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAC7D,KAAK,aAAa,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,eAAe,CAAA;CAAE,CAAC,CAAA;AACzE,KAAK,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC,CAAA;AAC9E,KAAK,WAAW,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE,CAAC,CAAA;AAC7D,KAAK,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,EAAE;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAAC,CAAA;AAIzE;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,cAAc,CAAC,CAKlE;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,IAAI,aAAa,CAAC,WAAW,CAAC,CAKhD;AAED;;;GAGG;AACH,wBAAgB,GAAG,IAAI,aAAa,CAAC,WAAW,CAAC,CAKhD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,IAAI,aAAa,CAAC,aAAa,CAAC,CAK5D;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,aAAa,CAAC,iBAAiB,CAAC,CAKjE;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,aAAa,CAAC,WAAW,CAAC,CAKjE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,aAAa,CAAC,iBAAiB,CAAC,CAK5D;AAgBD;;;GAGG;AACH,qBAAa,eAAe;IAkBxB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,UAAU,CAAC;IAlBrB;;;;OAIG;IACH,OAAO,CAAC,KAAK,CAAgB;IAE7B;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAQ;gBAGd,MAAM,EAAE,MAAM,CAAC,WAAW,EAC1B,UAAU,CAAC,GAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,aAAA;IAGjD,OAAO,CAAC,WAAW;IAKnB;;;;;;;;;;;;OAYG;IACH,IAAI,CAAC,CAAC,SAAS,gBAAgB,EAC7B,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GACtB,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAazB;;;;;;;;OAQG;IACH,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAYtB,8EAA8E;IAC9E,OAAO,IAAI,IAAI;IASf;;;;;;;;;;;;;;;;;;OAkBG;IACH,UAAU,CAAC,CAAC,EAAE,gBAAgB,GAAG,IAAI;CAqBtC"}
@@ -122,6 +122,14 @@ export class TerminalQuerier {
122
122
  * before it — concurrent batches from independent callers stay isolated.
123
123
  */
124
124
  queue = [];
125
+ /**
126
+ * Set by dispose(). Post-dispose send()/flush() must not touch the
127
+ * terminal: they would re-enable raw mode (holdRawMode) and emit query
128
+ * bytes after the exit funnel's cleanup has already restored cooked
129
+ * mode — the replies then leak into the shell (#507). Resolving as
130
+ * "no answer" keeps callers' .then() chains inert instead of pending.
131
+ */
132
+ disposed = false;
125
133
  constructor(stdout, setRawMode) {
126
134
  this.stdout = stdout;
127
135
  this.setRawMode = setRawMode;
@@ -144,6 +152,8 @@ export class TerminalQuerier {
144
152
  * answer before the next flush() sentinel.
145
153
  */
146
154
  send(query) {
155
+ if (this.disposed)
156
+ return Promise.resolve(undefined);
147
157
  return new Promise(resolve => {
148
158
  this.queue.push({
149
159
  kind: 'query',
@@ -164,6 +174,8 @@ export class TerminalQuerier {
164
174
  * Safe to call with no pending queries — still waits for a round-trip.
165
175
  */
166
176
  flush() {
177
+ if (this.disposed)
178
+ return Promise.resolve();
167
179
  return new Promise(resolve => {
168
180
  this.queue.push({
169
181
  kind: 'sentinel',
@@ -175,6 +187,7 @@ export class TerminalQuerier {
175
187
  }
176
188
  /** Resolve and release all pending queries when their owning app unmounts. */
177
189
  dispose() {
190
+ this.disposed = true;
178
191
  for (const pending of this.queue.splice(0)) {
179
192
  if (pending.kind === 'query')
180
193
  pending.resolve(undefined);
@@ -53,6 +53,31 @@ export declare function setXtversionName(name: string): void;
53
53
  * @returns true when the terminal is xterm.js-based.
54
54
  */
55
55
  export declare function isXtermJs(): boolean;
56
+ /**
57
+ * True when the terminal can be safely probed with DECRQM
58
+ * (`CSI ? <mode> $ p`).
59
+ *
60
+ * DECRQM carries a `$` intermediate byte before its final `p`. A conforming
61
+ * parser consumes the whole sequence and either answers with DECRPM or stays
62
+ * silent, so callers have historically treated an unanswered probe as
63
+ * "unsupported" and sent it unconditionally. macOS Terminal.app breaks that
64
+ * assumption: it does not implement DECRQM *and* its CSI parser gives up at
65
+ * the `$`, printing the trailing `p` to the screen as literal text. Every
66
+ * probe therefore leaks a visible `p` at the cursor.
67
+ *
68
+ * Terminal.app reports `TERM=xterm-256color`, so TERM sniffing cannot tell it
69
+ * apart from a real xterm — `TERM_PROGRAM=Apple_Terminal` is the only marker.
70
+ * It is not forwarded over SSH, which matches the scope of the bug: the leak
71
+ * only happens when the sequence reaches Terminal.app's own parser, and a
72
+ * remote session is parsed by whatever terminal is actually attached.
73
+ *
74
+ * Kept as an exclusion rather than an allowlist so unknown terminals keep the
75
+ * (correct, spec-conforming) probe and only the known-broken one opts out.
76
+ * Same failure mode as the extended-keys allowlist below: assuming terminals
77
+ * silently ignore unknown CSI is not safe in practice.
78
+ * @returns true when it is safe to send a DECRQM probe.
79
+ */
80
+ export declare function supportsDecrqmProbe(): boolean;
56
81
  /**
57
82
  * True if this terminal correctly handles extended key reporting
58
83
  * (Kitty keyboard protocol + xterm modifyOtherKeys).
@@ -122,6 +147,23 @@ export type Terminal = {
122
147
  stdout: Writable;
123
148
  stderr: Writable;
124
149
  };
150
+ /**
151
+ * Write a frame diff to the terminal as a single buffered write. Wraps
152
+ * the output in BSU/ESU synchronized-update markers unless skipSyncMarkers
153
+ * is set. No-op when the diff contains no patches.
154
+ * @param terminal - the terminal to write to.
155
+ * @param diff - the frame diff patches to render.
156
+ * @param skipSyncMarkers - when true, omit the BSU/ESU wrapping.
157
+ */
158
+ /**
159
+ * Serialize a frame diff into a single ANSI string (BSU/ESU-wrapped unless
160
+ * skipSyncMarkers is set). Empty string when the diff has no patches.
161
+ * Extracted from writeDiffToTerminal so shutdown paths can write the last
162
+ * frame synchronously (issue #522: an async frame write racing the
163
+ * synchronous EXIT_ALT_SCREEN lands on the MAIN screen after the alt
164
+ * screen is gone, leaving misplaced residue).
165
+ */
166
+ export declare function serializeDiff(terminal: Terminal, diff: Diff, skipSyncMarkers?: boolean): string;
125
167
  /**
126
168
  * Write a frame diff to the terminal as a single buffered write. Wraps
127
169
  * the output in BSU/ESU synchronized-update markers unless skipSyncMarkers
@@ -1 +1 @@
1
- {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAKtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAYtC;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,eAAe,CAAA;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAuCtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAwDvD;AAcD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAGnC;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,WAAW,GAAE,MAAM,GAAG,SAAoC,EAC1D,kBAAkB,GAAE,MAAM,GAAG,SAA4C,GACxE,OAAO,CAOT;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAkC,CAAA;AAEpE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAuBD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,IAAI,CAwFN"}
1
+ {"version":3,"file":"terminal.d.ts","sourceRoot":"","sources":["../../../src/ink/terminal.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,QAAQ,CAAA;AAKtC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAA;AAYtC;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,KAAK,EAAE,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,eAAe,CAAA;IAC1D,UAAU,CAAC,EAAE,MAAM,CAAA;CACpB,CAAA;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,IAAI,OAAO,CAuCtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,IAAI,OAAO,CAEhD;AAED;;;;GAIG;AACH,wBAAgB,6BAA6B,IAAI,OAAO,CAwDvD;AAcD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAEnD;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAGnC;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAyBD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAG9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,WAAW,GAAE,MAAM,GAAG,SAAoC,EAC1D,kBAAkB,GAAE,MAAM,GAAG,SAA4C,GACxE,OAAO,CAOT;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,IAAI,OAAO,CAEpD;AAED;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,SAAkC,CAAA;AAEpE;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAuBD;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,MAAM,EAAE,QAAQ,CAAA;IAChB,MAAM,EAAE,QAAQ,CAAA;CACjB,CAAA;AAED;;;;;;;GAOG;AACH;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,MAAM,CAwFR;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE,IAAI,EACV,eAAe,UAAQ,GACtB,IAAI,CAMN"}
@@ -153,6 +153,33 @@ export function isXtermJs() {
153
153
  return true;
154
154
  return xtversionName?.startsWith('xterm.js') ?? false;
155
155
  }
156
+ /**
157
+ * True when the terminal can be safely probed with DECRQM
158
+ * (`CSI ? <mode> $ p`).
159
+ *
160
+ * DECRQM carries a `$` intermediate byte before its final `p`. A conforming
161
+ * parser consumes the whole sequence and either answers with DECRPM or stays
162
+ * silent, so callers have historically treated an unanswered probe as
163
+ * "unsupported" and sent it unconditionally. macOS Terminal.app breaks that
164
+ * assumption: it does not implement DECRQM *and* its CSI parser gives up at
165
+ * the `$`, printing the trailing `p` to the screen as literal text. Every
166
+ * probe therefore leaks a visible `p` at the cursor.
167
+ *
168
+ * Terminal.app reports `TERM=xterm-256color`, so TERM sniffing cannot tell it
169
+ * apart from a real xterm — `TERM_PROGRAM=Apple_Terminal` is the only marker.
170
+ * It is not forwarded over SSH, which matches the scope of the bug: the leak
171
+ * only happens when the sequence reaches Terminal.app's own parser, and a
172
+ * remote session is parsed by whatever terminal is actually attached.
173
+ *
174
+ * Kept as an exclusion rather than an allowlist so unknown terminals keep the
175
+ * (correct, spec-conforming) probe and only the known-broken one opts out.
176
+ * Same failure mode as the extended-keys allowlist below: assuming terminals
177
+ * silently ignore unknown CSI is not safe in practice.
178
+ * @returns true when it is safe to send a DECRQM probe.
179
+ */
180
+ export function supportsDecrqmProbe() {
181
+ return process.env.TERM_PROGRAM !== 'Apple_Terminal';
182
+ }
156
183
  // Terminals known to correctly implement the Kitty keyboard protocol
157
184
  // (CSI >1u) and/or xterm modifyOtherKeys (CSI >4;2m) for ctrl+shift+<letter>
158
185
  // disambiguation. We previously enabled unconditionally (#23350), assuming
@@ -280,10 +307,18 @@ function dumpFrame(buffer) {
280
307
  * @param diff - the frame diff patches to render.
281
308
  * @param skipSyncMarkers - when true, omit the BSU/ESU wrapping.
282
309
  */
283
- export function writeDiffToTerminal(terminal, diff, skipSyncMarkers = false) {
310
+ /**
311
+ * Serialize a frame diff into a single ANSI string (BSU/ESU-wrapped unless
312
+ * skipSyncMarkers is set). Empty string when the diff has no patches.
313
+ * Extracted from writeDiffToTerminal so shutdown paths can write the last
314
+ * frame synchronously (issue #522: an async frame write racing the
315
+ * synchronous EXIT_ALT_SCREEN lands on the MAIN screen after the alt
316
+ * screen is gone, leaving misplaced residue).
317
+ */
318
+ export function serializeDiff(terminal, diff, skipSyncMarkers = false) {
284
319
  // No output if there are no patches
285
320
  if (diff.length === 0) {
286
- return;
321
+ return '';
287
322
  }
288
323
  // BSU/ESU wrapping is opt-out to keep main-screen behavior unchanged.
289
324
  // Callers pass skipSyncMarkers=true when the terminal doesn't support
@@ -364,5 +399,20 @@ export function writeDiffToTerminal(terminal, diff, skipSyncMarkers = false) {
364
399
  if (useSync)
365
400
  buffer += ESU;
366
401
  dumpFrame(buffer);
402
+ return buffer;
403
+ }
404
+ /**
405
+ * Write a frame diff to the terminal as a single buffered write. Wraps
406
+ * the output in BSU/ESU synchronized-update markers unless skipSyncMarkers
407
+ * is set. No-op when the diff contains no patches.
408
+ * @param terminal - the terminal to write to.
409
+ * @param diff - the frame diff patches to render.
410
+ * @param skipSyncMarkers - when true, omit the BSU/ESU wrapping.
411
+ */
412
+ export function writeDiffToTerminal(terminal, diff, skipSyncMarkers = false) {
413
+ const buffer = serializeDiff(terminal, diff, skipSyncMarkers);
414
+ if (buffer === '') {
415
+ return;
416
+ }
367
417
  terminal.stdout.write(buffer);
368
418
  }
@@ -1,7 +1,7 @@
1
1
  /** Load and verify the pinned dsh-TUI admission profile. */
2
2
  import type { ContractRegistry, PermissionRegistry, RegistryEntry } from './types.js';
3
3
  export declare const DSH_STD_REVISION = "614dfa1ac168db79fcf4577cf0ebb34e2e3b944b";
4
- export declare const ECOSYSTEM_SPEC_REVISION = "2d0236f7d4579814d9d177a58d03ebd168025960";
4
+ export declare const ECOSYSTEM_SPEC_REVISION = "04d6798152db1df8523d97f51541adf42d3e41d9";
5
5
  export interface SpecData {
6
6
  dir: string;
7
7
  registry: ContractRegistry;
@@ -5,7 +5,7 @@ import { dirname, join } from 'node:path';
5
5
  import { fileURLToPath } from 'node:url';
6
6
  import { createAdmissionCatalog } from './tui-extension.js';
7
7
  export const DSH_STD_REVISION = '614dfa1ac168db79fcf4577cf0ebb34e2e3b944b';
8
- export const ECOSYSTEM_SPEC_REVISION = '2d0236f7d4579814d9d177a58d03ebd168025960';
8
+ export const ECOSYSTEM_SPEC_REVISION = '04d6798152db1df8523d97f51541adf42d3e41d9';
9
9
  const REGISTRY_FILE = join('registry', 'registry-0.15.json');
10
10
  const EXPECTED_PROFILE_VERSION = 'tui-admission/0.15';
11
11
  const EXPECTED_STD_REPOSITORY = 'https://github.com/Yan-Zero/dsh-std';
@@ -1 +1 @@
1
- {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/screens/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAwBzB,OAAO,EAAqB,KAAK,OAAO,EAAsE,MAAM,2BAA2B,CAAA;AAC/I,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAuK3D,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,SAAS,EACT,UAAkB,EAClB,cAAc,EAAE,kBAAkB,GACnC,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,aAAa,CAAA;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAA;IACjC,oEAAoE;IACpE,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,4BA8vGA"}
1
+ {"version":3,"file":"Chat.d.ts","sourceRoot":"","sources":["../../../src/screens/Chat.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAwBzB,OAAO,EAAqB,KAAK,OAAO,EAAsE,MAAM,2BAA2B,CAAA;AAC/I,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAChE,OAAO,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAA;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAA;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAA;AAGlE,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAA;AAwJ3D,wBAAgB,IAAI,CAAC,EACnB,OAAO,EACP,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,eAAe,EACf,kBAAkB,EAClB,MAAM,EACN,QAAQ,EACR,SAAS,EACT,UAAkB,EAClB,cAAc,EAAE,kBAAkB,GACnC,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,aAAa,EAAE,aAAa,CAAA;IAC5B;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAA;IAC7B;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAA;IACjC,oEAAoE;IACpE,eAAe,CAAC,EAAE,cAAc,CAAA;IAChC,iDAAiD;IACjD,kBAAkB,CAAC,EAAE,eAAe,CAAA;IACpC,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IACrB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,4BAq0GA"}
@@ -39,6 +39,7 @@ import { OverlayAbove } from '../components/OverlayAbove.js';
39
39
  import { PromptInput } from '../components/PromptInput.js';
40
40
  import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
41
41
  import { AutoRecapRow } from '../components/AutoRecapRow.js';
42
+ import { BalanceReportRow } from '../components/BalanceReportRow.js';
42
43
  import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
43
44
  import { StatusLine } from './StatusLine.js';
44
45
  import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
@@ -108,22 +109,6 @@ const NO_ROWS = [];
108
109
  function capitalize(text) {
109
110
  return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
110
111
  }
111
- /**
112
- * CC's built-in skill commands, driven through the DSH skill system: each
113
- * submits an activation prompt the model resolves via its skill catalog/load
114
- * tools (the corresponding SKILL.md ships under ~/.dsh/skills with dsh-tui).
115
- */
116
- // i18n keys, not resolved strings: module scope evaluates before apply()'s
117
- // setLang, so t() must run at the call site to follow the active language.
118
- const SKILL_PROMPTS = {
119
- audit: 'skill-audit-prompt',
120
- bug: 'skill-bug-prompt',
121
- practice: 'skill-practice-prompt',
122
- review: 'skill-review-prompt',
123
- pr_comments: 'skill-pr-comments-prompt',
124
- 'release-notes': 'skill-release-notes-prompt',
125
- 'vuln-check': 'skill-vuln-check-prompt',
126
- };
127
112
  /** Terminal-title spinner frames (CC's TITLE_ANIMATION_FRAMES). */
128
113
  const TITLE_ANIMATION_FRAMES = ['⠂', '⠐'];
129
114
  /** Searchable transcript text for one row (`/` incsearch, CC semantics:
@@ -349,6 +334,25 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
349
334
  recapAbortRef.current = null;
350
335
  setRecap(null);
351
336
  };
337
+ /** /balance report (`BalanceReportRow`): pure UI state like /recap — the
338
+ * result never enters the transcript or session log. Clicking the row
339
+ * re-queries (refreshing keeps the stale summary visible); a session
340
+ * switch retires the report. */
341
+ const [balance, setBalance] = React.useState(null);
342
+ const balanceSeqRef = React.useRef(0);
343
+ const runBalance = React.useCallback(() => {
344
+ const seq = ++balanceSeqRef.current;
345
+ setBalance(prev => ({ result: prev?.result ?? null, refreshing: true }));
346
+ void channel.balanceInfo().then(result => {
347
+ if (balanceSeqRef.current !== seq)
348
+ return;
349
+ setBalance({ result, refreshing: false });
350
+ });
351
+ }, [channel]);
352
+ const balanceSessionId = channel.agentId;
353
+ React.useEffect(() => {
354
+ setBalance(null);
355
+ }, [balanceSessionId]);
352
356
  // Auto-recap (`dsh-tui.recapOnOpen`): every time the session switches
353
357
  // (mount = open/resume, rewind/fork included), summarize its tail into
354
358
  // the dim AutoRecapRow. Failures stay silent in auto mode — `/recap`
@@ -1337,6 +1341,15 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1337
1341
  channel.pushLocal('/cost', lines);
1338
1342
  return true;
1339
1343
  }
1344
+ case 'balance': {
1345
+ // DeepSeek official account balance (free read-only endpoint): the
1346
+ // channel resolves DEEPSEEK_API_KEY through the credentials seam and
1347
+ // queries api.deepseek.com/user/balance. The result renders as the
1348
+ // interactive BalanceReportRow (hover for details, click to refresh).
1349
+ setHelpOpen(false);
1350
+ runBalance();
1351
+ return true;
1352
+ }
1340
1353
  case 'settings': {
1341
1354
  // Plugin settings screen (issue #165): opens immediately; the screen
1342
1355
  // reads sections + namespaces from the channel itself.
@@ -1718,21 +1731,6 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1718
1731
  setHelpOpen(false);
1719
1732
  channel.pushLocal('/connect', [t('connect-none')]);
1720
1733
  return true;
1721
- case 'audit':
1722
- case 'bug':
1723
- case 'practice':
1724
- case 'review':
1725
- case 'pr_comments':
1726
- case 'release-notes':
1727
- case 'vuln-check': {
1728
- // CC's skill commands: drive the DSH skill system by sending the
1729
- // activation prompt to the model (it loads the skill via its skill
1730
- // catalog/load tools when the SKILL.md ships in ~/.dsh/skills).
1731
- const key = SKILL_PROMPTS[name];
1732
- if (key)
1733
- channel.submit(t(key));
1734
- return true;
1735
- }
1736
1734
  default: {
1737
1735
  // Plugin-registered command (DSH command registry): dispatch through
1738
1736
  // the channel, whose execution logs command/run + command/done (the
@@ -2737,7 +2735,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2737
2735
  // double-press exit when the input is empty; ctrl+d keeps the
2738
2736
  // time-based double-press exit regardless.
2739
2737
  if (channel.working) {
2740
- channel.cancel();
2738
+ // First press while working only interrupts. If that abort is still
2739
+ // converging (cancelPending) the next press is the user insisting on
2740
+ // leaving: go straight to the exit funnel. Without this, a stuck turn
2741
+ // (long tool call that never settles, silent stream) swallows every
2742
+ // Ctrl+C forever — raw mode keeps the launcher's SIGINT escape
2743
+ // unreachable until the TUI exits.
2744
+ if (channel.cancelPending) {
2745
+ onExit();
2746
+ }
2747
+ else {
2748
+ channel.cancel();
2749
+ // Interrupt replaces any previously armed exit: the next press
2750
+ // must re-confirm instead of exiting out from under the turn.
2751
+ exitPendingRef.current = false;
2752
+ if (exitTimerRef.current)
2753
+ clearTimeout(exitTimerRef.current);
2754
+ }
2741
2755
  }
2742
2756
  else if (input === 'c' && promptControllerRef.current?.hasText()) {
2743
2757
  promptControllerRef.current.clear();
@@ -2791,6 +2805,29 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2791
2805
  // Working-activity line (spinner slot): context-pressure prefix shares the
2792
2806
  // StatusLine thresholds (amber ≥ 80, red ≥ 95).
2793
2807
  const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
2808
+ // ── Interrupt lane ─────────────────────────────────────────────────────
2809
+ // The approval and ask_user_question panels park the agent until the user
2810
+ // answers, but they render inside the conversation layout — every screen
2811
+ // early-return below (plugin scene, browser, settings, subagent, trace)
2812
+ // used to win over them, leaving the session stuck with no visible cause.
2813
+ // While one is pending and a screen is up, the panel takes the whole
2814
+ // terminal INSTEAD of the screen. The screen's open flag survives, so the
2815
+ // decision lands back on the screen (remounted fresh — the same lifecycle
2816
+ // as closing and reopening it); keyboard exclusivity holds because the
2817
+ // covered screen is unmounted, exactly like the chat-state prompt slot.
2818
+ // The panel elements are shared with the prompt-slot chain below so the
2819
+ // two mount sites cannot drift.
2820
+ const approvalPanelNode = approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : null;
2821
+ const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent(), onBack: questionSnapshot.canGoBack
2822
+ ? draft => questionStore.backCurrent(draft)
2823
+ : undefined }, questionSnapshot.key)) : null;
2824
+ const interruptPanel = approvalPanelNode ?? questionPanelNode;
2825
+ const screenOpen = channel.pluginScene !== undefined || browserOpen || settingsOpen
2826
+ || subagentDetailId !== null || subagentDashboardOpen || sceneOpen;
2827
+ if (interruptPanel !== null && screenOpen) {
2828
+ const node = (_jsx(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: interruptPanel }));
2829
+ return fullscreen ? node : _jsx(AlternateScreen, { children: node });
2830
+ }
2794
2831
  // A plugin scene (dsh-tui-scenes) takes the whole terminal the same way
2795
2832
  // the trajectory scene does, and sits at the TOP of this return chain:
2796
2833
  // an open() landing while the session browser or the trajectory scene is
@@ -2925,7 +2962,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2925
2962
  // transcript mount batches (and the first wheel events) for the
2926
2963
  // frame budget right when the user wants to read history. Fresh
2927
2964
  // sessions keep the full intro; restored ones settle instantly.
2928
- skipIntro: channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamFoldedRows: streamFoldedRows, onToggleStreamFold: toggleStreamFolded, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: (agentId) => setSubagentDetailId(agentId), onOpenFile: openFileActions })] }), (() => {
2965
+ skipIntro: channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamFoldedRows: streamFoldedRows, onToggleStreamFold: toggleStreamFolded, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: (agentId) => setSubagentDetailId(agentId), onOpenFile: openFileActions })] }), (() => {
2929
2966
  // Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
2930
2967
  // rail (default), the proportional scrollbar, or nothing. The
2931
2968
  // slot keeps its 2 columns in both rendered modes (Qwen's
@@ -2948,7 +2985,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2948
2985
  // the animated chars/4 estimate, matching the classic
2949
2986
  // spinner's counter (the suffix used raw chars before,
2950
2987
  // inflating the reading next to a real upload number).
2951
- suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : dialogSnapshot !== null ? (_jsx(ExtensionDialog, { dialog: dialogSnapshot, onDecide: value => dialogs.decide(dialogSnapshot.key, value), onCancel: () => dialogs.cancel(dialogSnapshot.key) }, dialogSnapshot.key)) : overlay.kind === 'tips' ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(TipsPanel, { onClose: () => dispatchOverlay({ type: 'close-if', kind: 'tips' }) }) })) : recap !== null && (!recap.auto || recap.expanded) ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RecapPanel, { summary: recap.summary, title: recap.title, error: recap.error, streaming: !recap.done, titleApplied: recap.titleApplied, onClose: () => {
2988
+ suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), balance !== null && (_jsx(BalanceReportRow, { result: balance.result, refreshing: balance.refreshing, tokens: channel.tokens, model: channel.model, onRefresh: runBalance, onDismiss: () => setBalance(null) })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), approvalPanelNode !== null ? (approvalPanelNode) : dialogSnapshot !== null ? (_jsx(ExtensionDialog, { dialog: dialogSnapshot, onDecide: value => dialogs.decide(dialogSnapshot.key, value), onCancel: () => dialogs.cancel(dialogSnapshot.key) }, dialogSnapshot.key)) : overlay.kind === 'tips' ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(TipsPanel, { onClose: () => dispatchOverlay({ type: 'close-if', kind: 'tips' }) }) })) : recap !== null && (!recap.auto || recap.expanded) ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RecapPanel, { summary: recap.summary, title: recap.title, error: recap.error, streaming: !recap.done, titleApplied: recap.titleApplied, onClose: () => {
2952
2989
  // An expanded auto recap collapses back to its dim row;
2953
2990
  // a manual /recap closes outright.
2954
2991
  if (recap.auto) {
@@ -2971,9 +3008,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2971
3008
  void setClipboard(btw.answer ?? '').then(raw => { if (raw)
2972
3009
  writeRaw?.(raw); });
2973
3010
  channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
2974
- } }) })) : questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent(), onBack: questionSnapshot.canGoBack
2975
- ? draft => questionStore.backCurrent(draft)
2976
- : undefined }, questionSnapshot.key)) : (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
3011
+ } }) })) : questionPanelNode !== null ? (questionPanelNode) : (_jsx(PromptInput, { channel: channel, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, controllerRef: promptControllerRef })), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
2977
3012
  ? undefined
2978
3013
  : {
2979
3014
  band: wakeBand,
@@ -1 +1 @@
1
- {"version":3,"file":"SessionBrowser.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAsBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAoDxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAkhBlB"}
1
+ {"version":3,"file":"SessionBrowser.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionBrowser.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAwBzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAoDxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,IAAI,EACJ,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,WAAW,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,OAAO,CAAA;IAC9C,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,GAAG,KAAK,CAAC,SAAS,CAiiBlB"}
@@ -10,22 +10,23 @@ import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
10
10
  import { isMod, isPlainReturn, modLabel } from '../utils/modifiers.js';
11
11
  import { formatProject, spreadRow, tailWidth, truncateWidth } from '../sessions/format.js';
12
12
  import { stringWidth } from '../ink/stringWidth.js';
13
+ import { TICK, MULTIPLICATION_X } from '../cc/figures.js';
13
14
  import { anchorTop, buildView, DEFAULT_FILTERS, moveSelection, seekSelectable, sessionAt, windowEnd, } from '../sessions/view.js';
14
15
  import { t } from '../i18n.js';
15
16
  /**
16
- * Rows the layout cannot do without: the header, the search box, the hints.
17
- * Everything else — the rules, the list itself — yields before these do.
17
+ * Rows the layout cannot do without: the header, the bordered search card
18
+ * (three rows: top border, input, bottom border), the notice slot, and the
19
+ * hints. Everything else — the rule, the list itself — yields before these do.
18
20
  */
19
- const MANDATORY_LINES = 3;
21
+ const MANDATORY_LINES = 6;
20
22
  /**
21
- * The three horizontal rules, in the order they earn their row.
23
+ * The one remaining horizontal rule, above the hints.
22
24
  *
23
- * 0 frames the header, 2 lifts the hints off the content, 1 separates the
24
- * search box — and that last one is the least load-bearing, so on a terminal
25
- * too short for all three it is the one that goes. Rules are decoration; a
26
- * row of content, or the hint line telling the user which keys exist, is not.
25
+ * The search card's own border separates it from the header and the list, so
26
+ * the two rules that used to flank it are gone; what remains lifts the hints
27
+ * off the content — decoration, and the first thing a short terminal drops.
27
28
  */
28
- const RULE_PRIORITY = [0, 2, 1];
29
+ const RULE_PRIORITY = [2];
29
30
  /** Terminal width below which the preview replaces the list instead of joining it. */
30
31
  const SPLIT_MIN_COLUMNS = 100;
31
32
  /** A thrown value's message, for a notification that has to say something. */
@@ -217,6 +218,16 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
217
218
  if (landed !== undefined)
218
219
  setFocusId(landed.id);
219
220
  };
221
+ /**
222
+ * Mouse wheel over the list walks the cursor (the window is cursor-follow,
223
+ * so rolling IS scrolling; the preview pane rides along). Confirm/rename
224
+ * seats keep the keyboard as sole owner.
225
+ */
226
+ const handleWheel = (event) => {
227
+ if (mode !== 'list')
228
+ return;
229
+ step(event.deltaY >= 0 ? 1 : -1);
230
+ };
220
231
  // Preview follows the cursor. Keyed on the id so arrowing past a row does
221
232
  // not refetch the one it came from, and guarded on unmount so a slow read
222
233
  // landing after the cursor moved cannot overwrite a newer preview.
@@ -247,14 +258,16 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
247
258
  const previewWidth = splitPreview ? Math.min(56, Math.floor(columns * 0.42)) : columns;
248
259
  const listWidth = Math.max(20, columns - (splitPreview ? previewWidth : 0));
249
260
  // Height is distributed, never assumed. Mandatory rows first, then a row
250
- // for each region the current state actually needs, then the rules while
261
+ // for each region the current state actually needs, then the rule while
251
262
  // rows remain, and the list gets what is left — which may be nothing.
252
263
  //
253
264
  // The arithmetic below is the whole vertical layout: the regions sum to
254
265
  // exactly `rows` at every height down to MANDATORY_LINES. Anything that
255
266
  // assumed a fixed chrome would overflow on a short terminal, and the row
256
267
  // that falls off the bottom is always the last one — the hints.
257
- const extraLines = (mode === 'list' ? 0 : 1) + (notice === undefined ? 0 : 1);
268
+ // The notice slot is mandatory (permanent): a delete/rename report must
269
+ // never shift the list by arriving.
270
+ const extraLines = mode === 'list' ? 0 : 1;
258
271
  const ruleBudget = Math.max(0, Math.min(RULE_PRIORITY.length, rows - MANDATORY_LINES - extraLines));
259
272
  const rules = new Set(RULE_PRIORITY.slice(0, ruleBudget));
260
273
  const listHeight = Math.max(0, rows - MANDATORY_LINES - extraLines - rules.size);
@@ -469,12 +482,14 @@ export function SessionBrowser({ channel, home, sameProject, onClose, }) {
469
482
  t('session-hint-list-mid', { mod: modLabel }),
470
483
  t('session-hint-list-short'),
471
484
  ], inputBudget);
472
- return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), rules.has(0) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(filters.query, inputBudget), isFocused: mode === 'list', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('session-search-placeholder', { scope }), inputBudget), borderless: true }) }), rules.has(1) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { width: columns }) })), _jsxs(Box, { flexGrow: 1, flexShrink: 1, children: [!soloPreview && (_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [!loaded && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-loading'), listWidth - 2)}` })), loaded && view.rows.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('resume-none-in-cwd'), listWidth - 2)}` })), visible.map((row, index) => row.kind === 'project' ? (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "planMode", children: truncateWidth(` ${formatProject(row.project, home)}`, listWidth - 6) }), _jsx(Text, { dimColor: true, children: ` ${row.count}` })] }, `project:${row.project}:${index}`)) : (_jsx(SessionListRow, { session: row.session, width: listWidth, depth: row.depth, focused: windowTop + index === focus, now: now,
485
+ return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), _jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(filters.query, inputBudget), isFocused: mode === 'list', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('session-search-placeholder', { scope }), inputBudget) }) }), _jsxs("ink-box", { style: { flexDirection: 'row', flexGrow: 1, flexShrink: 1, overflow: 'hidden' }, onWheel: handleWheel, children: [!soloPreview && (_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [!loaded && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-loading'), listWidth - 2)}` })), loaded && view.rows.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('resume-none-in-cwd'), listWidth - 2)}` })), visible.map((row, index) => row.kind === 'project' ? (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "planMode", children: truncateWidth(` ${formatProject(row.project, home)}`, listWidth - 6) }), _jsx(Text, { dimColor: true, children: ` ${row.count}` })] }, `project:${row.project}:${index}`)) : (_jsx(SessionListRow, { session: row.session, width: listWidth, depth: row.depth, focused: windowTop + index === focus, now: now,
473
486
  // 点击行 = 聚焦 + 恢复该会话(与 Enter 同路径)
474
487
  onClick: () => {
475
488
  setFocusId(row.session.id);
476
489
  resumeSession(row.session);
477
- } }, row.session.id)))] })), (splitPreview || soloPreview) && focused !== undefined && (_jsx(SessionPreview, { session: focused, entries: preview, loading: previewLoading, width: previewWidth, height: listHeight, home: home, now: now }))] }), notice !== undefined && (_jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: notice.tone === 'error' ? 'error' : 'success', children: ` ${truncateWidth(notice.text, Math.max(0, columns - 2))}` }) })), mode === 'confirm-delete' && focused !== undefined && (_jsx(Box, { flexShrink: 0, onClick: () => {
490
+ } }, row.session.id)))] })), (splitPreview || soloPreview) && focused !== undefined && (_jsx(SessionPreview, { session: focused, entries: preview, loading: previewLoading, width: previewWidth, height: listHeight, home: home, now: now }))] }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: notice?.tone === 'error' ? 'error' : 'success', children: notice === undefined
491
+ ? ' '
492
+ : ` ${notice.tone === 'error' ? MULTIPLICATION_X : TICK} ${truncateWidth(notice.text, Math.max(0, columns - 4))}` }) }), mode === 'confirm-delete' && focused !== undefined && (_jsx(Box, { flexShrink: 0, onClick: () => {
478
493
  // 点击确认行 = 确认删除(与 Enter 同路径);确认屏本身就是
479
494
  // 显式确认层,取消保留键盘 Esc,防误点
480
495
  setMode('list');
@@ -1 +1 @@
1
- {"version":3,"file":"SessionTree.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAUzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AAqIxD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,gBAAgB,EAChB,OAAO,EACP,aAAa,GACd,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,yEAAyE;IACzE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC,GAAG,KAAK,CAAC,SAAS,CAselB"}
1
+ {"version":3,"file":"SessionTree.d.ts","sourceRoot":"","sources":["../../../src/screens/SessionTree.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAYzB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAA;AA8IxD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,gBAAgB,EAChB,OAAO,EACP,aAAa,GACd,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,sEAAsE;IACtE,gBAAgB,EAAE,MAAM,CAAA;IACxB,OAAO,EAAE,MAAM,IAAI,CAAA;IACnB,yEAAyE;IACzE,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;CACtC,GAAG,KAAK,CAAC,SAAS,CAyflB"}