@deepseek-harness-tui/dsh-tui 0.9.0 → 0.9.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 (307) hide show
  1. package/README.md +69 -256
  2. package/bin/dsh-tui.js +172 -5
  3. package/cordis.patch.yml +16 -4
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
  5. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
  7. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
  8. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
  9. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
  11. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
  12. package/dsh-ecosystem-spec/registry/README.md +14 -14
  13. package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
  14. package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
  15. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
  16. package/lib/types/cc/hyperlink.d.ts +7 -1
  17. package/lib/types/cc/hyperlink.d.ts.map +1 -1
  18. package/lib/types/cc/hyperlink.js +3 -2
  19. package/lib/types/cc/markdown.d.ts.map +1 -1
  20. package/lib/types/cc/markdown.js +33 -3
  21. package/lib/types/cc/sessionColors.d.ts +27 -0
  22. package/lib/types/cc/sessionColors.d.ts.map +1 -0
  23. package/lib/types/cc/sessionColors.js +39 -0
  24. package/lib/types/commands.d.ts.map +1 -1
  25. package/lib/types/commands.js +6 -0
  26. package/lib/types/components/AutoRecapRow.d.ts +23 -0
  27. package/lib/types/components/AutoRecapRow.d.ts.map +1 -0
  28. package/lib/types/components/AutoRecapRow.js +23 -0
  29. package/lib/types/components/ColorPicker.d.ts +17 -0
  30. package/lib/types/components/ColorPicker.d.ts.map +1 -0
  31. package/lib/types/components/ColorPicker.js +20 -0
  32. package/lib/types/components/CommandSuggestions.d.ts +3 -1
  33. package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
  34. package/lib/types/components/ContextBarView.d.ts +33 -0
  35. package/lib/types/components/ContextBarView.d.ts.map +1 -0
  36. package/lib/types/components/ContextBarView.js +46 -0
  37. package/lib/types/components/EffortInputBorder.d.ts +12 -1
  38. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  39. package/lib/types/components/EffortInputBorder.js +22 -5
  40. package/lib/types/components/FileActionsPanel.d.ts +22 -0
  41. package/lib/types/components/FileActionsPanel.d.ts.map +1 -0
  42. package/lib/types/components/FileActionsPanel.js +28 -0
  43. package/lib/types/components/FileSuggestions.d.ts +3 -1
  44. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  45. package/lib/types/components/MessageList.d.ts +3 -1
  46. package/lib/types/components/MessageList.d.ts.map +1 -1
  47. package/lib/types/components/MessageList.js +13 -6
  48. package/lib/types/components/ModelPicker.d.ts +26 -4
  49. package/lib/types/components/ModelPicker.d.ts.map +1 -1
  50. package/lib/types/components/ModelPicker.js +26 -6
  51. package/lib/types/components/PromptInput.d.ts.map +1 -1
  52. package/lib/types/components/PromptInput.js +365 -57
  53. package/lib/types/components/RecapPanel.d.ts +25 -0
  54. package/lib/types/components/RecapPanel.d.ts.map +1 -0
  55. package/lib/types/components/RecapPanel.js +55 -0
  56. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
  57. package/lib/types/components/ScrollbarGutter.js +55 -4
  58. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  59. package/lib/types/components/SubagentCard.js +6 -4
  60. package/lib/types/components/SuggestionCard.d.ts +4 -2
  61. package/lib/types/components/SuggestionCard.d.ts.map +1 -1
  62. package/lib/types/components/TimelineRail.d.ts.map +1 -1
  63. package/lib/types/components/TimelineRail.js +9 -2
  64. package/lib/types/components/design-system/ThemeProvider.d.ts +7 -0
  65. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
  66. package/lib/types/components/design-system/ThemeProvider.js +2 -1
  67. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +8 -1
  68. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  69. package/lib/types/components/messages/AssistantToolUseMessage.js +36 -8
  70. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +7 -3
  71. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  72. package/lib/types/components/questions/AskUserQuestionPanel.js +41 -9
  73. package/lib/types/dsh-adapter/channel.d.ts +101 -2
  74. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  75. package/lib/types/dsh-adapter/channel.js +969 -93
  76. package/lib/types/dsh-adapter/compat/index.d.ts +8 -6
  77. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  78. package/lib/types/dsh-adapter/compat/index.js +8 -6
  79. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +150 -0
  80. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  81. package/lib/types/dsh-adapter/compat/sessionLog.js +518 -41
  82. package/lib/types/dsh-adapter/contract.d.ts +1 -1
  83. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  84. package/lib/types/dsh-adapter/contract.js +1 -0
  85. package/lib/types/dsh-adapter/extension-events.d.ts +1 -1
  86. package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
  87. package/lib/types/dsh-adapter/index.d.ts +10 -0
  88. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  89. package/lib/types/dsh-adapter/index.js +5 -0
  90. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  91. package/lib/types/dsh-adapter/plugin.js +317 -8
  92. package/lib/types/dsh-adapter/presets.d.ts +18 -0
  93. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  94. package/lib/types/dsh-adapter/presets.js +31 -0
  95. package/lib/types/dsh-adapter/providerWizard.d.ts +29 -1
  96. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  97. package/lib/types/dsh-adapter/providerWizard.js +89 -5
  98. package/lib/types/dsh-adapter/questions.d.ts +19 -2
  99. package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
  100. package/lib/types/dsh-adapter/questions.js +53 -10
  101. package/lib/types/dsh-adapter/recap.d.ts +49 -0
  102. package/lib/types/dsh-adapter/recap.d.ts.map +1 -0
  103. package/lib/types/dsh-adapter/recap.js +121 -0
  104. package/lib/types/dsh-adapter/sessionTree.d.ts +314 -0
  105. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -0
  106. package/lib/types/dsh-adapter/sessionTree.js +954 -0
  107. package/lib/types/dsh-adapter/shortcuts.d.ts +15 -44
  108. package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
  109. package/lib/types/dsh-adapter/shortcuts.js +28 -165
  110. package/lib/types/dsh-adapter/types.d.ts +1 -1
  111. package/lib/types/dsh-adapter/types.d.ts.map +1 -1
  112. package/lib/types/herdr.js +1 -1
  113. package/lib/types/i18n.d.ts +604 -176
  114. package/lib/types/i18n.d.ts.map +1 -1
  115. package/lib/types/i18n.js +206 -72
  116. package/lib/types/ink/log-update.d.ts.map +1 -1
  117. package/lib/types/ink/log-update.js +21 -0
  118. package/lib/types/migrationPrefs.d.ts +60 -0
  119. package/lib/types/migrationPrefs.d.ts.map +1 -0
  120. package/lib/types/migrationPrefs.js +97 -0
  121. package/lib/types/modelGroups.d.ts +72 -0
  122. package/lib/types/modelGroups.d.ts.map +1 -0
  123. package/lib/types/modelGroups.js +97 -0
  124. package/lib/types/modelRecents.d.ts +39 -0
  125. package/lib/types/modelRecents.d.ts.map +1 -0
  126. package/lib/types/modelRecents.js +97 -0
  127. package/lib/types/native-ts/yoga-layout/index.d.ts +11 -0
  128. package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
  129. package/lib/types/native-ts/yoga-layout/index.js +31 -1
  130. package/lib/types/oauth.d.ts +15 -0
  131. package/lib/types/oauth.d.ts.map +1 -0
  132. package/lib/types/oauth.js +14 -0
  133. package/lib/types/plugin-spec/registry.d.ts +1 -1
  134. package/lib/types/plugin-spec/registry.js +1 -1
  135. package/lib/types/reload.d.ts +109 -0
  136. package/lib/types/reload.d.ts.map +1 -0
  137. package/lib/types/reload.js +118 -0
  138. package/lib/types/screens/Chat.d.ts +3 -1
  139. package/lib/types/screens/Chat.d.ts.map +1 -1
  140. package/lib/types/screens/Chat.js +588 -64
  141. package/lib/types/screens/SessionTree.d.ts +27 -0
  142. package/lib/types/screens/SessionTree.d.ts.map +1 -0
  143. package/lib/types/screens/SessionTree.js +555 -0
  144. package/lib/types/screens/StatusLine.d.ts +0 -9
  145. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  146. package/lib/types/screens/StatusLine.js +215 -52
  147. package/lib/types/screens/StatusMetrics.d.ts +35 -3
  148. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  149. package/lib/types/screens/StatusMetrics.js +63 -8
  150. package/lib/types/screens/chatOverlay.d.ts +17 -1
  151. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  152. package/lib/types/screens/chatOverlay.js +3 -1
  153. package/lib/types/tips.d.ts.map +1 -1
  154. package/lib/types/tips.js +22 -4
  155. package/lib/types/update.d.ts +159 -0
  156. package/lib/types/update.d.ts.map +1 -1
  157. package/lib/types/update.js +496 -30
  158. package/lib/types/utils/debug.d.ts +1 -1
  159. package/lib/types/utils/debug.js +3 -3
  160. package/lib/types/utils/fileTarget.d.ts +46 -0
  161. package/lib/types/utils/fileTarget.d.ts.map +1 -0
  162. package/lib/types/utils/fileTarget.js +178 -0
  163. package/lib/types/utils/keymap.d.ts +134 -0
  164. package/lib/types/utils/keymap.d.ts.map +1 -0
  165. package/lib/types/utils/keymap.js +311 -0
  166. package/lib/types/utils/mentions.d.ts +25 -2
  167. package/lib/types/utils/mentions.d.ts.map +1 -1
  168. package/lib/types/utils/mentions.js +69 -10
  169. package/lib/types/utils/openExternal.d.ts +50 -0
  170. package/lib/types/utils/openExternal.d.ts.map +1 -0
  171. package/lib/types/utils/openExternal.js +170 -0
  172. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -0
  173. package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +140 -0
  174. package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +11 -0
  175. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -0
  176. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.d.ts +14 -0
  177. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.js +67 -0
  178. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +64 -0
  179. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.js +170 -0
  180. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +79 -0
  181. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +196 -0
  182. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +68 -0
  183. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +193 -0
  184. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.d.ts +32 -0
  185. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.js +101 -0
  186. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +31 -0
  187. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +68 -0
  188. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.d.ts +69 -0
  189. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.js +129 -0
  190. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +70 -0
  191. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
  192. package/node_modules/@dsh-std/command/LICENSE +21 -21
  193. package/node_modules/@dsh-std/command/README.md +26 -26
  194. package/node_modules/@dsh-std/command/README.zh.md +28 -28
  195. package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
  196. package/node_modules/@dsh-std/command/package.json +22 -22
  197. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  198. package/node_modules/@dsh-std/connection/LICENSE +21 -21
  199. package/node_modules/@dsh-std/connection/README.md +42 -42
  200. package/node_modules/@dsh-std/connection/README.zh.md +42 -42
  201. package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  202. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  203. package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  204. package/node_modules/@dsh-std/connection/package.json +20 -20
  205. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  206. package/node_modules/@dsh-std/core/LICENSE +21 -21
  207. package/node_modules/@dsh-std/core/README.md +13 -13
  208. package/node_modules/@dsh-std/core/README.zh.md +13 -13
  209. package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  210. package/node_modules/@dsh-std/core/package.json +21 -21
  211. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  212. package/node_modules/@dsh-std/manifest/LICENSE +21 -21
  213. package/node_modules/@dsh-std/manifest/README.md +5 -5
  214. package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  215. package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  216. package/node_modules/@dsh-std/manifest/package.json +20 -20
  217. package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  218. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
  219. package/node_modules/@dsh-std/messages/LICENSE +21 -21
  220. package/node_modules/@dsh-std/messages/README.md +5 -5
  221. package/node_modules/@dsh-std/messages/README.zh.md +7 -7
  222. package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
  223. package/node_modules/@dsh-std/messages/package.json +20 -20
  224. package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
  225. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  226. package/node_modules/@dsh-std/presentation/LICENSE +21 -21
  227. package/node_modules/@dsh-std/presentation/README.md +27 -27
  228. package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  229. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  230. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  231. package/node_modules/@dsh-std/presentation/package.json +25 -25
  232. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
  233. package/node_modules/@dsh-std/storage/LICENSE +21 -21
  234. package/node_modules/@dsh-std/storage/README.md +5 -5
  235. package/node_modules/@dsh-std/storage/README.zh.md +7 -7
  236. package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
  237. package/node_modules/@dsh-std/storage/package.json +20 -20
  238. package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
  239. package/package.json +17 -5
  240. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  241. package/presets/liangshen/instruction-hint.mjs +8 -0
  242. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  243. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
  244. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
  245. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
  246. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  247. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  248. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  249. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
  250. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  251. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
  252. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
  253. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
  254. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  255. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
  256. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  257. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
  258. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
  259. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  260. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  261. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
  262. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  263. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  264. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
  265. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
  266. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  267. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  268. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  269. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
  270. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  271. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
  272. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
  273. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
  274. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  275. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
  276. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  277. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
  278. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
  279. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
  280. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  281. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
  282. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  283. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
  284. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
  285. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
  286. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  287. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
  288. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  289. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
  290. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
  291. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
  292. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  293. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  294. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  295. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
  296. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  297. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
  298. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
  299. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
  300. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  301. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
  302. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  303. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
  304. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
  305. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
  306. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  307. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
