@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
@@ -1,12 +1,22 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import { t, getLang, setLang, isLang, writeLangPref, subscribeLang, LANGS } from '../i18n.js';
3
+ import { t, getLang, setLang, isLang, writeLangPref, readLangPref, subscribeLang, LANGS } from '../i18n.js';
4
+ import { readThemePref } from '../themePrefs.js';
5
+ import { readPresetPref } from '../presetPrefs.js';
6
+ import { readModelPref } from '../modelPrefs.js';
7
+ import { readActivityFrames } from '../activityPrefs.js';
8
+ import { envThemeOverride } from '../components/design-system/ThemeProvider.js';
9
+ import { hasPath } from '../dsh-adapter/settingsEditor.js';
10
+ import { planReload } from '../reload.js';
4
11
  import { AlternateScreen, Box, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
5
12
  import * as tuiKit from '../ui.js';
6
13
  import { POINTER } from '../cc/figures.js';
7
- import { isMod, isPlainReturnInput, modLabel } from '../utils/modifiers.js';
14
+ import { isPlainReturnInput, modLabel } from '../utils/modifiers.js';
15
+ import { actionMatches } from '../utils/keymap.js';
8
16
  import { formatTokens } from '../cc/format.js';
9
17
  import { homeDir } from '../utils/paths.js';
18
+ import { deriveModelGroups, modelPickerLanding, recentCatalogModels, RECENTS_GROUP_PROVIDER, } from '../modelGroups.js';
19
+ import { readModelRecents, recordModelUse } from '../modelRecents.js';
10
20
  import { sessionCwdMatches } from '../dsh-adapter/channel.js';
11
21
  import { TuiDialogStore } from '../dsh-adapter/dialogs.js';
12
22
  import { TuiStatusStore } from '../dsh-adapter/status.js';
@@ -28,6 +38,7 @@ import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
28
38
  import { OverlayAbove } from '../components/OverlayAbove.js';
29
39
  import { PromptInput } from '../components/PromptInput.js';
30
40
  import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
41
+ import { AutoRecapRow } from '../components/AutoRecapRow.js';
31
42
  import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
32
43
  import { StatusLine } from './StatusLine.js';
33
44
  import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
@@ -36,11 +47,13 @@ import { ModelPicker } from '../components/ModelPicker.js';
36
47
  import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
37
48
  import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
38
49
  import { SessionBrowser } from './SessionBrowser.js';
50
+ import { SessionTree } from './SessionTree.js';
39
51
  import { Settings } from './Settings.js';
40
52
  import { WorkspacePicker } from '../components/WorkspacePicker.js';
41
53
  import { WorkspaceMenuPicker } from '../components/WorkspaceMenuPicker.js';
42
54
  import { WorkspaceFlowPicker } from '../components/WorkspaceFlowPicker.js';
43
55
  import { ActivityPicker } from '../components/ActivityPicker.js';
56
+ import { ColorPicker } from '../components/ColorPicker.js';
44
57
  import { EffortSlider } from '../components/EffortSlider.js';
45
58
  import { PresetPicker } from '../components/PresetPicker.js';
46
59
  import { PermissionsPicker, PERMISSION_PRESET_IDS } from '../components/PermissionsPicker.js';
@@ -53,9 +66,15 @@ import { ThinkingToggle } from '../components/ThinkingToggle.js';
53
66
  import { HistorySearchDialog } from '../components/HistorySearchDialog.js';
54
67
  import { RewindPicker } from '../components/RewindPicker.js';
55
68
  import { BtwPanel } from '../components/BtwPanel.js';
69
+ import { RecapPanel } from '../components/RecapPanel.js';
70
+ import { isValidSessionColor, SESSION_COLOR_NAMES } from '../cc/sessionColors.js';
56
71
  import { TipsPanel } from '../components/TipsPanel.js';
57
72
  import { SubagentDashboard } from '../components/SubagentDashboard.js';
58
73
  import { SubagentDetailScene } from '../components/SubagentDetailScene.js';
74
+ import { FileActionsPanel, FILE_ACTION_COUNT } from '../components/FileActionsPanel.js';
75
+ import { openExternal, openFile, revealInFileManager } from '../utils/openExternal.js';
76
+ import { fileUrlToPath, parseFileLinkUrl, resolveTargetPath } from '../utils/fileTarget.js';
77
+ import { statSync } from 'node:fs';
59
78
  import { setClipboard } from '../ink/termio/osc.js';
60
79
  import { TerminalWriteContext } from '../ink/useTerminalNotification.js';
61
80
  import instances from '../ink/instances.js';
@@ -148,7 +167,7 @@ let fallbackApprovalStore;
148
167
  */
149
168
  let fallbackDialogStore;
150
169
  let fallbackStatusStore;
151
- export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, onExit, onUpdate, fullscreen = false, trajectorySeen: trajectorySeenProp, }) {
170
+ export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, }) {
152
171
  const writeRaw = React.useContext(TerminalWriteContext);
153
172
  // Re-render whenever the channel mutates; rows/status are read fresh below.
154
173
  React.useSyncExternalStore(channel.subscribe, () => channel.version);
@@ -233,11 +252,56 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
233
252
  */
234
253
  const [overlay, dispatchOverlay] = React.useReducer(chatOverlayReducer, NO_OVERLAY);
235
254
  const [models, setModels] = React.useState([]);
255
+ /** Provider display identities for the /model group level; refreshed alongside `models`. */
256
+ const [providerInfos, setProviderInfos] = React.useState([]);
257
+ /** /model 最近使用分组:成功切换即记录(去重置顶,上限 10),重启保留。 */
258
+ const [modelRecents, setModelRecents] = React.useState(() => readModelRecents());
259
+ /** Two-level /model: the drilled-in provider route; undefined = group level.
260
+ * Reset on open; stale ids resolve back to the group level via `activeModelGroup`. */
261
+ const [modelGroup, setModelGroup] = React.useState(undefined);
262
+ /** True while the picker sits in the single-provider fast path (drilled in
263
+ * at open, the group level never shown): Esc closes directly and no back
264
+ * hint renders — a pinned recents pseudo-group must not fake a two-level
265
+ * walk the user never saw (issue #527 regression: repro-picker-windowing). */
266
+ const [modelPickerDirect, setModelPickerDirect] = React.useState(false);
267
+ /** Group rows over the current catalog, first-appearance (registry) order,
268
+ * with the pinned recents pseudo-group first when any entry is catalogued. */
269
+ const modelGroups = React.useMemo(() => deriveModelGroups(models, providerInfos, modelRecents), [models, providerInfos, modelRecents]);
270
+ /** The drilled-in group, but only while it still exists in the catalog. */
271
+ const activeModelGroup = modelGroup !== undefined && modelGroups.some(group => group.provider === modelGroup)
272
+ ? modelGroup
273
+ : undefined;
274
+ const groupModels = React.useMemo(() => {
275
+ if (activeModelGroup === undefined)
276
+ return [];
277
+ if (activeModelGroup === RECENTS_GROUP_PROVIDER)
278
+ return recentCatalogModels(modelRecents, models);
279
+ return models.filter(model => model.provider === activeModelGroup);
280
+ }, [models, modelRecents, activeModelGroup]);
281
+ /** Switch + record: every successful switch feeds the /model recents group
282
+ * (picker Enter/click, `/model provider/id`, the wizard's live switch,
283
+ * and /reload's applied model all ride this one path). */
284
+ const switchModelRecorded = (provider, id, name) => {
285
+ if (name !== undefined)
286
+ channel.notify(t('model-switching', { name }));
287
+ return channel.switchModel(provider, id).then((ok) => {
288
+ if (!ok)
289
+ return ok;
290
+ if (name !== undefined)
291
+ channel.notify(t('model-switched', { name }));
292
+ setModelRecents(recordModelUse({ provider, id }));
293
+ return ok;
294
+ });
295
+ };
236
296
  /** `/skills` 技能目录(issue #204):null = 注册表快照在途。 */
237
297
  const [skillsList, setSkillsList] = React.useState(null);
238
298
  /** `/resume` opens the session browser, a screen rather than a panel. It
239
299
  * owns its own selection, filters and keyboard — Chat only opens it. */
240
300
  const [browserOpen, setBrowserOpen] = React.useState(false);
301
+ /** `/tree` opens the session family tree (pi's Session Tree): every rewind
302
+ * fork stitched back onto the message it diverged from, hover previews,
303
+ * and per-node rewind/fork/adopt actions. Like the browser, a screen. */
304
+ const [treeOpen, setTreeOpen] = React.useState(false);
241
305
  /** `/settings` opens the plugin settings screen (issue #165) — like the
242
306
  * browser, a screen rather than a panel: it owns its own focus, staged
243
307
  * drafts and keyboard; Chat only opens it. */
@@ -271,6 +335,68 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
271
335
  btwAbortRef.current = null;
272
336
  setBtw(null);
273
337
  };