@@ -8,10 +8,12 @@ import { EffortChargeGlyph } from './EffortChargeGlyph.js';
8
8
  import { EffortInputBorder } from './EffortInputBorder.js';
9
9
  import { EffortTierBadge } from './EffortTierBadge.js';
10
10
  import { isLightThemeActive } from '../theme.js';
11
+ import { sessionColorHex } from '../cc/sessionColors.js';
11
12
  import { useDeclaredCursor } from '../ink/hooks/use-declared-cursor.js';
12
13
  import { noteAuxNumber } from '../ink/geometry-trace.js';
13
14
  import instances from '../ink/instances.js';
14
15
  import { stringWidth } from '../ink/stringWidth.js';
16
+ import { truncateToWidth } from '../ink/truncateToWidth.js';
15
17
  import { getGraphemeSegmenter } from '../utils/intl.js';
16
18
  import { formatClipboardInsert, readClipboard } from '../utils/clipboard.js';
17
19
  import { editInExternalEditor } from '../utils/externalEditor.js';
@@ -20,11 +22,25 @@ import { appendHistory } from '../history.js';
20
22
  import { mentionAtCaret } from '../utils/mentions.js';
21
23
  import { preserveSelection } from '../utils/fileSuggestions.js';
22
24
  import { isMod } from '../utils/modifiers.js';
25
+ import { actionMatches } from '../utils/keymap.js';
23
26
  import { CommandSuggestions } from './CommandSuggestions.js';
24
27
  import { FileSuggestions } from './FileSuggestions.js';
25
28
  import { HelpMenu } from './HelpMenu.js';
26
29
  import { OverlayAbove } from './OverlayAbove.js';
30
+ import { SuggestionCard, cardContentWidth } from './SuggestionCard.js';
27
31
  const HISTORY_LIMIT = 50;
32
+ /**
33
+ * Paste fold (CC-style collapse with a visible preview, no black box):
34
+ * a paste that leaves the input this big folds into a one-line chip
35
+ * showing the line/char count PLUS the first line of content. Hover peeks
36
+ * at the full text (window pinned to the head); clicking the chip — or
37
+ * the `▾` prefix on the first row while expanded — toggles the fold; Esc
38
+ * or any editing key unfolds first. Enter still submits the FULL text:
39
+ * folding never drops data.
40
+ */
41
+ const FOLD_MIN_LINES = 6;
42
+ const FOLD_MIN_CHARS = 600;
43
+ const isBigInput = (text) => text.split('\n').length >= FOLD_MIN_LINES || text.length >= FOLD_MIN_CHARS;
28
44
  function clipboardImageMediaType(path) {
29
45
  if (/\.png$/iu.test(path))
30
46
  return 'image/png';
@@ -179,6 +195,30 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
179
195
  const [themeName] = useTheme();
180
196
  const [value, setValue] = React.useState('');
181
197
  const [cursor, setCursor] = React.useState(0);
198
+ /**
199
+ * CC-style fold block: the [start, end) span of `value` that renders as
200
+ * a one-line chip while the text around it stays fully editable. Created
201
+ * by a big paste; only an EXPLICIT expand (chip/card click, Esc) or
202
+ * delete removes it — typing NEVER unfolds the block.
203
+ */
204
+ const [foldBlock, setFoldBlock] = React.useState(null);
205
+ /** Pointer over the input box (drives the hover peek card). */
206
+ const [hovered, setHovered] = React.useState(false);
207
+ /** 120ms grace so the pointer crossing the input border row from the
208
+ * chip up onto the peek card never flickers the card. */
209
+ const hoverLeaveTimerRef = React.useRef(null);
210
+ const hoverEnter = React.useCallback(() => {
211
+ if (hoverLeaveTimerRef.current) {
212
+ clearTimeout(hoverLeaveTimerRef.current);
213
+ hoverLeaveTimerRef.current = null;
214
+ }
215
+ setHovered(true);
216
+ }, []);
217
+ const hoverLeave = React.useCallback(() => {
218
+ if (hoverLeaveTimerRef.current)
219
+ clearTimeout(hoverLeaveTimerRef.current);
220
+ hoverLeaveTimerRef.current = setTimeout(() => setHovered(false), 120);
221
+ }, []);
182
222
  const valueRef = React.useRef(value);
183
223
  const cursorRef = React.useRef(cursor);
184
224
  valueRef.current = value;
@@ -211,6 +251,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
211
251
  React.useEffect(() => {
212
252
  if (fillText && fillText !== lastFill.current) {
213
253
  lastFill.current = fillText;
254
+ updateFoldBlock(null);
214
255
  setInput(fillText);
215
256
  onFillConsumed?.();
216
257
  }
@@ -218,9 +259,9 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
218
259
  // Double-tap Esc to clear (CC semantics).
219
260
  const escPendingRef = React.useRef(false);
220
261
  const escTimerRef = React.useRef(null);
221
- /** True while a Ctrl+V clipboard read is in flight (ignore repeat keys). */
262
+ /** True while a clipboard paste read is in flight (ignore repeat keys). */
222
263
  const clipboardBusyRef = React.useRef(false);
223
- /** True while the external editor owns the terminal (Ctrl+G round-trip). */
264
+ /** True while the external editor owns the terminal (editor-key round-trip). */
224
265
  const editorBusyRef = React.useRef(false);
225
266
  /** Enter dedupe window: cmd pipelines can deliver one Enter as `\r`+`\n`. */
226
267
  const lastEnterAtRef = React.useRef(0);
@@ -228,6 +269,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
228
269
  return () => {
229
270
  if (escTimerRef.current)
230
271
  clearTimeout(escTimerRef.current);
272
+ if (hoverLeaveTimerRef.current)
273
+ clearTimeout(hoverLeaveTimerRef.current);
231
274
  };
232
275
  }, []);
233
276
  const { columns, rows: terminalRows } = useTerminalSize();
@@ -290,28 +333,77 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
290
333
  !helpOpen &&
291
334
  !selectionActive &&
292
335
  fileEscRef.current !== mention?.start;
336
+ /** Fold-block state + a synchronous mirror (setInput reads the ref).
337
+ * Creating a block also drags a caret that sits inside it out to the
338
+ * block's end (the block is atomic; typing continues after it). */
339
+ const foldBlockRef = React.useRef(null);
340
+ const updateFoldBlock = (block) => {
341
+ foldBlockRef.current = block;
342
+ setFoldBlock(block);
343
+ if (block) {
344
+ const c = cursorRef.current;
345
+ if (c > block.start && c < block.end) {
346
+ cursorRef.current = block.end;
347
+ setCursor(block.end);
348
+ }
349
+ }
350
+ };
293
351
  const setInput = (next, cursorOffset = next.length) => {
294
- valueRef.current = next;
352
+ const prev = valueRef.current;
353
+ const prevCursor = cursorRef.current;
354
+ const block = foldBlockRef.current;
295
355
  // Normalize onto a grapheme boundary (also clamps into range): every
296
356
  // caller passes a caret they believe is on a character edge — paste
297
357
  // merges, history fills, and IME composition can still hand back an
298
358
  // offset inside a surrogate pair or combining cluster.
299
- cursorRef.current = normalizeCursorOffset(next, cursorOffset);
359
+ let offset = normalizeCursorOffset(next, cursorOffset);
360
+ if (block) {
361
+ // Fold block is atomic: the caret never lands INSIDE [start, end).
362
+ if (offset > block.start && offset < block.end) {
363
+ offset = offset - block.start < block.end - offset ? block.start : block.end;
364
+ }
365
+ // Every real edit happens AT the caret, so a block that starts at or
366
+ // after the caret shifts with the value delta; one fully past the
367
+ // caret is untouched. Whole-value replacements (fill/history/editor)
368
+ // clear the block explicitly at their call sites.
369
+ const delta = next.length - prev.length;
370
+ let start = block.start;
371
+ let end = block.end;
372
+ if (prevCursor <= block.start) {
373
+ start += delta;
374
+ end += delta;
375
+ }
376
+ start = Math.max(0, Math.min(start, next.length));
377
+ end = Math.max(start, Math.min(end, next.length));
378
+ if (start >= end)
379
+ updateFoldBlock(null);
380
+ else if (start !== block.start || end !== block.end)
381
+ updateFoldBlock({ start, end });
382
+ }
383
+ // The synchronous mirrors are what batch-dispatched events (one stdin
384
+ // read → several keys, no render in between) read on their next turn.
385
+ valueRef.current = next;
386
+ cursorRef.current = offset;
300
387
  setValue(next);
301
- setCursor(cursorRef.current);
388
+ setCursor(offset);
302
389
  };
303
390
  /**
304
391
  * Accept the selected file suggestion: replace ONLY the mention token at
305
392
  * the caret (prefix/suffix text survives), quoting whitespace paths. A
306
393
  * directory inserts `@dir/` without a trailing space so completion
307
- * continues into it; a file completes the token with a space.
394
+ * continues into it; a file completes the token with a space. A typed
395
+ * `#L12-14` suffix is NOT part of the replacement — completion ends at
396
+ * `pathEnd` so the line range survives acceptance (issue #359).
308
397
  */
309
398
  const acceptFile = (candidate) => {
310
399
  if (!mention)
311
400
  return;
312
401
  const file = candidate.path;
313
402
  const body = /\s/.test(file) ? `@"${file}"` : `@${file}`;
314
- const insert = candidate.kind === 'directory' ? body : `${body} `;
403
+ // A typed `#L12-14` suffix rides along AFTER the completed body (before
404
+ // the trailing space) — quoting a whitespace path must not detach it.
405
+ const suffix = mention.pathEnd === undefined ? '' : value.slice(mention.pathEnd, mention.end);
406
+ const insert = candidate.kind === 'directory' ? `${body}${suffix}` : `${body}${suffix} `;
315
407
  const next = value.slice(0, mention.start) + insert + value.slice(mention.end);
316
408
  setInput(next, mention.start + insert.length);
317
409
  setFileSelected(0);
@@ -388,6 +480,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
388
480
  return;
389
481
  }
390
482
  setInput(item.text);
483
+ updateFoldBlock(null);
391
484
  setSelectedCommand(0);
392
485
  setFileSelected(0);
393
486
  channel.notify(t('input-retracted'), { timeoutMs: 2000 });
@@ -449,13 +542,17 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
449
542
  return handled;
450
543
  };