338
+ /** /recap overlay (pi-recap semantics): pure UI state like /btw — the
339
+ * summary never enters the transcript or session log; applying the
340
+ * proposed title goes through the normal /rename path. `auto` marks the
341
+ * recapOnOpen-triggered run (rendered as the dim AutoRecapRow until
342
+ * expanded); `expanded` lifts an auto recap into the full RecapPanel;
343
+ * `rowsAtTrigger` is the last user-row id when the auto run started —
344
+ * a newer user row (the user starts a new message) retires the recap. */
345
+ const [recap, setRecap] = React.useState(null);
346
+ const recapAbortRef = React.useRef(null);
347
+ const closeRecap = () => {
348
+ recapAbortRef.current?.abort();
349
+ recapAbortRef.current = null;
350
+ setRecap(null);
351
+ };
352
+ // Auto-recap (`dsh-tui.recapOnOpen`): every time the session switches
353
+ // (mount = open/resume, rewind/fork included), summarize its tail into
354
+ // the dim AutoRecapRow. Failures stay silent in auto mode — `/recap`
355
+ // surfaces them; the summary never enters the transcript or session log.
356
+ const autoRecapSessionId = channel.agentId;
357
+ React.useEffect(() => {
358
+ // A session switch retires the previous recap outright — an old
359
+ // session's 回顾 has no place above a new conversation.
360
+ setRecap(null);
361
+ if (!channel.autoRecapOnOpen)
362
+ return;
363
+ // No conversation yet (/new): nothing to recap, don't even fire.
364
+ if (!channel.rows.some(row => row.kind === 'user' || row.kind === 'assistant'))
365
+ return;
366
+ recapAbortRef.current?.abort();
367
+ const controller = new AbortController();
368
+ recapAbortRef.current = controller;
369
+ const lastUserId = channel.rows.filter(row => row.kind === 'user').at(-1)?.id ?? -1;
370
+ setRecap({ raw: '', summary: '', error: undefined, done: false, titleApplied: false, auto: true, expanded: false, rowsAtTrigger: lastUserId });
371
+ void channel.recapRecent({
372
+ signal: controller.signal,
373
+ onText: delta => setRecap(prev => (prev ? { ...prev, raw: prev.raw + delta } : prev)),
374
+ }).then(result => {
375
+ if (controller.signal.aborted)
376
+ return;
377
+ setRecap(prev => {
378
+ if (prev === null || !prev.auto)
379
+ return prev;
380
+ // Auto mode stays quiet on failure (no activity / llm missing / error).
381
+ if (result.summary === null)
382
+ return null;
383
+ return { ...prev, summary: result.summary, title: result.title, error: result.error, done: true };
384
+ });
385
+ });
386
+ return () => controller.abort();
387
+ }, [autoRecapSessionId]);
388
+ // The user starts a new message → the auto recap has served its purpose
389
+ // (catching them up) and bows out. A newer user row is the signal; the
390
+ // assistant's own streamed rows don't count.
391
+ const lastUserRowId = channel.rows.filter(row => row.kind === 'user').at(-1)?.id ?? -1;
392
+ React.useEffect(() => {
393
+ if (recap !== null &&
394
+ recap.auto &&
395
+ recap.rowsAtTrigger !== undefined &&
396
+ lastUserRowId > recap.rowsAtTrigger) {
397
+ closeRecap();
398
+ }
399
+ }, [lastUserRowId, recap]);
274
400
  /** Subagent dashboard (Ctrl+A): displays active/completed subagents. */