451
544
  /** Clipboard reads are asynchronous; insert against the latest render so
452
- * typing while PowerShell owns the clipboard never gets overwritten. */
545
+ * typing while PowerShell owns the clipboard never gets overwritten.
546
+ * Returns the resulting value and the insertion offset so callers can
547
+ * apply the paste fold. */
453
548
  const insertClipboardAtCaret = (text) => {
454
549
  const current = valueRef.current;
455
550
  const position = cursorRef.current;
456
- setInput(current.slice(0, position) + text + current.slice(position), position + text.length);
551
+ const next = current.slice(0, position) + text + current.slice(position);
552
+ setInput(next, position + text.length);
457
553
  setSelectedCommand(0);
458
554
  setFileSelected(0);
555
+ return { next, at: position };
459
556
  };
460
557
  /** Line index of the cursor; -1 when the cursor is at the very end. */
461
558
  const cursorLine = (text, cursorOffset) => {
@@ -481,6 +578,39 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
481
578
  // the text/caret produced by the preceding event in that batch.
482
579
  const value = valueRef.current;
483
580
  const cursor = cursorRef.current;
581
+ // Fold block (CC-style): Esc expands it — it must NEVER clear text
582
+ // that LOOKS like one line; Backspace at the block's tail / Delete at
583
+ // its head deletes the WHOLE block in one key; ←/→ jump over the
584
+ // atomic block. Typing NEVER expands it — the caret lives outside the
585
+ // block and edits land in the visible text around it.
586
+ const block = foldBlockRef.current;
587
+ // Line-level moves/edits (↑/↓/Home/End/Ctrl+A/E/U/K/W) treat the
588
+ // block as an atomic row: boundaries that would land inside it are
589
+ // clamped to its edges.
590
+ const clampRowStart = (pos) => block && cursor >= block.end ? Math.max(pos, block.end) : pos;
591
+ const clampRowEnd = (pos) => block && cursor <= block.start ? Math.min(pos, block.start) : pos;
592
+ if (block) {
593
+ if (key.escape) {
594
+ updateFoldBlock(null);
595
+ return;
596
+ }
597
+ if ((key.backspace && cursor === block.end) || (key.delete && cursor === block.start)) {
598
+ const next = value.slice(0, block.start) + value.slice(block.end);
599
+ updateFoldBlock(null);
600
+ setInput(next, block.start);
601
+ setSelectedCommand(0);
602
+ setFileSelected(0);
603
+ return;
604
+ }
605
+ if (key.leftArrow && cursor === block.end) {
606
+ setInput(value, block.start);
607
+ return;
608
+ }
609
+ if (key.rightArrow && cursor === block.start) {
610
+ setInput(value, block.end);
611
+ return;
612
+ }
613
+ }
484
614
  // Grapheme boundaries of the current text: every caret move / delete
485
615
  // below snaps onto one of these offsets (never mid-cluster).
486
616
  const bounds = graphemeBoundaries(value);
@@ -497,13 +627,20 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
497
627
  // verbatim at the caret. Paste content may contain newlines — that is
498
628
  // NOT Enter — so this branch runs before the whole-line submit rule.
499
629
  if (event?.isPasted && input.length > 0) {
500
- insertAtCaret(input.replace(/\r\n/g, '\n').replace(/\r/g, '\n'));
630
+ const text = input.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
631
+ insertAtCaret(text);
632
+ // A big paste becomes a CC-style fold block right away (hover peeks
633
+ // at it); an existing block is replaced by the new paste's span.
634
+ if (isBigInput(text))
635
+ updateFoldBlock({ start: cursor, end: cursor + text.length });
501
636
  return;
502
637
  }
503
- // Ctrl+V / Cmd+V: raw mode hands the key to the app, so the clipboard is
504
- // read here — text, file paths when the file manager copied files, or an
505
- // exported temp-file path when the clipboard holds a raw image.
506
- if (isMod(key) && input === 'v') {
638
+ // Clipboard paste (default Ctrl+V / Cmd+V, plus the Alt+V alias for
639
+ // terminals that intercept Ctrl+V — combos are user-remappable via
640
+ // /settings): raw mode hands the key to the app, so the clipboard is
641
+ // read here — text, file paths when the file manager copied files, or
642
+ // an exported temp-file path when the clipboard holds a raw image.
643
+ if (actionMatches('paste', input, key)) {
507
644
  if (clipboardBusyRef.current)
508
645
  return;
509
646
  // Match insertAtCaret's overlay/selection dismissal up front: the
@@ -547,7 +684,11 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
547
684
  // Insert against the LIVE input state: the read above resolved
548
685
  // asynchronously and the user may have typed while waiting.
549
686
  const text = formatClipboardInsert(content);
550
- insertClipboardAtCaret(text);
687
+ const { at } = insertClipboardAtCaret(text);
688
+ // Same fold as bracketed paste: big clipboard text becomes a
689
+ // fold block covering exactly the pasted span.
690
+ if (isBigInput(text))
691
+ updateFoldBlock({ start: at, end: at + text.length });
551
692
  })
552
693
  .catch(() => {
553
694
  channel.notify(t('input-clipboard-read-failed'), { color: 'warning' });
@@ -559,27 +700,30 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
559
700
  });
560
701
  return;
561
702
  }
562
- // Help is modal for modified keys and every Enter variant. Ctrl+V above
563
- // is the intentional exception: paste closes Help and inserts visibly.
703
+ // Help is modal for modified keys and every Enter variant. The paste
704
+ // branch above is the intentional exception: paste closes Help and
705
+ // inserts visibly.
564
706
  // Swallow here before editor/submit/interrupt branches can mutate hidden
565
707
  // composer or working-turn state; plain typing still dismisses Help below.
566
708
  if (helpOpen && !key.escape && (key.ctrl || key.meta || key.super || key.return || input.includes('\n') || input.includes('\r'))) {
567
709
  event.stopImmediatePropagation();
568
710
  return;
569
711
  }
570
- // Ctrl+G: edit the current draft in $VISUAL/$EDITOR (issue #123,
571
- // readline's edit-and-execute-command). The draft is written to a temp
572
- // file, the terminal is handed to the editor (Ink's alt-screen handoff),
573
- // and the saved text replaces the input when it differs. The util maps
574
- // every failure to an outcome, but the catch/finally here is the hard
575
- // guarantee: a rejected promise must never kill the process, and the
576
- // busy flag must always clear or Ctrl+G stays locked forever.
577
- if (key.ctrl && input === 'g') {
712
+ // Ctrl+G (remappable via /settings): edit the current draft in
713
+ // $VISUAL/$EDITOR (issue #123, readline's edit-and-execute-command). The
714
+ // draft is written to a temp file, the terminal is handed to the editor
715
+ // (Ink's alt-screen handoff), and the saved text replaces the input when
716
+ // it differs. The util maps every failure to an outcome, but the
717
+ // catch/finally here is the hard guarantee: a rejected promise must
718
+ // never kill the process, and the busy flag must always clear or the
719
+ // editor key stays locked forever.
720
+ if (actionMatches('editor', input, key)) {
578
721
  editorBusyRef.current = true;
579
722
  void (async () => {
580
723
  try {
581
724
  const outcome = await editInExternalEditor(value);
582
725
  if (outcome.kind === 'edited') {
726
+ updateFoldBlock(null);
583
727
  setInput(outcome.text);
584
728
  setSelectedCommand(0);
585
729
  setFileSelected(0);
@@ -724,8 +868,10 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
724
868
  }
725
869
  if (key.tab && overlayOpen) {
726
870
  const command = suggestions[selectedCommand];
727
- if (command)
871
+ if (command) {
872
+ updateFoldBlock(null);
728
873
  setInput(command.replacement);
874
+ }
729
875
  return;
730
876
  }
731
877
  // Tab while the model is working = queue for AFTER the turn (followup),
@@ -779,6 +925,22 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
779
925
  setFileSelected(index => index <= 0 ? fileMatches.length - 1 : index - 1);
780
926
  return;
781
927
  }
928
+ // With a fold block the caret in the tail walks the TAIL's lines
929
+ // (the block is one atomic row); the first tail line steps over the
930
+ // block to its head.
931
+ if (block && cursor >= block.end) {
932
+ const tailCursor = cursor - block.end;
933
+ const line = cursorLine(tail, tailCursor);
934
+ if (line > 0) {
935
+ const upToLineStart = tail.lastIndexOf('\n', tailCursor - 1);
936
+ const prevLineStart = upToLineStart === -1 ? 0 : tail.lastIndexOf('\n', upToLineStart - 1) + 1;
937
+ const prevLine = tail.slice(prevLineStart, upToLineStart);
938
+ setInput(value, block.end + prevLineStart + Math.min(cursorColumn(tail, tailCursor), prevLine.length));
939
+ return;
940
+ }
941
+ setInput(value, block.start);
942
+ return;
943
+ }
782
944
  const line = cursorLine(value, cursor);
783
945
  if (line > 0) {
784
946
  // Move to the previous line, clamping to its length.
@@ -802,6 +964,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
802
964
  historyIndex.current = Math.max(0, historyIndex.current - 1);
803
965
  }
804
966
  const entry = history.current[historyIndex.current] ?? '';
967
+ updateFoldBlock(null);
805
968
  setInput(entry);
806
969
  return;
807
970
  }
@@ -810,6 +973,36 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
810
973
  setFileSelected(index => index >= fileMatches.length - 1 ? 0 : index + 1);
811
974
  return;
812
975
  }
976
+ // Mirror of the tail-side ↑ walk: with a fold block the caret walks
977
+ // the TAIL's lines; past its last line it falls through to the
978
+ // overlay/history handling below.
979
+ if (block && cursor >= block.end) {
980
+ const tailCursor = cursor - block.end;
981
+ const line = cursorLine(tail, tailCursor);
982
+ const lines = tail.split('\n');
983
+ if (line < lines.length - 1) {
984
+ const nextLineStart = tail.indexOf('\n', tailCursor) + 1;
985
+ const nextLineEnd = tail.indexOf('\n', nextLineStart);
986
+ const nextLine = tail.slice(nextLineStart, nextLineEnd === -1 ? tail.length : nextLineEnd);
987
+ setInput(value, block.end + nextLineStart + Math.min(cursorColumn(tail, tailCursor), nextLine.length));
988
+ return;
989
+ }
990
+ }
991
+ else if (block && cursor <= block.start) {
992
+ // Head side: walk the HEAD's lines; its last line steps over the
993
+ // block to the tail (never into history for a multi-row input).
994
+ const line = cursorLine(head, cursor);
995
+ const lines = head.split('\n');
996
+ if (line < lines.length - 1) {
997
+ const nextLineStart = head.indexOf('\n', cursor) + 1;
998
+ const nextLineEnd = head.indexOf('\n', nextLineStart);
999
+ const nextLine = head.slice(nextLineStart, nextLineEnd === -1 ? head.length : nextLineEnd);
1000
+ setInput(value, nextLineStart + Math.min(cursorColumn(head, cursor), nextLine.length));
1001
+ return;
1002
+ }
1003
+ setInput(value, block.end);
1004
+ return;
1005
+ }
813
1006
  const line = cursorLine(value, cursor);
814
1007
  const lines = value.split('\n');
815
1008
  if (line < lines.length - 1) {
@@ -827,6 +1020,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
827
1020
  return;
828
1021
  if (historyIndex.current >= history.current.length - 1) {
829
1022
  historyIndex.current = -1;
1023
+ updateFoldBlock(null);
830
1024
  setInput(historyDraft.current);
831
1025
  }
832
1026
  else {
@@ -871,43 +1065,44 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
871
1065
  return;
872
1066
  }
873
1067
  if (key.home) {
874
- // Start of the current line.
875
- const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
1068
+ // Start of the current line (the block is one atomic row).
1069
+ const lineStart = clampRowStart(value.lastIndexOf('\n', cursor - 1) + 1);
876
1070
  setInput(value, lineStart);
877
1071
  return;
878
1072
  }
879
1073
  if (key.end) {
880
1074
  // End of the current line.
881
1075
  const nextLine = value.indexOf('\n', cursor);
882
- setInput(value, nextLine === -1 ? value.length : nextLine);
1076
+ setInput(value, nextLine === -1 ? value.length : clampRowEnd(nextLine));
883
1077
  return;
884
1078
  }
885
1079
  if (isMod(key) && input === 'a') {
886
- const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
1080
+ const lineStart = clampRowStart(value.lastIndexOf('\n', cursor - 1) + 1);
887
1081
  setInput(value, lineStart);
888
1082
  return;
889
1083
  }
890
1084
  if (isMod(key) && input === 'e') {
891
1085
  const nextLine = value.indexOf('\n', cursor);
892
- setInput(value, nextLine === -1 ? value.length : nextLine);
1086
+ setInput(value, nextLine === -1 ? value.length : clampRowEnd(nextLine));
893
1087
  return;
894
1088
  }
895
1089
  if (isMod(key) && input === 'u') {
896
- // Delete to start of line.
897
- const lineStart = value.lastIndexOf('\n', cursor - 1) + 1;
1090
+ // Delete to start of line (never into the block).
1091
+ const lineStart = clampRowStart(value.lastIndexOf('\n', cursor - 1) + 1);
898
1092
  setInput(value.slice(0, lineStart) + value.slice(cursor), lineStart);
899
1093
  return;
900
1094
  }
901
1095
  if (isMod(key) && input === 'k') {
902
- // Delete to end of line.
1096
+ // Delete to end of line (never into the block).
903
1097
  const nextLine = value.indexOf('\n', cursor);
904
- const end = nextLine === -1 ? value.length : nextLine;
1098
+ const end = nextLine === -1 ? value.length : clampRowEnd(nextLine);
905
1099
  setInput(value.slice(0, cursor) + value.slice(end), cursor);
906
1100
  return;
907
1101
  }
908
1102
  if (isMod(key) && input === 'w') {
909
1103
  // Delete the word before the cursor (CC/readline behavior): skip
910
- // trailing whitespace, then the whitespace-delimited word.
1104
+ // trailing whitespace, then the whitespace-delimited word. The
1105
+ // deletion start never crosses into the block.
911
1106
  const before = value.slice(0, cursor);
912
1107
  let end = before.length;
913
1108
  while (end > 0 && /\s/.test(before[end - 1]))
@@ -915,7 +1110,8 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
915
1110
  let start = end;
916
1111
  while (start > 0 && !/\s/.test(before[start - 1]))
917
1112
  start--;
918
- setInput(value.slice(0, start) + value.slice(cursor), start);
1113
+ const clipped = clampRowStart(start);
1114
+ setInput(value.slice(0, clipped) + value.slice(cursor), clipped);
919
1115
  return;
920
1116
  }
921
1117
  if (key.escape) {
@@ -948,8 +1144,17 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
948
1144
  return;
949
1145
  }
950
1146
  // A single Esc clears the current input (if any); the double-tap
951
- // path below handles rewind/clear on an already-empty input.
1147
+ // path below handles rewind/clear on an already-empty input. A BIG
1148
+ // input folds into a block instead (Esc = the fold toggle; the
1149
+ // expand → fold → expand cycle is lossless, and clearing a big draft
1150
+ // stays reachable via the block-delete Backspace or Ctrl+C).
952
1151
  if (value.length > 0) {
1152
+ if (isBigInput(value)) {
1153
+ updateFoldBlock({ start: 0, end: value.length });
1154
+ setSelectedCommand(0);
1155
+ setFileSelected(0);
1156
+ return;
1157
+ }
953
1158
  setInput('', 0);
954
1159
  setSelectedCommand(0);
955
1160
  setFileSelected(0);
@@ -998,8 +1203,33 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
998
1203
  // single column — a fixed floor of 10 would wrap far too early and park
999
1204
  // the declared cursor past the value box's actual width.
1000
1205
  const inputWidth = Math.max(1, columns - 3);
1001
- const visualLines = wrapToWidth(value, inputWidth);
1002
- const caretVisualLine = wrapToWidth(value.slice(0, cursor), inputWidth).length - 1;
1206
+ const block = foldBlock;
1207
+ // Fold-block model: the value renders as [head rows][chip row][tail rows]
1208
+ // — the block is ONE atomic visual row regardless of its text size; text
1209
+ // before/after it stays fully editable and never unfolds it.
1210
+ const foldText = block ? value.slice(block.start, block.end) : value;
1211
+ const head = block ? value.slice(0, block.start) : '';
1212
+ const tail = block ? value.slice(block.end) : '';
1213
+ const headRows = block ? wrapToWidth(head, inputWidth) : [];
1214
+ const tailRows = block ? wrapToWidth(tail, inputWidth) : [];
1215
+ const chipRow = block ? headRows.length : -1;
1216
+ const visualLines = block
1217
+ ? [...headRows, '', ...tailRows]
1218
+ : wrapToWidth(value, inputWidth);
1219
+ const caretVisualLine = block
1220
+ ? cursor <= block.start
1221
+ ? wrapToWidth(value.slice(0, cursor), inputWidth).length - 1
1222
+ : headRows.length + 1 + wrapToWidth(value.slice(block.end, cursor), inputWidth).length - 1
1223
+ : wrapToWidth(value.slice(0, cursor), inputWidth).length - 1;
1224
+ // Fold stats describe the BLOCK (or the whole input when expanded and
1225
+ // the ▾ prefix offers a manual whole-input fold). The chip shows the
1226
+ // block's own line/char count + first-line preview — the preview text
1227
+ // around it is unaffected.
1228
+ const big = isBigInput(foldText);
1229
+ const stats = big
1230
+ ? t('input-fold-stats', { lines: foldText.split('\n').length, chars: foldText.length })
1231
+ : '';
1232
+ const peekOpen = block !== null && hovered && !selectionActive;
1003
1233
  const windowStart = Math.max(0, Math.min(caretVisualLine - MAX_VISIBLE_LINES + 1, visualLines.length - MAX_VISIBLE_LINES));
1004
1234
  const visibleLines = visualLines.slice(windowStart, windowStart + MAX_VISIBLE_LINES);
1005
1235
  // Caret position in the caret's visual row, in two units:
@@ -1009,35 +1239,75 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
1009
1239
  // cursor mid-character and Windows Terminal would paint the IME
1010
1240
  // preedit (pinyin) over the surrounding text).
1011
1241
  const caretCharCol = () => {
1012
- const before = value.slice(0, cursor);
1242
+ const before = block && cursor >= block.end ? value.slice(block.end, cursor) : value.slice(0, cursor);
1013
1243
  const rows = wrapToWidth(before, inputWidth);
1014
1244
  const last = rows[rows.length - 1] ?? '';
1015
1245
  return last.length;
1016
1246
  };
1017
1247
  const caretVisualCol = () => {
1018
- const before = value.slice(0, cursor);
1248
+ const before = block && cursor >= block.end ? value.slice(block.end, cursor) : value.slice(0, cursor);
1019
1249
  const rows = wrapToWidth(before, inputWidth);
1020
1250
  const last = rows[rows.length - 1] ?? '';
1021
1251
  return stringWidth(last);
1022
1252
  };
1253
+ // Folded chip content: block stats + first-line preview + hover hint,
1254
+ // all pre-truncated to the input width (the row is one line, always).
1255
+ const foldBadge = `▸ ${stats}`;
1256
+ const foldHint = t('input-fold-hover');
1257
+ const foldPreviewWidth = inputWidth - stringWidth(foldBadge) - stringWidth(` · ${foldHint}`) - 6;
1258
+ const foldPreview = foldPreviewWidth >= 8
1259
+ ? truncateToWidth(foldText.split('\n')[0] ?? '', foldPreviewWidth)
1260
+ : '';
1261
+ // Expanded-state fold affordance: a `▾` prefix at the start of the FIRST
1262
+ // row (only while the window is at the top and no block exists); its
1263
+ // cells fold the whole input into a block again on click.
1264
+ const prefixLabel = `▾ ${stats} · `;
1265
+ const prefixCols = !block && big && windowStart === 0 ? stringWidth(prefixLabel) : 0;
1023
1266
  const rendered = visibleLines.map((line, index) => {
1024
1267
  const absoluteLine = windowStart + index;
1268
+ if (block && absoluteLine === chipRow) {
1269
+ // The fold block's atomic chip row: click expands it; hover pops the
1270
+ // peek card. The row is one line no matter how big the block is.
1271
+ return (_jsxs(Box, { flexDirection: "row", onClick: (event) => {
1272
+ event.stopImmediatePropagation();
1273
+ updateFoldBlock(null);
1274
+ }, onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: [_jsx(Text, { dimColor: true, children: foldBadge }), foldPreview !== '' && _jsx(Text, { dimColor: true, children: " \u00B7 " }), foldPreview !== '' && _jsx(Text, { wrap: "truncate-end", children: foldPreview }), _jsx(Text, { dimColor: true, children: ` · ${foldHint}` })] }, `fold-${absoluteLine}`));
1275
+ }
1276
+ const withPrefix = absoluteLine === 0 && prefixCols > 0;
1277
+ const text = withPrefix ? truncateToWidth(line, inputWidth - prefixCols) : line;
1278
+ const prefix = withPrefix ? _jsx(Text, { dimColor: true, children: prefixLabel }) : null;
1025
1279
  if (absoluteLine !== caretVisualLine) {
1026
- return (_jsx(Text, { wrap: "truncate-end", children: line }, absoluteLine));
1280
+ return (_jsxs(Text, { wrap: "truncate-end", children: [prefix, text] }, absoluteLine));
1027
1281
  }
1028
1282
  // Caret row: invert the grapheme cluster at the caret column (solid
1029
1283
  // block). `col` is a cluster boundary by construction (the cursor is
1030
1284
  // normalized onto boundaries and wrapping only breaks between
1031
1285
  // graphemes), so [col, next boundary) covers the WHOLE cluster — a
1032
1286
  // surrogate pair or ZWJ emoji inverts as one glyph, never two broken
1033
- // halves.
1034
- const col = caretCharCol();
1035
- const clusterEnd = nextGraphemeBoundary(graphemeBoundaries(line), col);
1036
- const before = line.slice(0, col);
1037
- const at = clusterEnd > col ? line.slice(col, clusterEnd) : ' ';
1038
- const after = line.slice(clusterEnd);
1039
- return (_jsxs(Text, { wrap: "truncate-end", children: [before, _jsx(Text, { inverse: true, children: at }), after] }, absoluteLine));
1287
+ // halves. Clamped into the prefix-shortened row text so the block
1288
+ // caret never renders under the fold prefix.
1289
+ const col = Math.min(caretCharCol(), text.length);
1290
+ const clusterEnd = nextGraphemeBoundary(graphemeBoundaries(text), col);
1291
+ const before = text.slice(0, col);
1292
+ const at = clusterEnd > col ? text.slice(col, clusterEnd) : ' ';
1293
+ const after = text.slice(clusterEnd);
1294
+ return (_jsxs(Text, { wrap: "truncate-end", children: [prefix, before, _jsx(Text, { inverse: true, children: at }), after] }, absoluteLine));
1040
1295
  });
1296
+ // Peek card content: the BLOCK's text wrapped to the card's inner width,
1297
+ // capped at PEEK_MAX_ROWS visual rows (a preview — click to expand and
1298
+ // edit the real input). The footer reports the clipped remainder.
1299
+ const PEEK_MAX_ROWS = 10;
1300
+ const peekVisualLines = [];
1301
+ for (const line of foldText.split('\n')) {
1302
+ for (const row of wrapToWidth(line, cardContentWidth(columns))) {
1303
+ if (peekVisualLines.length >= PEEK_MAX_ROWS)
1304
+ break;
1305
+ peekVisualLines.push(row);
1306
+ }
1307
+ if (peekVisualLines.length >= PEEK_MAX_ROWS)
1308
+ break;
1309
+ }
1310
+ const peekClipped = peekVisualLines.length >= PEEK_MAX_ROWS;
1041
1311
  // Composer height shrink: clearing multi-line text (Enter/Esc/Ctrl+C/
1042
1312
  // Backspace) collapses the input area within one commit, shifting the
1043
1313
  // status line up and the whole chrome with it. The renderer's
@@ -1070,7 +1340,7 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
1070
1340
  // Clamp the declared column to the wrap width: a grapheme wider than
1071
1341
  // the last remaining column (emoji at width 1) can push the visual
1072
1342
  // column past inputWidth, and the park must stay inside the value box.
1073
- column: Math.min(caretVisualCol(), inputWidth),
1343
+ column: Math.min(caretVisualCol() + (caretVisualLine === 0 && prefixCols > 0 ? prefixCols : 0), inputWidth),
1074
1344
  active: !selectionActive,
1075
1345
  });
1076
1346
  /**
@@ -1078,22 +1348,55 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
1078
1348
  * row/column relative to the box) to a UTF-16 cursor offset via the same
1079
1349
  * grapheme walk the renderer wraps with — exact under CJK widths, wrapped
1080
1350
  * rows and multi-codepoint clusters. Clicks land on the boundary nearest
1081
- * the clicked cell (mid-grapheme snaps to its start).
1351
+ * the clicked cell (mid-grapheme snaps to its start). With a fold block,
1352
+ * clicks map into the head/tail text (the chip row has its own expand
1353
+ * onClick); without one, the fold prefix's cells fold the whole input.
1082
1354
  */
1083
1355
  const handleValueClick = React.useCallback((e) => {
1084
1356
  const clickedVisual = windowStart + e.localRow;
1085
1357
  const clamped = Math.max(0, Math.min(clickedVisual, visualLines.length - 1));
1086
- setCursor(clickToCursorOffset(value, inputWidth, clamped, e.localCol));
1087
- }, [windowStart, visualLines.length, value, inputWidth]);
1358
+ if (block) {
1359
+ if (clamped === chipRow)
1360
+ return;
1361
+ if (clamped < chipRow) {
1362
+ setCursor(clickToCursorOffset(head, inputWidth, clamped, e.localCol));
1363
+ }
1364
+ else {
1365
+ setCursor(block.end + clickToCursorOffset(tail, inputWidth, clamped - chipRow - 1, e.localCol));
1366
+ }
1367
+ return;
1368
+ }
1369
+ if (clamped === 0 && prefixCols > 0 && e.localCol < prefixCols) {
1370
+ updateFoldBlock({ start: 0, end: value.length });
1371
+ return;
1372
+ }
1373
+ const col = clamped === 0 && prefixCols > 0 ? Math.max(0, e.localCol - prefixCols) : e.localCol;
1374
+ setCursor(clickToCursorOffset(value, clamped === 0 && prefixCols > 0 ? inputWidth - prefixCols : inputWidth, clamped, col));
1375
+ }, [windowStart, visualLines.length, value, inputWidth, prefixCols, block, chipRow, head, tail]);
1088
1376
  // 浮层整体挂载条件:与内部面板可见条件精确同值。关闭时必须把整个