275
401
  const [subagentDashboardOpen, setSubagentDashboardOpen] = React.useState(false);
276
402
  /** Detail view for a specific subagent (opened from dashboard). */
@@ -281,6 +407,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
281
407
  */
282
408
  const [logoNonce, setLogoNonce] = React.useState(0);
283
409
  React.useEffect(() => () => btwAbortRef.current?.abort(), []);
410
+ React.useEffect(() => () => recapAbortRef.current?.abort(), []);
284
411
  /**
285
412
  * The trajectory scene (issue #80 evolution). Unlike every other overlay
286
413
  * here it is not a panel but a whole screen: while open, Chat renders the
@@ -325,6 +452,68 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
325
452
  ink?.invalidatePrevFrame();
326
453
  ink?.reanchorViewport();
327
454
  }, []);
455
+ /**
456
+ * Click-to-act targets: the Ink instance's hyperlink-open callback (wired
457
+ * in the effect below) resolves every clickable target the transcript
458
+ * renders — http(s) links open the browser, `dsh-file:`/`file://` paths
459
+ * open the file-action menu. `dsh-file:` payloads are RAW display paths
460
+ * (possibly relative), so they resolve against the CURRENT channel cwd
461
+ * at click time (read through a ref so this callback keeps a stable
462
+ * identity — it is threaded into memoized row components).
463
+ */
464
+ const cwdRef = React.useRef(channel.cwd);
465
+ React.useEffect(() => {
466
+ cwdRef.current = channel.cwd;
467
+ }, [channel.cwd]);
468
+ const openFileActions = React.useCallback((rawPath) => {
469
+ const resolved = resolveTargetPath(rawPath, cwdRef.current);
470
+ // Whether the target is a directory decides the first menu row's label
471
+ // ("open file" vs "open folder"). Missing paths count as files.
472
+ let isDir = false;
473
+ try {
474
+ isDir = statSync(resolved).isDirectory();
475
+ }
476
+ catch {
477
+ isDir = false;
478
+ }
479
+ dispatchOverlay({ type: 'open', overlay: { kind: 'file-actions', path: resolved, index: 0, isDir } });
480
+ }, []);
481
+ /** Run one file-action menu row: 0 = open file, 1 = reveal in file
482
+ * manager, 2 = copy absolute path. */
483
+ const runFileAction = React.useCallback((index, path) => {
484
+ if (index === 0)
485
+ openFile(path);
486
+ else if (index === 1)
487
+ revealInFileManager(path);
488
+ else
489
+ void setClipboard(path);
490
+ }, []);
491
+ const handleOpenTarget = React.useCallback((url) => {
492
+ const rawPath = parseFileLinkUrl(url);
493
+ if (rawPath !== undefined) {
494
+ openFileActions(rawPath);
495
+ return;
496
+ }
497
+ const filePath = fileUrlToPath(url);
498
+ if (filePath !== undefined) {
499
+ openFileActions(filePath);
500
+ return;
501
+ }
502
+ openExternal(url);
503
+ }, [openFileActions]);
504
+ // Wire the click-to-open callback into the Ink instance (the field is
505
+ // otherwise never set — clicking links was a no-op). Re-wired whenever
506
+ // the handler changes (cwd moves), cleared on unmount.
507
+ React.useEffect(() => {
508
+ const ink = instances.get(process.stdout) ?? instances.values().next().value;
509
+ if (ink)
510
+ ink.onHyperlinkClick = handleOpenTarget;
511
+ return () => {
512
+ const current = instances.get(process.stdout) ?? instances.values().next().value;
513
+ if (current)
514
+ current.onHyperlinkClick = undefined;
515
+ };
516
+ }, [handleOpenTarget]);
328
517
  /** `/` transcript search (less-style incsearch, ported from CC's REPL).
329
518
  * Only the bar's open/closed mode lives in `overlay`; the query and match
330
519
  * counters persist past the bar closing so n/N keep walking the matches. */
@@ -574,6 +763,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
574
763
  }
575
764
  channel.notify(ok ? t('lang-switched', { lang }) : t('lang-switch-failed', { lang }), { color: ok ? 'success' : 'error' });
576
765
  };
766
+ /** Localized label of one /reload surface, for the change report. */
767
+ const reloadKindLabel = (kind) => {
768
+ switch (kind) {
769
+ case 'theme': return t('reload-kind-theme');
770
+ case 'lang': return t('reload-kind-lang');
771
+ case 'preset': return t('reload-kind-preset');
772
+ case 'model': return t('reload-kind-model');
773
+ case 'activity': return t('reload-kind-activity');
774
+ }
775
+ };
577
776
  const runCommand = (name, rawInput = '') => {
578
777
  switch (name) {
579
778
  case 'activity': {
@@ -775,11 +974,50 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
775
974
  });
776
975
  return true;
777
976
  }
977
+ case 'color': {
978
+ // `/color`(CC accent,按会话持久化):无参打开调色板选择器,
979
+ // `/color <name>` 直接设置,`/color status` 显示当前,`/color
980
+ // reset` 清除回主题默认。颜色经 `session/color` 事件按会话保存
981
+ // ——resume/rewind 后仍是这个会话自己的颜色(见 channel.ts)。
982
+ setHelpOpen(false);
983
+ const parts = rawInput.trim().split(/\s+/).filter(Boolean);
984
+ if (parts.length === 0) {
985
+ dispatchOverlay({
986
+ type: 'open',
987
+ overlay: {
988
+ kind: 'color',
989
+ index: Math.max(0, SESSION_COLOR_NAMES.indexOf(channel.sessionColor)),
990
+ },
991
+ });
992
+ return true;
993
+ }
994
+ if (parts[0] === 'status') {
995
+ channel.pushLocal('/color', [
996
+ channel.sessionColor === ''
997
+ ? t('color-current-none')
998
+ : t('color-current', { name: channel.sessionColor }),
999
+ t('color-usage', { list: SESSION_COLOR_NAMES.join('/') }),
1000
+ ]);
1001
+ return true;
1002
+ }
1003
+ if (parts[0] === 'reset') {
1004
+ channel.setSessionColor('');
1005
+ channel.notify(t('color-reset'));
1006
+ return true;
1007
+ }
1008
+ const colorName = parts[0].toLowerCase();
1009
+ if (!isValidSessionColor(colorName)) {
1010
+ channel.notify(t('color-unknown', { name: colorName, list: SESSION_COLOR_NAMES.join(' · ') }), { color: 'error' });
1011
+ return true;
1012
+ }
1013
+ channel.setSessionColor(colorName);
1014
+ channel.notify(t('color-set', { name: colorName }), { color: 'success' });
1015
+ return true;
1016
+ }
778
1017
  case 'new': {
779
1018
  // One-shot `/new` (issue #25): the old session stays persisted and
780
1019
  // is recoverable via /resume, so discarding the live view is
781
- // non-destructive — no CC-style "press /new again" confirmation.
782
- setHelpOpen(false);
1020
+ // non-destructive — no CC-style "press /new again" confirmation. setHelpOpen(false)
783
1021
  void channel.newSession().then((ok) => {
784
1022
  if (!ok)
785
1023
  return;
@@ -854,11 +1092,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
854
1092
  channel.notify(t('model-unknown', { spec }), { color: 'error', timeoutMs: 8000 });
855
1093
  return;
856
1094
  }
857
- channel.notify(t('model-switching', { name: model.name }));
858
- void channel.switchModel(provider, id).then((ok) => {
859
- if (ok)
860
- channel.notify(t('model-switched', { name: model.name }));
861
- });
1095
+ void switchModelRecorded(provider, id, model.name);
862
1096
  });
863
1097
  return true;
864
1098
  }
@@ -866,18 +1100,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
866
1100
  // Opens over the cached catalog (empty cache shows the loading
867
1101
  // pane); the fresh list lands with the authoritative focus, and the
868
1102
  // kind-guarded set-index cannot re-focus a picker the user left.
869
- dispatchOverlay({
870
- type: 'open',
871
- overlay: {
872
- kind: 'model',
873
- index: Math.max(0, models.findIndex(model => model.provider === channel.provider && model.id === channel.model)),
874
- },
875
- });
1103
+ // Seed-on-open: the model in use IS a use — recording it here means
1104
+ // the recents group exists before the first post-update switch, and
1105
+ // switching A→B keeps A in the list (the file records what was
1106
+ // used, not only switches made after the file appeared).
1107
+ let recentsNow = modelRecents;
1108
+ if (channel.provider !== '' && channel.model !== ''
1109
+ && !recentsNow.some(ref => ref.provider === channel.provider && ref.id === channel.model)) {
1110
+ recentsNow = recordModelUse({ provider: channel.provider, id: channel.model });
1111
+ setModelRecents(recentsNow);
1112
+ }
1113
+ // Two-level landing: recents (when catalogued) focus their pinned
1114
+ // row; else multi-provider catalogs focus the current provider's
1115
+ // group row; a single-provider catalog without a meaningful recents
1116
+ // list drills straight into its model list (pre-grouping UX).
1117
+ {
1118
+ const landing = modelPickerLanding(models, channel.provider, channel.model, recentsNow);
1119
+ setModelGroup(landing.group);
1120
+ setModelPickerDirect(landing.group !== undefined);
1121
+ dispatchOverlay({ type: 'open', overlay: { kind: 'model', index: landing.index } });
1122
+ }
876
1123
  void channel.listModels().then((list) => {
877
1124
  setModels(list);
878
- const index = list.findIndex(model => model.provider === channel.provider && model.id === channel.model);
879
- dispatchOverlay({ type: 'set-index', kind: 'model', index: index >= 0 ? index : 0 });
1125
+ const landing = modelPickerLanding(list, channel.provider, channel.model, recentsNow);
1126
+ setModelGroup(landing.group);
1127
+ setModelPickerDirect(landing.group !== undefined);
1128
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
880
1129
  });
1130
+ void channel.listProviders().then(setProviderInfos).catch(() => setProviderInfos([]));
881
1131
  return true;
882
1132
  }
883
1133
  case 'skills': {
@@ -934,7 +1184,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
934
1184
  notify: (text, options) => channel.notify(text, options),
935
1185
  pushLocal: (title, lines) => channel.pushLocal(title, lines),
936
1186
  working: () => channel.working,
937
- switchModel: (provider, model) => channel.switchModel(provider, model),
1187
+ switchModel: (provider, model) => switchModelRecorded(provider, model),
938
1188
  }).catch(() => {
939
1189
  // The wizard notifies on every handled failure; this only swallows
940
1190
  // an unexpected reject so it never surfaces as an unhandled promise.
@@ -1027,6 +1277,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1027
1277
  setHelpOpen(false);
1028
1278
  openRewind();
1029
1279
  return true;
1280
+ case 'tree': {
1281
+ // The session family tree (pi's Session Tree): every fork branch
1282
+ // stitched back, hover previews, per-node rewind/fork/adopt.
1283
+ setHelpOpen(false);
1284
+ setTreeOpen(true);
1285
+ return true;
1286
+ }
1287
+ case 'fork': {
1288
+ // Tip fork (kimi-code semantics): a persisted copy of the whole
1289
+ // conversation the user enters via /resume — the live session and
1290
+ // its running turn stay untouched.
1291
+ setHelpOpen(false);
1292
+ void channel.forkSession();
1293
+ return true;
1294
+ }
1030
1295
  case 'exit':
1031
1296
  case 'quit':
1032
1297
  case 'q':
@@ -1130,12 +1395,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1130
1395
  setHelpOpen(false);
1131
1396
  void channel.describeCredential('DEEPSEEK_API_KEY')
1132
1397
  .catch(() => undefined)
1133
- .then(status => {
1398
+ .then(async (status) => {
1134
1399
  const keyStatus = status === undefined
1135
1400
  ? t('login-credentials-unavailable')
1136
1401
  : status.configured
1137
1402
  ? t('login-key-configured', { ref: 'DEEPSEEK_API_KEY' })
1138
1403
  : t('login-key-missing');
1404
+ // OAuth account states ride along only while a dsh-auth-style
1405
+ // plugin is mounted; absent it the lines below are exactly the
1406
+ // pre-plugin set.
1407
+ const oauth = await channel.oauthProviderStatuses().catch(() => undefined);
1139
1408
  channel.pushLocal('/login', [
1140
1409
  t('login-api-key', { status: keyStatus }),
1141
1410
  ...(status === undefined
@@ -1147,6 +1416,20 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1147
1416
  }),
1148
1417
  ]),
1149
1418
  t('login-base-url', { url: process.env.DEEPSEEK_BASE_URL ?? t('login-official-endpoint') }),
1419
+ ...(oauth === undefined
1420
+ ? []
1421
+ : [
1422
+ t('login-oauth-heading'),
1423
+ ...oauth.map(row => t('login-oauth-row', {
1424
+ provider: row.provider,
1425
+ state: row.signedIn
1426
+ ? t('login-oauth-in', { time: new Date(row.expiresAt ?? 0).toISOString() })
1427
+ : row.expired
1428
+ ? t('login-oauth-expired')
1429
+ : t('login-oauth-signed-out'),
1430
+ })),
1431
+ t('login-oauth-hint'),
1432
+ ]),
1150
1433
  ]);
1151
1434
  });
1152
1435
  return true;
@@ -1261,6 +1544,100 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1261
1544
  onUpdate();
1262
1545
  }