1089
1377
  // absolute 浮层移除——渲染器的 absolute-removed 检测只看被移除节点自身
1090
1378
  // 的 style.position,常驻浮层 + 移除普通子节点不会触发 blit 解毒,被
1091
1379
  // 覆盖的转录行会留空(见 Chat.tsx dialogOverlayOpen 注释)。
1092
- const floatersOpen = helpOpen || channel.pending.length > 0 || fileOverlayOpen || overlayOpen;
1380
+ const floatersOpen = helpOpen || channel.pending.length > 0 || fileOverlayOpen || overlayOpen || peekOpen;
1093
1381
  // 补全卡片边框与输入框 idle 边框同色(plan 模式下整套面板一起变 sage 绿)。
1094
- const promptAccent = channel.mode.plan === true ? 'planMode' : 'promptBorder';
1382
+ // `/color` 会话强调色优先于主题 promptBorder(plan 模式仍整体走 sage 绿)。
1383
+ // `?? ''` 防御最小 mock channel(只声明用到的字段的回归脚本)。
1384
+ const sessionAccent = sessionColorHex(channel.sessionColor ?? '');
1385
+ const promptAccent = channel.mode.plan === true ? 'planMode' : (sessionAccent ?? 'promptBorder');
1386
+ // 顶边框右侧的会话名标签(CC 风格 chip):色随强调色;超宽截断,宽度
1387
+ // 随终端列数伸缩但不超过 28 显示单元。默认关闭——`/settings` 的
1388
+ // 「会话名标签」开关(dsh-tui.promptSessionLabel)开启后显示。
1389
+ const sessionTitle = channel.sessionTitle ?? '';
1390
+ const topRightLabel = channel.promptSessionLabel === true && sessionTitle !== ''
1391
+ ? {
1392
+ text: truncateToWidth(sessionTitle, Math.max(8, Math.min(28, columns - 8))),
1393
+ color: channel.mode.plan === true ? 'planMode' : (sessionAccent ?? 'claude'),
1394
+ ink: 'inverseText',
1395
+ }
1396
+ : undefined;
1095
1397
  return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [floatersOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 6, 1), children: [helpOpen && (_jsx(Box, { marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList, viewportHeight: helpViewportHeight, viewportWidth: columns, scrollRef: helpScrollRef, onCommandPick: (name) => {
1096
1398
  // 点击命令行 = 填入 /name 并关闭帮助(Tab 补全的鼠标等价)
1399
+ updateFoldBlock(null);
1097
1400
  setInput(`/${name} `);
1098
1401
  onToggleHelp();
1099
1402
  } }) })), !helpOpen && channel.pending.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, paddingBottom: 1, children: [channel.pending.some(item => item.placement === 'steer') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u26A1 ", t('input-pending-steer-label')] }), channel.pending
@@ -1120,7 +1423,12 @@ export function PromptInput({ channel, helpOpen, onToggleHelp, onRunCommand, sel
1120
1423
  // 滚轮 = 移动选中行(与 ↑/↓ 同路径,窗口跟随)
1121
1424
  onWheelStep: (step) => {
1122
1425
  setSelectedCommand(i => Math.max(0, Math.min(suggestions.length - 1, i + step)));
1123
- } }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [_jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), _jsx(Box, { ref: valueBoxRef, flexGrow: 1, flexShrink: 1, onClick: handleValueClick, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) })] }) })] }));
1426
+ } })), peekOpen && (_jsx(Box, { onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: _jsx(SuggestionCard, { title: stats, columns: columns, accent: promptAccent, footer: peekClipped
1427
+ ? t('input-fold-peek-footer', { lines: foldText.split('\n').length })
1428
+ : undefined, rows: peekVisualLines.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, index))), onRowPick: () => {
1429
+ updateFoldBlock(null);
1430
+ setHovered(false);
1431
+ } }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [_jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), _jsx(Box, { ref: valueBoxRef, flexGrow: 1, flexShrink: 1, onClick: handleValueClick, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) })] }) })] }));
1124
1432
  }
1125
1433
  /**
1126
1434
  * Hard-wrap text into visual rows of at most `width` columns (CJK-aware via