1263
1546
  return true;
1547
+ case 'reload': {
1548
+ // pi-style soft reload: re-read the persisted preference files
1549
+ // (~/.dsh-tui/{theme,lang,agent-preset,model,working-activity}.json)
1550
+ // and re-apply live, honoring the boot-time precedence (env >
1551
+ // cordis.yml > settings user layer > pref). The dsh-tui settings
1552
+ // namespace is NOT re-read here — its watch applies edits live and
1553
+ // the platform watcher hot-reloads settings.yaml itself. What no
1554
+ // reload can re-read (cordis.yml root config, frozen fullscreen,
1555
+ // newly built code) is listed in the footer and served by /restart.
1556
+ setHelpOpen(false);
1557
+ const tuiNamespace = channel.settingsHost()
1558
+ ?.listNamespaces()
1559
+ .find(entry => entry.ns === 'dsh-tui');
1560
+ const plan = planReload({
1561
+ envTheme: envThemeOverride(),
1562
+ envLang: isLang(process.env.DSH_TUI_LANG) ? process.env.DSH_TUI_LANG : undefined,
1563
+ themePref: readThemePref(),
1564
+ currentTheme: themeName,
1565
+ langPref: readLangPref(),
1566
+ currentLang: getLang(),
1567
+ langOverriddenBySettings: tuiNamespace !== undefined && hasPath(tuiNamespace.user, ['lang']),
1568
+ configuredLang: channel.configuredLang,
1569
+ configuredPreset: channel.configuredPreset,
1570
+ presetPref: readPresetPref(),
1571
+ currentPreset: channel.agentPreset,
1572
+ configuredModel: {
1573
+ provider: channel.configuredProvider,
1574
+ model: channel.configuredModel,
1575
+ },
1576
+ modelPref: readModelPref(),
1577
+ currentModel: { provider: channel.provider, model: channel.model },
1578
+ configuredActivity: channel.configuredActivityFrames,
1579
+ activityPref: readActivityFrames(),
1580
+ currentActivity: channel.activityFrames,
1581
+ });
1582
+ for (const item of plan.apply) {
1583
+ switch (item.kind) {
1584
+ case 'theme':
1585
+ setTheme(item.to);
1586
+ break;
1587
+ case 'lang':
1588
+ applyLang(item.to);
1589
+ break;
1590
+ case 'preset':
1591
+ void channel.switchPreset(item.to);
1592
+ break;
1593
+ case 'model':
1594
+ if (item.route !== undefined) {
1595
+ void switchModelRecorded(item.route.provider, item.route.model);
1596
+ }
1597
+ break;
1598
+ case 'activity':
1599
+ channel.setActivityFrames(item.to);
1600
+ break;
1601
+ }
1602
+ }
1603
+ const lines = [t('reload-header')];
1604
+ for (const item of plan.apply) {
1605
+ lines.push(t('reload-applied', {
1606
+ kind: reloadKindLabel(item.kind),
1607
+ from: item.from,
1608
+ to: item.to,
1609
+ }));
1610
+ }
1611
+ for (const kind of plan.unchanged) {
1612
+ lines.push(t('reload-unchanged', { kind: reloadKindLabel(kind) }));
1613
+ }
1614
+ for (const skip of plan.skipped) {
1615
+ const key = skip.reason === 'env-wins'
1616
+ ? 'reload-skipped-env'
1617
+ : skip.reason === 'config-wins' ? 'reload-skipped-config' : 'reload-skipped-invalid';
1618
+ lines.push(t(key, { kind: reloadKindLabel(skip.kind) }));
1619
+ }
1620
+ lines.push(t('reload-footer'));
1621
+ channel.pushLocal('/reload', lines);
1622
+ return true;
1623
+ }
1624
+ case 'restart':
1625
+ // pi-style reload tail: /reload cannot re-read boot-time-only state
1626
+ // (cordis.yml root config, frozen fullscreen layout, newly built
1627
+ // code), so /restart respawns the process with the original argv and
1628
+ // resumes this session — the /update handoff minus the pnpm step.
1629
+ setHelpOpen(false);
1630
+ if (onRestart === undefined) {
1631
+ channel.notify(t('restart-unavailable'), { color: 'warning' });
1632
+ }
1633
+ else if (channel.working) {
1634
+ channel.notify(t('update-working'), { color: 'warning' });
1635
+ }
1636
+ else {
1637
+ channel.notify(t('restart-starting'));
1638
+ onRestart();
1639
+ }
1640
+ return true;
1264
1641
  case 'vim':
1265
1642
  channel.notify(t('vim-not-implemented'));
1266
1643
  return true;
@@ -1271,6 +1648,33 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1271
1648
  t('terminal-paste-hint', { mod: modLabel }),
1272
1649
  ]);
1273
1650
  return true;
1651
+ case 'recap': {
1652
+ // `/recap`(pi-recap 语义):对会话最近活动做一次无工具单轮
1653
+ // 调用,生成一行摘要 + 建议标题。摘要是纯 UI 状态(不进 transcript
1654
+ // 也不进 session log);建议标题经「应用」按钮走 /rename 路径。
1655
+ setHelpOpen(false);
1656
+ recapAbortRef.current?.abort();
1657
+ const controller = new AbortController();
1658
+ recapAbortRef.current = controller;
1659
+ setRecap({ raw: '', summary: '', error: undefined, done: false, titleApplied: false, auto: false, expanded: true });
1660
+ void channel.recapRecent({
1661
+ signal: controller.signal,
1662
+ onText: delta => setRecap(prev => (prev ? { ...prev, raw: prev.raw + delta } : prev)),
1663
+ }).then(result => {
1664
+ if (controller.signal.aborted)
1665
+ return;
1666
+ setRecap(prev => (prev
1667
+ ? {
1668
+ ...prev,
1669
+ summary: result.summary ?? prev.raw,
1670
+ title: result.title,
1671
+ error: result.error,
1672
+ done: true,
1673
+ }
1674
+ : prev));
1675
+ });
1676
+ return true;
1677
+ }
1274
1678
  case 'btw': {
1275
1679
  // CC /btw:单轮无工具侧问,overlay 态纯 UI,不打断主回合、不写
1276
1680
  // 会话历史。空参数只提示用法。
@@ -1621,6 +2025,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1621
2025
  // search box, which Chat would otherwise route into the prompt.
1622
2026
  if (browserOpen)
1623
2027
  return;
2028
+ // Same for the session tree: plain letters drive its search, clicks and
2029
+ // Enter drive its action menu.
2030
+ if (treeOpen)
2031
+ return;
1624
2032
  // Same for the settings screen: plain letters (s save / d discard) and
1625
2033
  // the field draft editor belong to it alone.
1626
2034
  if (settingsOpen)
@@ -1881,29 +2289,55 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1881
2289
  return;
1882
2290
  }
1883
2291
  if (overlay.kind === 'model') {
2292
+ // Two-level picker: group rows at the top (Enter drills in), one
2293
+ // provider's models below (Enter switches, the same live-fork path as
2294
+ // the flat picker always had). Esc/⌫ climbs one level and only closes
2295
+ // at the top; a single-group catalog never shows the group level, so
2296
+ // Esc there closes directly.
2297
+ const rowCount = activeModelGroup === undefined ? modelGroups.length : groupModels.length;
1884
2298
  if (key.upArrow || key.downArrow) {
1885
- dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: models.length });
2299
+ dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rowCount });
1886
2300
  }
1887
2301
  else if (plainReturn) {
1888
- const model = models[overlay.index];
2302
+ if (activeModelGroup === undefined) {
2303
+ const group = modelGroups[overlay.index];
2304
+ if (!group) {
2305
+ dispatchOverlay({ type: 'close' });
2306
+ return;
2307
+ }
2308
+ setModelGroup(group.provider);
2309
+ // The recents group opens on its most-recent entry; a provider
2310
+ // group on its current model when it owns one, else its first row.
2311
+ if (group.provider === RECENTS_GROUP_PROVIDER) {
2312
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
2313
+ return;
2314
+ }
2315
+ const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
2316
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
2317
+ return;
2318
+ }
2319
+ const model = groupModels[overlay.index];
1889
2320
  // oxlint-disable-next-line typescript/no-unnecessary-condition -- runtime guard: out-of-range index on an empty list
1890
2321
  if (model) {
1891
2322
  // Enter switches the live model right away: the conversation is
1892
2323
  // forked at its end and continued with an agent routed to the new
1893
- // model (history replays unchanged).
2324
+ // model (history replays unchanged) — and feeds the recents group.
1894
2325
  dispatchOverlay({ type: 'close' });
1895
- channel.notify(t('model-switching', { name: model.name }));
1896
- void channel.switchModel(model.provider, model.id).then((ok) => {
1897
- if (ok)
1898
- channel.notify(t('model-switched', { name: model.name }));
1899
- });
2326
+ void switchModelRecorded(model.provider, model.id, model.name);
1900
2327
  }
1901
2328
  else {
1902
2329
  dispatchOverlay({ type: 'close' });
1903
2330
  }
1904
2331
  }
1905
- else if (key.escape) {
1906
- dispatchOverlay({ type: 'close' });
2332
+ else if (key.escape || key.backspace) {
2333
+ if (activeModelGroup !== undefined && modelGroups.length > 1 && !modelPickerDirect) {
2334
+ setModelGroup(undefined);
2335
+ const groupIndex = Math.max(0, modelGroups.findIndex(group => group.provider === activeModelGroup));
2336
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: groupIndex });
2337
+ }
2338
+ else {
2339
+ dispatchOverlay({ type: 'close' });
2340
+ }
1907
2341
  }
1908
2342
  return;
1909
2343
  }
@@ -1942,6 +2376,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1942
2376
  }
1943
2377
  return;
1944
2378
  }
2379
+ if (overlay.kind === 'color') {
2380
+ if (key.upArrow || key.downArrow) {
2381
+ dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: SESSION_COLOR_NAMES.length });
2382
+ }
2383
+ else if (plainReturn) {
2384
+ const name = SESSION_COLOR_NAMES[overlay.index];
2385
+ dispatchOverlay({ type: 'close' });
2386
+ if (name) {
2387
+ channel.setSessionColor(name);
2388
+ channel.notify(t('color-set', { name }), { color: 'success' });
2389
+ }
2390
+ }
2391
+ else if (key.escape) {
2392
+ dispatchOverlay({ type: 'close' });
2393
+ }
2394
+ return;
2395
+ }
1945
2396
  if (overlay.kind === 'effort') {
1946
2397
  if (key.leftArrow || key.rightArrow) {
1947
2398
  const delta = key.leftArrow ? -1 : 1;
@@ -2065,8 +2516,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2065
2516
  dispatchOverlay({ type: 'move', delta: -1, count: historyMatches.length });
2066
2517
  }
2067
2518
  }
2068
- else if (key.downArrow || (isMod(key) && input === 'r')) {
2069
- // CC's historySearch:next — ↓ and repeat ctrl+r walk to the next match.
2519
+ else if (key.downArrow || actionMatches('history', input, key)) {
2520
+ // CC's historySearch:next — ↓ and the history key (default Ctrl+R)
2521
+ // walk to the next match.
2070
2522
  if (historyMatches.length > 0) {
2071
2523
  dispatchOverlay({ type: 'move', delta: 1, count: historyMatches.length });
2072
2524
  }
@@ -2172,24 +2624,41 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2172
2624
  }
2173
2625
  return;
2174
2626
  }
2175
- if (isMod(key) && input === 't') {
2176
- // Ctrl+T opens the trajectory scene at any point in the session.
2627
+ if (overlay.kind === 'file-actions') {
2628
+ // Click-to-act file menu: ↑/↓ move, Enter runs the focused action,
2629
+ // Esc closes.
2630
+ if (key.upArrow || key.downArrow) {
2631
+ dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: FILE_ACTION_COUNT });
2632
+ }
2633
+ else if (plainReturn) {
2634
+ const path = overlay.path;
2635
+ dispatchOverlay({ type: 'close' });
2636
+ runFileAction(overlay.index, path);
2637
+ }
2638
+ else if (key.escape) {
2639
+ dispatchOverlay({ type: 'close' });
2640
+ }
2641
+ return;
2642
+ }
2643
+ if (actionMatches('trajectory', input, key)) {
2644
+ // The trajectory scene key (default Ctrl+T) opens it at any point in
2645
+ // the session.
2177
2646
  openScene();
2178
2647
  return;
2179
2648
  }
2180
- if (isMod(key) && input === 'a') {
2181
- // Ctrl+A opens the subagent dashboard.
2649
+ if (actionMatches('dashboard', input, key)) {
2650
+ // The subagent dashboard key (default Ctrl+A) opens the dashboard.
2182
2651
  setSubagentDashboardOpen(true);
2183
2652
  return;
2184
2653
  }
2185
- if (isMod(key) && input === 'p' && loadedContextVisible) {
2186
- // Ctrl+P toggles the startup loaded-context panel while it is on
2187
- // screen (transcript still empty); once rows take over and the
2188
- // panel disappears the key has nothing left to do.
2654
+ if (actionMatches('contextPanel', input, key) && loadedContextVisible) {
2655
+ // The loaded-context panel key (default Ctrl+P) toggles the startup
2656
+ // panel while it is on screen (transcript still empty); once rows take
2657
+ // over and the panel disappears the key has nothing left to do.
2189
2658
  toggleLoadedContext();
2190
2659
  return;
2191
2660
  }
2192
- if (isMod(key) && input === 'r' && !helpOpen) {
2661
+ if (actionMatches('history', input, key) && !helpOpen) {
2193
2662
  setHistoryEntries(loadHistory());
2194
2663
  dispatchOverlay({
2195
2664
  type: 'open',
@@ -2231,11 +2700,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2231
2700
  }
2232
2701
  event.stopImmediatePropagation();
2233
2702
  }
2234
- else if (isMod(key) && input === 'o' && !helpOpen) {
2235
- // Leaving transcript mode (Ctrl+O) — search was already handled above.
2236
- // Help is modal: toggling this state behind the overlay is invisible,
2237
- // then the next `/` unexpectedly opens transcript search instead of
2238
- // slash-command completion after Help closes.
2703
+ else if (actionMatches('transcript', input, key) && !helpOpen) {
2704
+ // Leaving transcript mode (default Ctrl+O) — search was already
2705
+ // handled above. Help is modal: toggling this state behind the
2706
+ // overlay is invisible, then the next `/` unexpectedly opens
2707
+ // transcript search instead of slash-command completion after Help
2708
+ // closes.
2239
2709
  setExpanded(previous => !previous);
2240
2710
  // The toggle rewrites every thinking row's layout at once. The
2241
2711
  // ordinary scroll-based diff pushes rows into terminal scrollback on
@@ -2280,17 +2750,18 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2280
2750
  requestExit();
2281
2751
  }
2282
2752
  }
2283
- else if (isMod(key) && input === 'l') {
2284
- // CC's app:redraw — clear the physical terminal and repaint.
2753
+ else if (actionMatches('redraw', input, key)) {
2754
+ // CC's app:redraw (default Ctrl+L) — clear the physical terminal and
2755
+ // repaint.
2285
2756
  instances.get(process.stdout)?.forceRedraw();
2286
2757
  }
2287
- else if (isMod(key) && input === 'e') {
2758
+ else if (actionMatches('showAll', input, key)) {
2288
2759
  setShowAllMessages(previous => !previous);
2289
2760
  }
2290
- else if (isMod(key) && input === 'q') {
2291
- // Fold/unfold the GoalTodoPanel todo section — works mid-turn too:
2292
- // the collapsed line keeps the done/total count and the live task
2293
- // preview, so long todo lists stop crowding the prompt.
2761
+ else if (actionMatches('todoFold', input, key)) {
2762
+ // Fold/unfold the GoalTodoPanel todo section (default Ctrl+Q) — works
2763
+ // mid-turn too: the collapsed line keeps the done/total count and the
2764
+ // live task preview, so long todo lists stop crowding the prompt.
2294
2765
  setTodoCollapsed(previous => !previous);
2295
2766
  }
2296
2767
  else if (plainReturn && !isSticky) {
@@ -2356,6 +2827,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2356
2827
  // hosts are already in it and must not nest a second one.
2357
2828
  return fullscreen ? browser : _jsx(AlternateScreen, { children: browser });
2358
2829
  }
2830
+ // The session tree follows the browser's rule exactly: it REPLACES the
2831
+ // conversation (an early return after every hook above has run), so there
2832
+ // is no transcript underneath to be repainted or bled through. The dropped
2833
+ // turn's prompt returns through the same fill path a rewind picker uses.
2834
+ if (treeOpen) {
2835
+ const tree = (_jsx(SessionTree, { channel: channel, currentSessionId: channel.agentId, onClose: () => setTreeOpen(false), onRestoreText: (text) => {
2836
+ setHistoryFill(text);
2837
+ } }));
2838
+ return fullscreen ? tree : _jsx(AlternateScreen, { children: tree });
2839
+ }
2359
2840
  // The settings screen follows the browser's rule exactly: it REPLACES the
2360
2841
  // conversation (an early return after every hook above has run), so there
2361
2842
  // is no transcript underneath to be repainted or bled through.
@@ -2444,7 +2925,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2444
2925
  // transcript mount batches (and the first wheel events) for the
2445
2926
  // frame budget right when the user wants to read history. Fresh
2446
2927
  // sessions keep the full intro; restored ones settle instantly.
2447
- 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) })] }), (() => {
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 })] }), (() => {
2448
2929
  // Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
2449
2930
  // rail (default), the proportional scrollbar, or nothing. The
2450
2931
  // slot keeps its 2 columns in both rendered modes (Qwen's
@@ -2467,11 +2948,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2467
2948
  // the animated chars/4 estimate, matching the classic
2468
2949
  // spinner's counter (the suffix used raw chars before,
2469
2950
  // inflating the reading next to a real upload number).
2470
- 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) }), 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' }) }) })) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
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: () => {
2952
+ // An expanded auto recap collapses back to its dim row;
2953
+ // a manual /recap closes outright.
2954
+ if (recap.auto) {
2955
+ setRecap(prev => (prev ? { ...prev, expanded: false } : prev));
2956
+ }
2957
+ else {
2958
+ closeRecap();
2959
+ }
2960
+ }, onCopy: () => {
2961
+ void setClipboard(recap.summary ?? '').then(raw => { if (raw)
2962
+ writeRaw?.(raw); });
2963
+ channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
2964
+ }, onApplyTitle: () => {
2965
+ if (recap.title === undefined || recap.titleApplied)
2966
+ return;
2967
+ channel.renameSession(recap.title);
2968
+ setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
2969
+ channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
2970
+ } }) })) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
2471
2971
  void setClipboard(btw.answer ?? '').then(raw => { if (raw)
2472
2972
  writeRaw?.(raw); });
2473
2973
  channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
2474
- } }) })) : questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent() }, 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
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
2475
2977
  ? undefined
2476
2978
  : {
2477
2979
  band: wakeBand,
@@ -2500,17 +3002,27 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2500
3002
  return;
2501
3003
  dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
2502
3004
  runWorkspaceFlowAction(signal => choice.choose(signal));
2503
- } }) })), overlay.kind === 'model' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : (_jsx(ModelPicker, { models: models, focusIndex: overlay.index, currentModel: `${channel.provider}/${channel.model}`, onPick: (index) => {
3005
+ } }) })), overlay.kind === 'model' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : activeModelGroup === undefined ? (_jsx(ModelPicker, { groups: modelGroups, focusIndex: overlay.index, currentProvider: channel.provider, onPick: (index) => {
3006
+ // 点击分组行 = 进入该组(与 Enter 同一条路径)
3007
+ const group = modelGroups[index];
3008
+ if (!group)
3009
+ return;
3010
+ setModelGroup(group.provider);
3011
+ if (group.provider === RECENTS_GROUP_PROVIDER) {
3012
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
3013
+ return;
3014
+ }
3015
+ const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
3016
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
3017
+ } })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
3018
+ ? t('picker-group-recent')
3019
+ : modelGroups.find(group => group.provider === activeModelGroup)?.label, showBack: modelGroups.length > 1 && !modelPickerDirect, showProviderPrefix: activeModelGroup === RECENTS_GROUP_PROVIDER, focusIndex: overlay.index, currentModel: `${channel.provider}/${channel.model}`, onPick: (index) => {
2504
3020
  // 点击行 = 应用该行模型(与 Enter 同一条路径)
2505
- const model = models[index];
3021
+ const model = groupModels[index];
2506
3022
  if (!model)
2507
3023
  return;
2508
3024
  dispatchOverlay({ type: 'close' });
2509
- channel.notify(t('model-switching', { name: model.name }));
2510
- void channel.switchModel(model.provider, model.id).then((ok) => {
2511
- if (ok)
2512
- channel.notify(t('model-switched', { name: model.name }));
2513
- });
3025
+ void switchModelRecorded(model.provider, model.id, model.name);
2514
3026
  } })) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
2515
3027
  const skill = skillsList[index];
2516
3028
  if (!skill)
@@ -2523,6 +3035,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2523
3035
  const name = PRESET_NAMES[index];
2524
3036
  if (name)
2525
3037
  channel.setActivityFrames(name);
3038
+ } }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
3039
+ dispatchOverlay({ type: 'close' });
3040
+ const name = SESSION_COLOR_NAMES[index];
3041
+ if (name) {
3042
+ channel.setSessionColor(name);
3043
+ channel.notify(t('color-set', { name }), { color: 'success' });
3044
+ }
2526
3045
  } }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
2527
3046
  // 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
2528
3047
  onPick: (index) => {
@@ -2590,6 +3109,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2590
3109
  const mode = index === 0 ? null : (overlay.modes?.[index - 1]?.id ?? null);
2591
3110
  dispatchOverlay({ type: 'close' });
2592
3111
  void performRewind(row, mode);
3112
+ } }) })), overlay.kind === 'file-actions' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(FileActionsPanel, { path: overlay.path, isDir: overlay.isDir, focusIndex: overlay.index, onPick: (index) => {
3113
+ // 点击行直接执行该动作(与 Enter 同路径)
3114
+ const path = overlay.path;
3115
+ dispatchOverlay({ type: 'close' });
3116
+ runFileAction(index, path);
2593
3117
  } }) })), overlay.kind === 'search' && _jsx(TranscriptSearchBar, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }));
2594
3118
  }
2595
3119
  /**