@deepseek-harness-tui/dsh-tui 0.11.0 → 0.11.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 (484) hide show
  1. package/README.md +55 -20
  2. package/assets/whale-girl/whale-girl-dark.png +0 -0
  3. package/assets/whale-girl/whale-girl-happy.png +0 -0
  4. package/assets/whale-girl/whale-girl.png +0 -0
  5. package/bin/dsh-tui.js +63 -3
  6. package/cordis.patch.yml +43 -20
  7. package/cordis.yml +6 -4
  8. package/lib/settings.json +1272 -0
  9. package/lib/types/activityPrefs.d.ts +24 -0
  10. package/lib/types/activityPrefs.d.ts.map +1 -1
  11. package/lib/types/activityPrefs.js +50 -1
  12. package/lib/types/adapter/channel/state.d.ts.map +1 -1
  13. package/lib/types/adapter/channel/state.js +3 -1
  14. package/lib/types/adapter/channel/ui-policy.d.ts +9 -2
  15. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  16. package/lib/types/adapter/channel/ui-policy.js +14 -1
  17. package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
  18. package/lib/types/adapter/kernel/replay.js +2 -0
  19. package/lib/types/adapter/ports/channel-display.d.ts +12 -0
  20. package/lib/types/adapter/ports/channel-display.d.ts.map +1 -1
  21. package/lib/types/adapter/ports/channel-session.d.ts +38 -0
  22. package/lib/types/adapter/ports/channel-session.d.ts.map +1 -1
  23. package/lib/types/adapter/ports/channel-settings.d.ts +12 -0
  24. package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
  25. package/lib/types/adapter/ports/channel-ui.d.ts +53 -7
  26. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  27. package/lib/types/adapter/ports/channel-view.d.ts +43 -12
  28. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  29. package/lib/types/adapter/ports/channel.d.ts +4 -1
  30. package/lib/types/adapter/ports/channel.d.ts.map +1 -1
  31. package/lib/types/commands.d.ts.map +1 -1
  32. package/lib/types/commands.js +2 -0
  33. package/lib/types/components/ActivityLine.d.ts +7 -2
  34. package/lib/types/components/ActivityLine.d.ts.map +1 -1
  35. package/lib/types/components/ActivityLine.js +0 -8
  36. package/lib/types/components/BalanceReportRow.d.ts +10 -2
  37. package/lib/types/components/BalanceReportRow.d.ts.map +1 -1
  38. package/lib/types/components/BalanceReportRow.js +18 -4
  39. package/lib/types/components/CompactionStatusRow.d.ts +24 -0
  40. package/lib/types/components/CompactionStatusRow.d.ts.map +1 -0
  41. package/lib/types/components/CompactionStatusRow.js +72 -0
  42. package/lib/types/components/GoalTodoPanel.js +2 -2
  43. package/lib/types/components/HelpMenu.d.ts +2 -2
  44. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  45. package/lib/types/components/HelpMenu.js +5 -4
  46. package/lib/types/components/InlineMathParagraph.d.ts +22 -0
  47. package/lib/types/components/InlineMathParagraph.d.ts.map +1 -0
  48. package/lib/types/components/InlineMathParagraph.js +178 -0
  49. package/lib/types/components/LogoV2.d.ts +41 -3
  50. package/lib/types/components/LogoV2.d.ts.map +1 -1
  51. package/lib/types/components/LogoV2.js +148 -24
  52. package/lib/types/components/Markdown.d.ts +10 -3
  53. package/lib/types/components/Markdown.d.ts.map +1 -1
  54. package/lib/types/components/Markdown.js +47 -14
  55. package/lib/types/components/MathBlock.d.ts +13 -0
  56. package/lib/types/components/MathBlock.d.ts.map +1 -0
  57. package/lib/types/components/MathBlock.js +136 -0
  58. package/lib/types/components/MessageList.d.ts +10 -1
  59. package/lib/types/components/MessageList.d.ts.map +1 -1
  60. package/lib/types/components/MessageList.js +15 -5
  61. package/lib/types/components/MigratePicker.d.ts +30 -0
  62. package/lib/types/components/MigratePicker.d.ts.map +1 -0
  63. package/lib/types/components/MigratePicker.js +38 -0
  64. package/lib/types/components/PromptInput.d.ts +17 -0
  65. package/lib/types/components/PromptInput.d.ts.map +1 -1
  66. package/lib/types/components/PromptInput.js +96 -9
  67. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  68. package/lib/types/components/SplitDiffView.js +3 -1
  69. package/lib/types/components/StarPrompt.d.ts +53 -0
  70. package/lib/types/components/StarPrompt.d.ts.map +1 -0
  71. package/lib/types/components/StarPrompt.js +281 -0
  72. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  73. package/lib/types/components/StreamingMarkdown.js +20 -3
  74. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  75. package/lib/types/components/SubagentCard.js +12 -6
  76. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  77. package/lib/types/components/SubagentDetailScene.js +8 -3
  78. package/lib/types/components/TimelineRail.d.ts.map +1 -1
  79. package/lib/types/components/TimelineRail.js +9 -1
  80. package/lib/types/components/Whale.d.ts +17 -8
  81. package/lib/types/components/Whale.d.ts.map +1 -1
  82. package/lib/types/components/Whale.js +25 -15
  83. package/lib/types/components/WhaleGirl.d.ts +29 -0
  84. package/lib/types/components/WhaleGirl.d.ts.map +1 -0
  85. package/lib/types/components/WhaleGirl.js +32 -0
  86. package/lib/types/components/WorkingSpinner.d.ts +4 -1
  87. package/lib/types/components/WorkingSpinner.d.ts.map +1 -1
  88. package/lib/types/components/WorkingSpinner.js +2 -2
  89. package/lib/types/components/bigfont.d.ts +47 -18
  90. package/lib/types/components/bigfont.d.ts.map +1 -1
  91. package/lib/types/components/bigfont.js +55 -40
  92. package/lib/types/components/design-system/HintLine.d.ts +1 -2
  93. package/lib/types/components/design-system/HintLine.d.ts.map +1 -1
  94. package/lib/types/components/design-system/HintLine.js +1 -2
  95. package/lib/types/components/design-system/ThemeProvider.d.ts +6 -0
  96. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
  97. package/lib/types/components/design-system/ThemeProvider.js +40 -1
  98. package/lib/types/components/maidPortrait.d.ts +44 -0
  99. package/lib/types/components/maidPortrait.d.ts.map +1 -0
  100. package/lib/types/components/maidPortrait.js +178 -0
  101. package/lib/types/components/mathPreview.d.ts +25 -0
  102. package/lib/types/components/mathPreview.d.ts.map +1 -0
  103. package/lib/types/components/mathPreview.js +48 -0
  104. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
  105. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  106. package/lib/types/components/messages/AssistantThinkingMessage.js +4 -2
  107. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -2
  108. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  109. package/lib/types/components/messages/AssistantToolUseMessage.js +31 -26
  110. package/lib/types/components/messages/TranscriptImages.d.ts.map +1 -1
  111. package/lib/types/components/messages/TranscriptImages.js +11 -1
  112. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +14 -2
  113. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  114. package/lib/types/components/questions/AskUserQuestionPanel.js +80 -38
  115. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  116. package/lib/types/components/questions/PlanReviewPanel.js +3 -3
  117. package/lib/types/components/sessions/ForeignSessionRow.d.ts +34 -0
  118. package/lib/types/components/sessions/ForeignSessionRow.d.ts.map +1 -0
  119. package/lib/types/components/sessions/ForeignSessionRow.js +43 -0
  120. package/lib/types/components/sessions/SourceTabs.d.ts +52 -0
  121. package/lib/types/components/sessions/SourceTabs.d.ts.map +1 -0
  122. package/lib/types/components/sessions/SourceTabs.js +70 -0
  123. package/lib/types/components/splashEggs.d.ts +56 -0
  124. package/lib/types/components/splashEggs.d.ts.map +1 -0
  125. package/lib/types/components/splashEggs.js +67 -0
  126. package/lib/types/components/splashFonts.d.ts +119 -0
  127. package/lib/types/components/splashFonts.d.ts.map +1 -0
  128. package/lib/types/components/splashFonts.js +267 -0
  129. package/lib/types/components/splashLayout.d.ts +41 -0
  130. package/lib/types/components/splashLayout.d.ts.map +1 -0
  131. package/lib/types/components/splashLayout.js +40 -0
  132. package/lib/types/components/whaleGirlSprite.d.ts +17 -0
  133. package/lib/types/components/whaleGirlSprite.d.ts.map +1 -0
  134. package/lib/types/components/whaleGirlSprite.js +47 -0
  135. package/lib/types/deepseekPricing.d.ts +74 -0
  136. package/lib/types/deepseekPricing.d.ts.map +1 -1
  137. package/lib/types/deepseekPricing.js +110 -11
  138. package/lib/types/dsh-adapter/activity-store.d.ts +221 -0
  139. package/lib/types/dsh-adapter/activity-store.d.ts.map +1 -0
  140. package/lib/types/dsh-adapter/activity-store.js +359 -0
  141. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
  142. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  143. package/lib/types/dsh-adapter/channel/action-readiness.js +6 -1
  144. package/lib/types/dsh-adapter/channel/background-action.d.ts +2 -1
  145. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  146. package/lib/types/dsh-adapter/channel/background-action.js +2 -0
  147. package/lib/types/dsh-adapter/channel/binding-events.d.ts +15 -14
  148. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  149. package/lib/types/dsh-adapter/channel/binding-events.js +70 -21
  150. package/lib/types/dsh-adapter/channel/compaction.d.ts +2 -2
  151. package/lib/types/dsh-adapter/channel/compaction.d.ts.map +1 -1
  152. package/lib/types/dsh-adapter/channel/compaction.js +35 -4
  153. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  154. package/lib/types/dsh-adapter/channel/local-actions.js +6 -1
  155. package/lib/types/dsh-adapter/channel/model-switch.d.ts +1 -2
  156. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  157. package/lib/types/dsh-adapter/channel/model-switch.js +6 -2
  158. package/lib/types/dsh-adapter/channel/paths.d.ts.map +1 -1
  159. package/lib/types/dsh-adapter/channel/paths.js +7 -1
  160. package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
  161. package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
  162. package/lib/types/dsh-adapter/channel/preferences.js +16 -0
  163. package/lib/types/dsh-adapter/channel/projection.d.ts +1 -2
  164. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  165. package/lib/types/dsh-adapter/channel/projection.js +127 -17
  166. package/lib/types/dsh-adapter/channel/question-record.d.ts +76 -0
  167. package/lib/types/dsh-adapter/channel/question-record.d.ts.map +1 -0
  168. package/lib/types/dsh-adapter/channel/question-record.js +107 -0
  169. package/lib/types/dsh-adapter/channel/session-adoption.d.ts +1 -1
  170. package/lib/types/dsh-adapter/channel/session-adoption.d.ts.map +1 -1
  171. package/lib/types/dsh-adapter/channel/session-adoption.js +1 -0
  172. package/lib/types/dsh-adapter/channel/session-lineage.d.ts +42 -0
  173. package/lib/types/dsh-adapter/channel/session-lineage.d.ts.map +1 -0
  174. package/lib/types/dsh-adapter/channel/session-lineage.js +51 -0
  175. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -1
  176. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
  177. package/lib/types/dsh-adapter/channel/session-live-adoption.js +10 -3
  178. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +2 -1
  179. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  180. package/lib/types/dsh-adapter/channel/session-metadata.js +43 -9
  181. package/lib/types/dsh-adapter/channel/session-reset.d.ts +1 -1
  182. package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -1
  183. package/lib/types/dsh-adapter/channel/session-reset.js +7 -1
  184. package/lib/types/dsh-adapter/channel/session-resume.d.ts +2 -1
  185. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  186. package/lib/types/dsh-adapter/channel/session-resume.js +5 -2
  187. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  188. package/lib/types/dsh-adapter/channel/settings-host.js +8 -0
  189. package/lib/types/dsh-adapter/channel/state.d.ts +19 -1
  190. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  191. package/lib/types/dsh-adapter/channel/state.js +8 -3
  192. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts +32 -17
  193. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  194. package/lib/types/dsh-adapter/channel/subagent-projection.js +309 -8
  195. package/lib/types/dsh-adapter/channel/types.d.ts +1 -1
  196. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  197. package/lib/types/dsh-adapter/channel.d.ts +1 -1
  198. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  199. package/lib/types/dsh-adapter/channel.js +34 -18
  200. package/lib/types/dsh-adapter/compat/index.d.ts +1 -1
  201. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  202. package/lib/types/dsh-adapter/compat/index.js +1 -1
  203. package/lib/types/dsh-adapter/compat/messages.d.ts +6 -0
  204. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -1
  205. package/lib/types/dsh-adapter/compat/messages.js +9 -0
  206. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +18 -0
  207. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  208. package/lib/types/dsh-adapter/compat/sessionLog.js +18 -3
  209. package/lib/types/dsh-adapter/compat/settings.d.ts +2 -0
  210. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -1
  211. package/lib/types/dsh-adapter/compat/settings.js +14 -0
  212. package/lib/types/dsh-adapter/contract.d.ts +5 -4
  213. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  214. package/lib/types/dsh-adapter/contract.js +9 -5
  215. package/lib/types/dsh-adapter/index.d.ts +36 -1
  216. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  217. package/lib/types/dsh-adapter/index.js +22 -6
  218. package/lib/types/dsh-adapter/migrate/adapters/claude-code.d.ts +3 -0
  219. package/lib/types/dsh-adapter/migrate/adapters/claude-code.d.ts.map +1 -0
  220. package/lib/types/dsh-adapter/migrate/adapters/claude-code.js +131 -0
  221. package/lib/types/dsh-adapter/migrate/adapters/claude-code.parse.d.ts +17 -0
  222. package/lib/types/dsh-adapter/migrate/adapters/claude-code.parse.d.ts.map +1 -0
  223. package/lib/types/dsh-adapter/migrate/adapters/claude-code.parse.js +329 -0
  224. package/lib/types/dsh-adapter/migrate/adapters/codex.d.ts +3 -0
  225. package/lib/types/dsh-adapter/migrate/adapters/codex.d.ts.map +1 -0
  226. package/lib/types/dsh-adapter/migrate/adapters/codex.js +102 -0
  227. package/lib/types/dsh-adapter/migrate/adapters/codex.parse.d.ts +16 -0
  228. package/lib/types/dsh-adapter/migrate/adapters/codex.parse.d.ts.map +1 -0
  229. package/lib/types/dsh-adapter/migrate/adapters/codex.parse.js +289 -0
  230. package/lib/types/dsh-adapter/migrate/adapters/grok-build.d.ts +3 -0
  231. package/lib/types/dsh-adapter/migrate/adapters/grok-build.d.ts.map +1 -0
  232. package/lib/types/dsh-adapter/migrate/adapters/grok-build.js +117 -0
  233. package/lib/types/dsh-adapter/migrate/adapters/grok-build.parse.d.ts +13 -0
  234. package/lib/types/dsh-adapter/migrate/adapters/grok-build.parse.d.ts.map +1 -0
  235. package/lib/types/dsh-adapter/migrate/adapters/grok-build.parse.js +178 -0
  236. package/lib/types/dsh-adapter/migrate/adapters/omp.d.ts +3 -0
  237. package/lib/types/dsh-adapter/migrate/adapters/omp.d.ts.map +1 -0
  238. package/lib/types/dsh-adapter/migrate/adapters/omp.js +133 -0
  239. package/lib/types/dsh-adapter/migrate/adapters/scan-fs.d.ts +60 -0
  240. package/lib/types/dsh-adapter/migrate/adapters/scan-fs.d.ts.map +1 -0
  241. package/lib/types/dsh-adapter/migrate/adapters/scan-fs.js +178 -0
  242. package/lib/types/dsh-adapter/migrate/adapters/scan.d.ts +8 -0
  243. package/lib/types/dsh-adapter/migrate/adapters/scan.d.ts.map +1 -0
  244. package/lib/types/dsh-adapter/migrate/adapters/scan.js +40 -0
  245. package/lib/types/dsh-adapter/migrate/adapters/zcode.d.ts +3 -0
  246. package/lib/types/dsh-adapter/migrate/adapters/zcode.d.ts.map +1 -0
  247. package/lib/types/dsh-adapter/migrate/adapters/zcode.js +93 -0
  248. package/lib/types/dsh-adapter/migrate/adapters/zcode.parse.d.ts +9 -0
  249. package/lib/types/dsh-adapter/migrate/adapters/zcode.parse.d.ts.map +1 -0
  250. package/lib/types/dsh-adapter/migrate/adapters/zcode.parse.js +89 -0
  251. package/lib/types/dsh-adapter/migrate/bin-path.d.ts +6 -0
  252. package/lib/types/dsh-adapter/migrate/bin-path.d.ts.map +1 -0
  253. package/lib/types/dsh-adapter/migrate/bin-path.js +30 -0
  254. package/lib/types/dsh-adapter/migrate/browse.d.ts +42 -0
  255. package/lib/types/dsh-adapter/migrate/browse.d.ts.map +1 -0
  256. package/lib/types/dsh-adapter/migrate/browse.js +90 -0
  257. package/lib/types/dsh-adapter/migrate/cli.d.ts +9 -0
  258. package/lib/types/dsh-adapter/migrate/cli.d.ts.map +1 -0
  259. package/lib/types/dsh-adapter/migrate/cli.js +106 -0
  260. package/lib/types/dsh-adapter/migrate/import-one.d.ts +89 -0
  261. package/lib/types/dsh-adapter/migrate/import-one.d.ts.map +1 -0
  262. package/lib/types/dsh-adapter/migrate/import-one.js +133 -0
  263. package/lib/types/dsh-adapter/migrate/index.d.ts +35 -0
  264. package/lib/types/dsh-adapter/migrate/index.d.ts.map +1 -0
  265. package/lib/types/dsh-adapter/migrate/index.js +107 -0
  266. package/lib/types/dsh-adapter/migrate/parse/injection.d.ts +40 -0
  267. package/lib/types/dsh-adapter/migrate/parse/injection.d.ts.map +1 -0
  268. package/lib/types/dsh-adapter/migrate/parse/injection.js +127 -0
  269. package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts +28 -0
  270. package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts.map +1 -0
  271. package/lib/types/dsh-adapter/migrate/parse/jsonl.js +40 -0
  272. package/lib/types/dsh-adapter/migrate/parse/role-turns.d.ts +28 -0
  273. package/lib/types/dsh-adapter/migrate/parse/role-turns.d.ts.map +1 -0
  274. package/lib/types/dsh-adapter/migrate/parse/role-turns.js +50 -0
  275. package/lib/types/dsh-adapter/migrate/parse/title.d.ts +18 -0
  276. package/lib/types/dsh-adapter/migrate/parse/title.d.ts.map +1 -0
  277. package/lib/types/dsh-adapter/migrate/parse/title.js +29 -0
  278. package/lib/types/dsh-adapter/migrate/parse/tools.d.ts +62 -0
  279. package/lib/types/dsh-adapter/migrate/parse/tools.d.ts.map +1 -0
  280. package/lib/types/dsh-adapter/migrate/parse/tools.js +110 -0
  281. package/lib/types/dsh-adapter/migrate/picker.d.ts +64 -0
  282. package/lib/types/dsh-adapter/migrate/picker.d.ts.map +1 -0
  283. package/lib/types/dsh-adapter/migrate/picker.js +86 -0
  284. package/lib/types/dsh-adapter/migrate/recent-agents.d.ts +40 -0
  285. package/lib/types/dsh-adapter/migrate/recent-agents.d.ts.map +1 -0
  286. package/lib/types/dsh-adapter/migrate/recent-agents.js +100 -0
  287. package/lib/types/dsh-adapter/migrate/sessionize.d.ts +39 -0
  288. package/lib/types/dsh-adapter/migrate/sessionize.d.ts.map +1 -0
  289. package/lib/types/dsh-adapter/migrate/sessionize.js +271 -0
  290. package/lib/types/dsh-adapter/migrate/types.d.ts +176 -0
  291. package/lib/types/dsh-adapter/migrate/types.d.ts.map +1 -0
  292. package/lib/types/dsh-adapter/migrate/types.js +11 -0
  293. package/lib/types/dsh-adapter/migrate/uuid.d.ts +7 -0
  294. package/lib/types/dsh-adapter/migrate/uuid.d.ts.map +1 -0
  295. package/lib/types/dsh-adapter/migrate/uuid.js +25 -0
  296. package/lib/types/dsh-adapter/plugin.d.ts +5 -3
  297. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  298. package/lib/types/dsh-adapter/plugin.js +168 -372
  299. package/lib/types/dsh-adapter/providerWizard.d.ts +17 -0
  300. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  301. package/lib/types/dsh-adapter/providerWizard.js +254 -66
  302. package/lib/types/dsh-adapter/questions.d.ts +22 -15
  303. package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
  304. package/lib/types/dsh-adapter/questions.js +47 -45
  305. package/lib/types/dsh-adapter/sessions/digest.d.ts +11 -6
  306. package/lib/types/dsh-adapter/sessions/digest.d.ts.map +1 -1
  307. package/lib/types/dsh-adapter/sessions/digest.js +20 -13
  308. package/lib/types/dsh-adapter/sessions/frames.d.ts +3 -1
  309. package/lib/types/dsh-adapter/sessions/frames.d.ts.map +1 -1
  310. package/lib/types/dsh-adapter/sessions/frames.js +5 -4
  311. package/lib/types/dsh-adapter/sessions/list.d.ts +6 -1
  312. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
  313. package/lib/types/dsh-adapter/sessions/list.js +188 -164
  314. package/lib/types/dsh-adapter/sessions/recovery.d.ts +16 -0
  315. package/lib/types/dsh-adapter/sessions/recovery.d.ts.map +1 -0
  316. package/lib/types/dsh-adapter/sessions/recovery.js +108 -0
  317. package/lib/types/dsh-adapter/sessions/snapshot.d.ts +7 -0
  318. package/lib/types/dsh-adapter/sessions/snapshot.d.ts.map +1 -0
  319. package/lib/types/dsh-adapter/sessions/snapshot.js +90 -0
  320. package/lib/types/dsh-adapter/sessions/store.d.ts +10 -4
  321. package/lib/types/dsh-adapter/sessions/store.d.ts.map +1 -1
  322. package/lib/types/dsh-adapter/sessions/store.js +62 -25
  323. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  324. package/lib/types/dsh-adapter/settings-sections.js +7 -4
  325. package/lib/types/dsh-adapter/sideQuestion.d.ts +41 -2
  326. package/lib/types/dsh-adapter/sideQuestion.d.ts.map +1 -1
  327. package/lib/types/dsh-adapter/sideQuestion.js +98 -2
  328. package/lib/types/dsh-adapter/subagents.d.ts +44 -6
  329. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  330. package/lib/types/dsh-adapter/subagents.js +163 -26
  331. package/lib/types/herdr.d.ts +2 -0
  332. package/lib/types/herdr.d.ts.map +1 -1
  333. package/lib/types/herdr.js +9 -3
  334. package/lib/types/history.d.ts +13 -1
  335. package/lib/types/history.d.ts.map +1 -1
  336. package/lib/types/history.js +15 -2
  337. package/lib/types/i18n.d.ts +512 -41
  338. package/lib/types/i18n.d.ts.map +1 -1
  339. package/lib/types/i18n.js +199 -20
  340. package/lib/types/ink/components/App.d.ts +2 -2
  341. package/lib/types/ink/components/App.d.ts.map +1 -1
  342. package/lib/types/ink/components/App.js +5 -1
  343. package/lib/types/ink/components/Image.d.ts +14 -1
  344. package/lib/types/ink/components/Image.d.ts.map +1 -1
  345. package/lib/types/ink/components/Image.js +2 -2
  346. package/lib/types/ink/events/input-event.js +3 -3
  347. package/lib/types/ink/hooks/use-terminal-images.d.ts +12 -0
  348. package/lib/types/ink/hooks/use-terminal-images.d.ts.map +1 -1
  349. package/lib/types/ink/hooks/use-terminal-images.js +13 -0
  350. package/lib/types/ink/ink.d.ts +6 -0
  351. package/lib/types/ink/ink.d.ts.map +1 -1
  352. package/lib/types/ink/ink.js +16 -1
  353. package/lib/types/ink/kitty-graphics.d.ts +44 -3
  354. package/lib/types/ink/kitty-graphics.d.ts.map +1 -1
  355. package/lib/types/ink/kitty-graphics.js +184 -23
  356. package/lib/types/ink/log-update.d.ts.map +1 -1
  357. package/lib/types/ink/log-update.js +19 -1
  358. package/lib/types/ink/output.d.ts +24 -2
  359. package/lib/types/ink/output.d.ts.map +1 -1
  360. package/lib/types/ink/output.js +83 -5
  361. package/lib/types/ink/parse-keypress.d.ts +26 -1
  362. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  363. package/lib/types/ink/parse-keypress.js +156 -13
  364. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  365. package/lib/types/ink/render-node-to-output.js +8 -1
  366. package/lib/types/ink/screen.d.ts +24 -0
  367. package/lib/types/ink/screen.d.ts.map +1 -1
  368. package/lib/types/ink/screen.js +89 -0
  369. package/lib/types/ink/selection.d.ts +68 -18
  370. package/lib/types/ink/selection.d.ts.map +1 -1
  371. package/lib/types/ink/selection.js +178 -31
  372. package/lib/types/ink/sixel-codec.d.ts +12 -1
  373. package/lib/types/ink/sixel-codec.d.ts.map +1 -1
  374. package/lib/types/ink/sixel-codec.js +29 -5
  375. package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
  376. package/lib/types/ink/sixel-graphics.js +5 -3
  377. package/lib/types/ink/styles.d.ts +9 -0
  378. package/lib/types/ink/styles.d.ts.map +1 -1
  379. package/lib/types/ink/terminal-image.d.ts +8 -0
  380. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  381. package/lib/types/ink/terminal-querier.d.ts +7 -1
  382. package/lib/types/ink/terminal-querier.d.ts.map +1 -1
  383. package/lib/types/ink/terminal-querier.js +12 -2
  384. package/lib/types/ink/timeline-rail.d.ts +5 -0
  385. package/lib/types/ink/timeline-rail.d.ts.map +1 -1
  386. package/lib/types/math/inline-layout.d.ts +29 -0
  387. package/lib/types/math/inline-layout.d.ts.map +1 -0
  388. package/lib/types/math/inline-layout.js +106 -0
  389. package/lib/types/math/layout.d.ts +58 -0
  390. package/lib/types/math/layout.d.ts.map +1 -0
  391. package/lib/types/math/layout.js +59 -0
  392. package/lib/types/math/renderer.d.ts +64 -0
  393. package/lib/types/math/renderer.d.ts.map +1 -0
  394. package/lib/types/math/renderer.js +256 -0
  395. package/lib/types/screens/Chat.d.ts +17 -1
  396. package/lib/types/screens/Chat.d.ts.map +1 -1
  397. package/lib/types/screens/Chat.js +618 -57
  398. package/lib/types/screens/SessionSupervisor.d.ts +2 -1
  399. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
  400. package/lib/types/screens/SessionSupervisor.js +156 -19
  401. package/lib/types/screens/Settings.d.ts.map +1 -1
  402. package/lib/types/screens/Settings.js +49 -11
  403. package/lib/types/screens/StatusLine.d.ts +5 -1
  404. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  405. package/lib/types/screens/StatusLine.js +47 -17
  406. package/lib/types/screens/chatOverlay.d.ts +5 -0
  407. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  408. package/lib/types/screens/chatOverlay.js +1 -0
  409. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts +47 -0
  410. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts.map +1 -0
  411. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.js +65 -0
  412. package/lib/types/screens/sessionSupervisor/model.d.ts +12 -0
  413. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -1
  414. package/lib/types/screens/sessionSupervisor/model.js +26 -0
  415. package/lib/types/screens/sessionSupervisor/useForeignSessions.d.ts +107 -0
  416. package/lib/types/screens/sessionSupervisor/useForeignSessions.d.ts.map +1 -0
  417. package/lib/types/screens/sessionSupervisor/useForeignSessions.js +312 -0
  418. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +4 -1
  419. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
  420. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +97 -14
  421. package/lib/types/sessionHistory.d.ts +2 -2
  422. package/lib/types/sessionHistory.d.ts.map +1 -1
  423. package/lib/types/sessionHistory.js +31 -5
  424. package/lib/types/settings/definitions.d.ts +681 -0
  425. package/lib/types/settings/definitions.d.ts.map +1 -0
  426. package/lib/types/settings/definitions.js +511 -0
  427. package/lib/types/starAction.d.ts +44 -0
  428. package/lib/types/starAction.d.ts.map +1 -0
  429. package/lib/types/starAction.js +52 -0
  430. package/lib/types/terminal-utils/latex.d.ts +10 -0
  431. package/lib/types/terminal-utils/latex.d.ts.map +1 -0
  432. package/lib/types/terminal-utils/latex.js +1417 -0
  433. package/lib/types/terminal-utils/markdown.d.ts +3 -1
  434. package/lib/types/terminal-utils/markdown.d.ts.map +1 -1
  435. package/lib/types/terminal-utils/markdown.js +26 -1
  436. package/lib/types/terminal-utils/math.d.ts +66 -0
  437. package/lib/types/terminal-utils/math.d.ts.map +1 -0
  438. package/lib/types/terminal-utils/math.js +337 -0
  439. package/lib/types/tips.d.ts.map +1 -1
  440. package/lib/types/tips.js +6 -0
  441. package/lib/types/tuiDisplayPrefs.d.ts +67 -0
  442. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  443. package/lib/types/tuiDisplayPrefs.js +59 -0
  444. package/lib/types/ui.d.ts +1 -1
  445. package/lib/types/ui.d.ts.map +1 -1
  446. package/lib/types/ui.js +1 -1
  447. package/lib/types/usageStats.d.ts +98 -0
  448. package/lib/types/usageStats.d.ts.map +1 -0
  449. package/lib/types/usageStats.js +169 -0
  450. package/lib/types/utils/bmp.d.ts +17 -0
  451. package/lib/types/utils/bmp.d.ts.map +1 -0
  452. package/lib/types/utils/bmp.js +154 -0
  453. package/lib/types/utils/clipboard.d.ts +14 -2
  454. package/lib/types/utils/clipboard.d.ts.map +1 -1
  455. package/lib/types/utils/clipboard.js +143 -8
  456. package/lib/types/utils/fold-long-lines.d.ts +1 -1
  457. package/lib/types/utils/fold-long-lines.d.ts.map +1 -1
  458. package/lib/types/utils/fold-long-lines.js +3 -2
  459. package/lib/types/utils/keymap.d.ts +4 -1
  460. package/lib/types/utils/keymap.d.ts.map +1 -1
  461. package/lib/types/utils/keymap.js +8 -0
  462. package/lib/types/utils/modifiers.d.ts +4 -3
  463. package/lib/types/utils/modifiers.d.ts.map +1 -1
  464. package/lib/types/utils/modifiers.js +4 -3
  465. package/lib/types/working-activity.d.ts +10 -9
  466. package/lib/types/working-activity.d.ts.map +1 -1
  467. package/lib/types/working-activity.js +17 -10
  468. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/LICENSE +202 -0
  469. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/NOTICE +20 -0
  470. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/index.d.ts +16 -0
  471. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/lib/bundled-packages.json +4 -0
  472. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/lib/index.mjs +20 -0
  473. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/lib/index.mjs.LEGAL.txt +35 -0
  474. package/node_modules/@dsh-tui-vendor/mathjax-tex-svg/package.json +29 -0
  475. package/package.json +117 -99
  476. package/lib/types/components/design-system/KeyboardShortcutHint.d.ts +0 -19
  477. package/lib/types/components/design-system/KeyboardShortcutHint.d.ts.map +0 -1
  478. package/lib/types/components/design-system/KeyboardShortcutHint.js +0 -14
  479. package/lib/types/dsh-adapter/channel/activity.d.ts +0 -20
  480. package/lib/types/dsh-adapter/channel/activity.d.ts.map +0 -1
  481. package/lib/types/dsh-adapter/channel/activity.js +0 -113
  482. package/lib/types/terminal-utils/terminal.d.ts +0 -18
  483. package/lib/types/terminal-utils/terminal.d.ts.map +0 -1
  484. package/lib/types/terminal-utils/terminal.js +0 -96
@@ -11,15 +11,25 @@ import { planReload } from '../reload.js';
11
11
  import { AlternateScreen, Box, Image, Text, useInput, ScrollBox, useTheme, useTerminalSize } from '../ui.js';
12
12
  import { usePageInset } from '../components/PageMargin.js';
13
13
  import { POINTER } from '../terminal-utils/figures.js';
14
- import { isPlainReturnInput, modLabel } from '../utils/modifiers.js';
15
- import { actionMatches } from '../utils/keymap.js';
14
+ import { isPlainReturnInput } from '../utils/modifiers.js';
15
+ import { actionMatches, effectiveComboDisplay, primaryComboString } from '../utils/keymap.js';
16
16
  import { formatTokens } from '../terminal-utils/format.js';
17
17
  import { homeDir } from '../utils/paths.js';
18
+ import { execFileNoThrow } from '../utils/execFileNoThrow.js';
18
19
  import { cleanRenderText, cleanScalarText } from '../dsh-adapter/sanitize.js';
19
20
  import { deriveModelGroups, modelPickerLanding, recentCatalogModels, RECENTS_GROUP_PROVIDER, } from '../modelGroups.js';
20
21
  import { readModelRecents, recordModelUse } from '../modelRecents.js';
21
22
  import { TuiDialogStore } from '../dsh-adapter/dialogs.js';
22
23
  import { TuiStatusStore } from '../dsh-adapter/status.js';
24
+ import { ActivityStore, useActivity } from '../dsh-adapter/activity-store.js';
25
+ import { rasterToPng, setMathPreviewOpener } from '../components/mathPreview.js';
26
+ import { renderMathRaster } from '../math/renderer.js';
27
+ /** Formula preview rasters are re-typeset at this cell scale (2× the
28
+ * transcript's) so the card's 100–800% zoom stays sharp instead of upscaling
29
+ * the inline pixels. Bounded like the other image budgets. */
30
+ const MATH_PREVIEW_SCALE = 2;
31
+ const MATH_PREVIEW_MAX_COLUMNS = 480;
32
+ const MATH_PREVIEW_MAX_ROWS = 64;
23
33
  import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
24
34
  import { ApprovalStore } from '../dsh-adapter/approvals.js';
25
35
  import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
@@ -32,6 +42,9 @@ import { useCopyOnSelect } from '../ink/hooks/use-copy-on-select.js';
32
42
  import { useSelection } from '../ink/hooks/use-selection.js';
33
43
  import { NoSelect } from '../ink/components/NoSelect.js';
34
44
  import { LogoHeader, MessageList } from '../components/MessageList.js';
45
+ import { splashFontIdOf } from '../components/splashFonts.js';
46
+ import { StarPrompt } from '../components/StarPrompt.js';
47
+ import { dueStarModal, markStarAsked, STAR_MILESTONES } from '../usageStats.js';
35
48
  import { TimelineRail } from '../components/TimelineRail.js';
36
49
  import { ScrollbarGutter } from '../components/ScrollbarGutter.js';
37
50
  import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
@@ -41,7 +54,9 @@ import { PromptInput } from '../components/PromptInput.js';
41
54
  import { PromptEditorLayer, usePromptEditorOpen } from '../components/PromptEditor.js';
42
55
  import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
43
56
  import { AutoRecapRow } from '../components/AutoRecapRow.js';
57
+ import { CompactionStatusRow } from '../components/CompactionStatusRow.js';
44
58
  import { BalanceReportRow } from '../components/BalanceReportRow.js';
59
+ import { estimateSessionCostSnapshotCny } from '../deepseekPricing.js';
45
60
  import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
46
61
  import { StatusLine } from './StatusLine.js';
47
62
  import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
@@ -51,6 +66,10 @@ import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
51
66
  import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
52
67
  import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
53
68
  import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
69
+ import { MigrateConfirm, MigratePicker } from '../components/MigratePicker.js';
70
+ import { collectMigratePickerRows, MIGRATE_SCAN_SPECS, parseImportSummary, resolveMigrateCommand } from '../dsh-adapter/migrate/picker.js';
71
+ import { collectActivitySamples, recentAgentsFrom } from '../dsh-adapter/migrate/recent-agents.js';
72
+ import { MIGRATION_ADAPTERS } from '../dsh-adapter/migrate/index.js';
54
73
  import { SessionSupervisor } from './SessionSupervisor.js';
55
74
  import { SessionTree } from './SessionTree.js';
56
75
  import { Settings } from './Settings.js';
@@ -88,7 +107,6 @@ import instances from '../ink/instances.js';
88
107
  import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
89
108
  import { useExternalVersion } from '../hooks/useExternalVersion.js';
90
109
  import { TrajectoryScene } from './TrajectoryScene.js';
91
- import { resumeFailureText } from '../sessions/resumeFailure.js';
92
110
  import { markHomeSeen } from '../homePrefs.js';
93
111
  import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
94
112
  import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
@@ -118,6 +136,10 @@ const STATUS_VIEW_UI = Object.freeze({
118
136
  /** Shared empty snapshot for hosts whose channel has no event log. */
119
137
  const NO_EVENTS = [];
120
138
  const COMMAND_RESULT_CELLS = 200;
139
+ /** Ceiling for one `dsh-tui migrate` child run. Discovery parses every source
140
+ * file, but a healthy import of thousands of conversations finishes well
141
+ * inside this; without a cap a wedged child would hang the loop forever. */
142
+ const MIGRATE_CHILD_TIMEOUT_MS = 30 * 60 * 1000;
121
143
  function cleanCommandError(error) {
122
144
  try {
123
145
  if (error instanceof Error) {
@@ -199,12 +221,14 @@ let fallbackApprovalStore;
199
221
  */
200
222
  let fallbackDialogStore;
201
223
  let fallbackStatusStore;
224
+ /** Standalone mounts (tests, bare embeds) without the composition root's store. */
225
+ let fallbackActivityStore;
202
226
  /** Identity of one caret-preview dismissal: the token (its title) on the
203
227
  * image, so the same image staged twice is dismissed per token. */
204
228
  function peekKey(image, title) {
205
229
  return `${title ?? ''} ${image.id}`;
206
230
  }
207
- export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, }) {
231
+ export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, starPrompt, }) {
208
232
  const writeRaw = React.useContext(TerminalWriteContext);
209
233
  // Re-render whenever the channel mutates; rows/status are read fresh below.
210
234
  // DEFAULT lane on purpose (useExternalVersion): the channel version bumps
@@ -237,6 +261,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
237
261
  // Plugin status contributions: text keys join into one line; bounded rich
238
262
  // views keep their own rows immediately above the prompt.
239
263
  const statusContributions = extensionStatus ?? (fallbackStatusStore ??= new TuiStatusStore());
264
+ // The working line: the working-activity plugin's published value for THIS
265
+ // session, read from its session projection. No projection value (plugin
266
+ // absent, or nothing published yet) simply means the classic spinner below.
267
+ const activityValues = activityStore ?? (fallbackActivityStore ??= new ActivityStore());
268
+ const workingActivity = useActivity(activityValues, channel.sessionId);
240
269
  const subscribeStatus = React.useCallback((listener) => statusContributions.subscribe(listener), [statusContributions]);
241
270
  const statusEntries = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getSnapshot());
242
271
  const statusViews = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getViewSnapshot());
@@ -250,18 +279,6 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
250
279
  channel.notify(t('ext-shortcut-failed', { combo }), { color: 'error', timeoutMs: 4000 });
251
280
  });
252
281
  }, [extensionShortcuts, channel]);
253
- // When a questionnaire batch completes, fold a Q&A summary into the
254
- // transcript (the tool card itself is hidden from the message list).
255
- const questionOpenRef = React.useRef(questionSnapshot !== null);
256
- React.useEffect(() => {
257
- const wasOpen = questionOpenRef.current;
258
- questionOpenRef.current = questionSnapshot !== null;
259
- if (wasOpen && questionSnapshot === null) {
260
- for (const summary of questionStore.takeSummaries()) {
261
- channel.pushLocal(summary.title, summary.lines);
262
- }
263
- }
264
- }, [channel, questionSnapshot, questionStore]);
265
282
  const [expanded, setExpanded] = React.useState(false);
266
283
  const [helpOpen, setHelpOpen] = React.useState(false);
267
284
  const [handle, setHandle] = React.useState(null);
@@ -277,6 +294,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
277
294
  const [timeline, setTimeline] = React.useState({
278
295
  turns: [],
279
296
  activeId: null,
297
+ pinnedId: null,
280
298
  upId: null,
281
299
  downId: null,
282
300
  });
@@ -298,6 +316,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
298
316
  * list while the fresh one loads, exactly as the boolean era did.
299
317
  */
300
318
  const [overlay, dispatchOverlay] = React.useReducer(chatOverlayReducer, NO_OVERLAY);
319
+ // `/migrate` picker rows (null = collecting in the background; the picker
320
+ // shows its empty state until the sub-second scan lands).
321
+ const [migrateRows, setMigrateRows] = React.useState(null);
322
+ // Multi-select state (PRD): checked agent ids + the confirmation layer's
323
+ // frozen snapshot of the checked rows.
324
+ const [migrateChecked, setMigrateChecked] = React.useState(new Set());
325
+ const [migratePending, setMigratePending] = React.useState([]);
326
+ // Smart-hint arming: while the migration hint notification is up, a bare
327
+ // Enter (empty prompt, no overlay) jumps straight into the picker with
328
+ // that source pre-checked (PRD #4). Any other key disarms.
329
+ const [migrateHintAgent, setMigrateHintAgent] = React.useState(null);
301
330
  // Chat and PromptInput both receive one parsed stdin batch. Keep the
302
331
  // permission focus synchronous so arrow+Enter in the same batch uses the
303
332
  // post-arrow row rather than the previous render's index.
@@ -410,6 +439,152 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
410
439
  * browser, a screen rather than a panel: it owns its own focus, staged
411
440
  * drafts and keyboard; Chat only opens it. */
412
441
  const [settingsOpen, setSettingsOpen] = React.useState(false);
442
+ /** 99h / 999 次的"求 star"开屏弹窗(`usageStats` 记账,一档只弹一次):
443
+ * 只在启动时判定一次——回合进行中、或已有整屏界面在开(如开机首页),
444
+ * 这一轮不弹也**不记账**,留给下一次启动。`starPrompt` 是测试缝:传
445
+ * `null` 显式关闭,传 actions 覆写两个按钮(不跑真 gh、不开真浏览器)。 */
446
+ /** 本次会话是否已经 star 成功(开屏彩蛋标题切「捡到小星星啦」)。 */
447
+ const [starred, setStarred] = React.useState(false);
448
+ const [starModal, setStarModal] = React.useState(null);
449
+ // 单发闩:只在第一个"安静的开屏视口"上武装定时器。700ms 窗口内整屏
450
+ // 界面打开 → cleanup 掐掉定时器且**不再重臂**(记账只发生在回调里,
451
+ // 所以这一档完好留给下一次启动);整屏界面随后关闭也不追到聊天视图
452
+ // 上补弹——开屏求星不追人。
453
+ const starModalArmedRef = React.useRef(false);
454
+ /** 预览缝(`DSH_TUI_STAR_MODAL=1`):启动即弹一次 99h 档的弹窗,**既不
455
+ * 读账本也不记账**——给作者看效果、给回归夹具用;生产不设这个变量。 */
456
+ const starModalPreview = process.env.DSH_TUI_STAR_MODAL === '1';
457
+ React.useEffect(() => {
458
+ if (starModalArmedRef.current)
459
+ return;
460
+ if (starPrompt === null)
461
+ return;
462
+ if (supervisorOpen || treeOpen || settingsOpen || channel.working)
463
+ return;
464
+ starModalArmedRef.current = true;
465
+ // 让开屏先画半秒:弹窗压在介绍动画之上,而不是同抢第一帧。
466
+ const timer = setTimeout(() => {
467
+ // 到点时回合已经开始的仍不弹(channel 是活对象,读到的是当前值)。
468
+ if (channel.working)
469
+ return;
470
+ if (starModalPreview) {
471
+ const preview = STAR_MILESTONES.findIndex(milestone => milestone.hours === 99);
472
+ if (preview >= 0)
473
+ setStarModal({ index: preview, phase: 'ask' });
474
+ return;
475
+ }
476
+ const index = dueStarModal(starPrompt?.dir);
477
+ if (index === null)
478
+ return;
479
+ markStarAsked(index, starPrompt?.dir);
480
+ setStarModal({ index, phase: 'ask' });
481
+ }, 700);
482
+ return () => { clearTimeout(timer); };
483
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- 只在整屏界面开合时重判;闩保证只武装一次
484
+ }, [supervisorOpen, treeOpen, settingsOpen]);
485
+ /** `/star` 命令、开屏标语的点击/`Alt+S` 共用的一键动作:异步跑 gh,界面
486
+ * 全程不阻塞,结果回来按四类各报一句(成功 / 没装 gh / 没登录 / 失败)。
487
+ * `starPrompt.onStar` 存在时走同一条测试缝(夹具因此不会真的去 star)。 */
488
+ /** 打开仓库页(走 `starPrompt.onOpen` 测试缝——夹具里不会真的拉起浏览器)。 */
489
+ const openStarPage = React.useCallback(() => {
490
+ const seam = starPrompt?.onOpen;
491
+ if (seam !== undefined) {
492
+ seam();
493
+ return;
494
+ }
495
+ void import('../starAction.js').then(({ STAR_REPO }) => {
496
+ openExternal(`https://github.com/${STAR_REPO}`);
497
+ });
498
+ }, [starPrompt]);
499
+ const runStarAction = React.useCallback(() => {
500
+ const seam = starPrompt?.onStar;
501
+ void (seam !== undefined
502
+ ? Promise.resolve(seam())
503
+ : import('../starAction.js').then(async ({ starRepo, STAR_REPO }) => {
504
+ const url = `https://github.com/${STAR_REPO}`;
505
+ const outcome = await starRepo();
506
+ if (outcome.kind === 'starred')
507
+ return { kind: 'starred' };
508
+ if (outcome.kind === 'no-gh')
509
+ return { kind: 'no-gh', url };
510
+ if (outcome.kind === 'not-authed')
511
+ return { kind: 'not-authed', url };
512
+ return { kind: 'failed', detail: outcome.detail, url };
513
+ })).then(attempt => {
514
+ if (attempt.kind === 'starred') {
515
+ setStarred(true);
516
+ // 成功就演一段庆祝(女仆娘接住星星)——`/star`、`Alt+S`、标语点击
517
+ // 都是这一条路。整屏界面开着或回合进行中时弹窗放不下,退回一句
518
+ // 通知,用户至少知道 star 点上了。
519
+ const blocked = channel.working || supervisorOpen || treeOpen || settingsOpen;
520
+ const index = STAR_MILESTONES.findIndex(milestone => milestone.hours === 99);
521
+ if (!blocked && index >= 0) {
522
+ setStarModal({ index, phase: 'done' });
523
+ return;
524
+ }
525
+ channel.notify(t('star-ok'), { color: 'success' });
526
+ return;
527
+ }
528
+ if (attempt.kind === 'no-gh') {
529
+ // 本机没法一键(没装 gh / 没登录)→ **自动**打开仓库页让用户自己点,
530
+ // 通知里说明原因(浏览器没拉起来时 URL 也还在文案里)。
531
+ openStarPage();
532
+ channel.notify(t('star-no-gh', { url: attempt.url }), { color: 'warning' });
533
+ return;
534
+ }
535
+ if (attempt.kind === 'not-authed') {
536
+ openStarPage();
537
+ channel.notify(t('star-not-authed', { url: attempt.url }), { color: 'warning' });
538
+ return;
539
+ }
540
+ channel.notify(t('star-failed', { detail: attempt.detail, url: attempt.url }), { color: 'error' });
541
+ });
542
+ }, [channel, starPrompt, openStarPage, supervisorOpen, treeOpen, settingsOpen]);
543
+ const starModalActions = React.useMemo(() => ({
544
+ // 弹窗自己演结果(成功→庆祝、失败→留在卡里说明原因),所以这里把
545
+ // 结局**回传**给它;`/star` 命令那条路仍走 runStarAction 的 notify。
546
+ onStar: () => {
547
+ const seam = starPrompt?.onStar;
548
+ // 成功把开屏彩蛋切成「捡到星星」版;gh 缺失/未登录**自动**打开仓库页
549
+ // (与一键路径同一套兜底)。注意**不要**给返回值再包一层 `.then()`——
550
+ // 多一个微任务会让弹窗"庆祝那一帧"被紧随其后的 Enter 关窗批掉
551
+ //(夹具 C8/C9 实测)。这里只挂副作用、原样返回。
552
+ const afterAttempt = (attempt) => {
553
+ if (attempt.kind === 'starred')
554
+ setStarred(true);
555
+ else if (attempt.kind === 'no-gh' || attempt.kind === 'not-authed')
556
+ openStarPage();
557
+ return attempt;
558
+ };
559
+ if (seam !== undefined) {
560
+ const result = seam();
561
+ if (result instanceof Promise) {
562
+ void result.then(afterAttempt);
563
+ return result;
564
+ }
565
+ return afterAttempt(result);
566
+ }
567
+ const run = import('../starAction.js').then(async ({ starRepo, STAR_REPO }) => {
568
+ const url = `https://github.com/${STAR_REPO}`;
569
+ const outcome = await starRepo();
570
+ if (outcome.kind === 'starred')
571
+ return { kind: 'starred' };
572
+ if (outcome.kind === 'no-gh')
573
+ return { kind: 'no-gh', url };
574
+ if (outcome.kind === 'not-authed')
575
+ return { kind: 'not-authed', url };
576
+ return { kind: 'failed', detail: outcome.detail, url };
577
+ });
578
+ void run.then(afterAttempt);
579
+ return run;
580
+ },
581
+ onOpen: () => {
582
+ setStarModal(null);
583
+ openStarPage();
584
+ },
585
+ }), [starPrompt, openStarPage]);
586
+ /** 弹窗关闭回调:稳定引用(见渲染处的注释)。 */
587
+ const closeStarModal = React.useCallback(() => { setStarModal(null); }, []);
413
588
  const [workspaceTargets, setWorkspaceTargets] = React.useState([]);
414
589
  const workspaceFlowRequestRef = React.useRef(0);
415
590
  const workspaceFlowAbortRef = React.useRef(null);
@@ -546,14 +721,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
546
721
  * renders with stale folds/expansion/selection — the "entered a freshly
547
722
  * dispatched session and it renders wrong" bug. Reset the same set `/new`
548
723
  * resets, plus the search overlay and the side question, and repaint the
549
- * transcript from the top.
724
+ * transcript pinned to the bottom so rewinds and model switches can continue.
550
725
  */
551
726
  const repaintTranscript = () => {
552
727
  const ink = instances.get(process.stdout) ?? instances.values().next().value;
553
728
  // Wait one task so React commits the new session's tree before the
554
729
  // scrollback clear repaints (same pattern as `/new`).
555
730
  setTimeout(() => {
556
- handle?.scrollTo(0);
731
+ handle?.scrollToBottom();
557
732
  ink?.clearScrollbackAndRedraw();
558
733
  }, 0);
559
734
  };
@@ -733,6 +908,39 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
733
908
  overlay: { kind: 'image-preview', image, gallery, index, ...(title === undefined ? {} : { title }) },
734
909
  });
735
910
  }, [channel]);
911
+ /** A clicked formula opens the same card as a transcript image. The raster
912
+ * is re-typeset at double the cell size, so 100% is a sharper formula
913
+ * rather than an upscaled one; when that re-render fails (too wide, TeX
914
+ * rejected) the pixels already on screen stand in. */
915
+ const openMathPreview = React.useCallback((preview) => {
916
+ const scaled = {
917
+ ...preview.request,
918
+ cellSize: {
919
+ width: preview.request.cellSize.width * MATH_PREVIEW_SCALE,
920
+ height: preview.request.cellSize.height * MATH_PREVIEW_SCALE,
921
+ },
922
+ maxColumns: Math.min(preview.request.maxColumns * MATH_PREVIEW_SCALE, MATH_PREVIEW_MAX_COLUMNS),
923
+ maxRows: Math.min(preview.request.maxRows * MATH_PREVIEW_SCALE, MATH_PREVIEW_MAX_ROWS),
924
+ };
925
+ const image = {
926
+ id: `math:${preview.tex}`,
927
+ width: preview.source.width,
928
+ height: preview.source.height,
929
+ name: preview.tex,
930
+ mediaType: 'image/png',
931
+ read: async () => {
932
+ const rendered = await renderMathRaster(scaled);
933
+ return rasterToPng(rendered.ok ? rendered.raster.source : preview.source);
934
+ },
935
+ };
936
+ openImagePreview(image, preview.tex);
937
+ }, [openImagePreview]);
938
+ // The math components live deep inside the transcript, so the opener is
939
+ // published rather than threaded through every message row's props.
940
+ React.useEffect(() => {
941
+ setMathPreviewOpener(openMathPreview);
942
+ return () => setMathPreviewOpener(undefined);
943
+ }, [openMathPreview]);
736
944
  // Agent-binding generation is monotonic across every agent replacement
737
945
  // and bumps before the replacement emit, closing the ABA hole where a
738
946
  // resumed session reuses the same id. Partial test/embed channels fall
@@ -1099,6 +1307,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1099
1307
  // fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
1100
1308
  // subscription in inline mode, where selection belongs to the terminal.
1101
1309
  // The copy clears the highlight and posts a transient notification.
1310
+ // Smart migration hint (product ask): ~12s after mount, one background
1311
+ // pass over the foreign-agent stores; when a source was active inside the
1312
+ // 20-minute window, surface the user's own wording once per session. The
1313
+ // file-level mtime scan is the counter's walk shape (sub-second) and runs
1314
+ // off the render path; failures read as "no data" and stay silent.
1315
+ const migrateHintShownRef = React.useRef(false);
1316
+ React.useEffect(() => {
1317
+ if (migrateHintShownRef.current)
1318
+ return;
1319
+ const timer = setTimeout(() => {
1320
+ migrateHintShownRef.current = true;
1321
+ void (async () => {
1322
+ const newest = await new Promise(resolve => {
1323
+ setImmediate(() => resolve(collectActivitySamples(MIGRATION_ADAPTERS, adapter => MIGRATE_SCAN_SPECS[adapter.id])));
1324
+ });
1325
+ const top = recentAgentsFrom(newest, Date.now())[0];
1326
+ if (top !== undefined) {
1327
+ channel.notify(t('migrate-hint-notify', { agent: top.label }), { timeoutMs: 10000 });
1328
+ // PRD #4: while the hint is up, a bare Enter (empty prompt, no
1329
+ // overlay) jumps into the picker with this source pre-checked;
1330
+ // the global key layer below consumes it, anything else disarms.
1331
+ setMigrateHintAgent(top.agentId);
1332
+ setTimeout(() => setMigrateHintAgent(current => current === top.agentId ? null : current), 10_000);
1333
+ }
1334
+ })();
1335
+ }, 12_000);
1336
+ return () => clearTimeout(timer);
1337
+ }, [channel]);
1102
1338
  useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }),
1103
1339
  // Stale-selection refusal: the highlighted rows were replaced in place
1104
1340
  // (streaming overwrite), so nothing was copied — say why instead of
@@ -1299,10 +1535,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1299
1535
  const ok = writeLangPref(lang);
1300
1536
  setLang(lang);
1301
1537
  const settingsHost = channel.settingsHost();
1302
- const tuiView = settingsHost?.listNamespaces().find(entry => entry.ns === 'dsh-tui');
1538
+ // This mount's own namespace (custom Loader ids exist): looking up the
1539
+ // literal 'dsh-tui' skipped the mirror entirely on such mounts.
1540
+ const tuiView = settingsHost?.listNamespaces().find(entry => entry.ns === channel.settingsNamespace);
1303
1541
  if (settingsHost !== undefined && tuiView !== undefined) {
1304
1542
  void settingsHost
1305
- .write('dsh-tui', [{ op: 'set', path: ['lang'], value: lang }], tuiView.revision)
1543
+ .write(channel.settingsNamespace, [{ op: 'set', path: ['lang'], value: lang }], tuiView.revision)
1306
1544
  .catch(() => { });
1307
1545
  }
1308
1546
  channel.notify(ok ? t('lang-switched', { lang }) : t('lang-switch-failed', { lang }), { color: ok ? 'success' : 'error' });
@@ -1317,6 +1555,82 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1317
1555
  case 'activity': return t('reload-kind-activity');
1318
1556
  }
1319
1557
  };
1558
+ /** Collect picker rows off the current turn: the scan is synchronous FS
1559
+ * work (name-only walk + per-file stat), so it is deferred by one macrotask
1560
+ * to let the overlay paint its loading state first. */
1561
+ const collectMigrateRows = () => new Promise(resolve => {
1562
+ setImmediate(() => resolve(collectMigratePickerRows(Date.now())));
1563
+ });
1564
+ /** Run `dsh-tui migrate <args>` in a child process through the package
1565
+ * bin; resolves with the exit code and the combined output. Uses the
1566
+ * shared no-throw runner (bounded capture, timeout, windowsHide): a wedged
1567
+ * child would otherwise hang the sequential per-source loop forever. */
1568
+ const runMigrateChild = async (parts) => {
1569
+ const { dirname } = await import('node:path');
1570
+ const { fileURLToPath } = await import('node:url');
1571
+ const { resolveOwnBin } = await import('../dsh-adapter/migrate/bin-path.js');
1572
+ // This file sits at a different depth per layout (src/screens vs
1573
+ // lib/types/screens), so the bin resolves by upward probe — see
1574
+ // bin-path.ts; a fixed dirname count fails on real installs.
1575
+ const bin = resolveOwnBin(dirname(fileURLToPath(import.meta.url)));
1576
+ if (bin === undefined) {
1577
+ channel.notify(t('migrate-spawn-failed'), { color: 'error', timeoutMs: 8000 });
1578
+ return { code: -1, out: '' };
1579
+ }
1580
+ const result = await execFileNoThrow(process.execPath, [bin, 'migrate', ...parts], {
1581
+ timeout: MIGRATE_CHILD_TIMEOUT_MS,
1582
+ });
1583
+ // A killed child reports `code: null` and whatever it managed to print; put
1584
+ // the reason on the record so the transcript does not read as a silent
1585
+ // failure. Nothing at all (no code, no output) means it never really ran.
1586
+ if (result.code === null) {
1587
+ return {
1588
+ code: null,
1589
+ out: `${result.stdout}${result.stderr}${t('migrate-child-timeout', { minutes: MIGRATE_CHILD_TIMEOUT_MS / 60_000 })}\n`,
1590
+ };
1591
+ }
1592
+ if (result.code === 1 && result.stdout === '' && result.stderr === '') {
1593
+ channel.notify(t('migrate-spawn-failed'), { color: 'error', timeoutMs: 8000 });
1594
+ }
1595
+ // stdout carries the per-source report, stderr the usage/error lines;
1596
+ // both belong in the /migrate transcript row.
1597
+ return { code: result.code, out: `${result.stdout}${result.stderr}` };
1598
+ };
1599
+ /** Orchestrate the confirmation layer's confirmed rows (PRD #3): one child
1600
+ * per source, sequential; per-source progress notifications (throttled by
1601
+ * the source boundary — no intra-source spam), real per-source counters
1602
+ * parsed from each child's report, and a final summary that NEVER claims
1603
+ * success for a source that did not run (the P2 fix). */
1604
+ const spawnMigrateSources = (rows, dryRun) => {
1605
+ const allOut = [];
1606
+ let failures = 0;
1607
+ void (async () => {
1608
+ for (let i = 0; i < rows.length; i++) {
1609
+ const row = rows[i];
1610
+ channel.notify(t(dryRun ? 'migrate-previewing-source' : 'migrate-importing-source', { label: row.label, i: i + 1, n: rows.length }), { timeoutMs: 4000 });
1611
+ const { code, out } = await runMigrateChild(dryRun ? [row.agentId, '--dry-run'] : [row.agentId]);
1612
+ allOut.push(...out.split('\n').map(line => cleanRenderText(line, 400)).filter(Boolean));
1613
+ if (code !== 0)
1614
+ failures += 1;
1615
+ // Per-source real counters straight from the child's report line.
1616
+ const summary = parseImportSummary(out).find(entry => entry.agentId === row.agentId);
1617
+ if (!dryRun && summary !== undefined) {
1618
+ channel.notify(t('migrate-source-done', { label: row.label, imported: summary.imported, existing: summary.existing }), { timeoutMs: 6000 });
1619
+ }
1620
+ }
1621
+ // The transcript row is this run's record. When a source failed AND no
1622
+ // child output was captured at all (killed by the timeout, or never
1623
+ // spawned), the success wording would contradict the notification right
1624
+ // above it — report the failure here too.
1625
+ const fallbackLine = failures > 0
1626
+ ? t('migrate-failed', { n: failures })
1627
+ : t(dryRun ? 'migrate-all-previewed' : 'migrate-all-done', { n: rows.length });
1628
+ channel.pushLocal('/migrate', allOut.length > 0 ? allOut : [fallbackLine]);
1629
+ channel.notify(failures === 0
1630
+ ? t(dryRun ? 'migrate-all-previewed' : 'migrate-all-done', { n: rows.length })
1631
+ : t('migrate-failed', { n: failures }), failures === 0 ? { timeoutMs: 6000 } : { color: 'error', timeoutMs: 10000 });
1632
+ })();
1633
+ };
1320
1634
  const runCommand = (name, rawInput = '', images = []) => {
1321
1635
  switch (name) {
1322
1636
  case 'activity': {
@@ -1915,7 +2229,28 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1915
2229
  const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
1916
2230
  lines.push(t('cost-cache-hit-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
1917
2231
  }
1918
- lines.push(t('cost-note'));
2232
+ // 金额与拆解:主会话按模型分桶 + 子代理按各自 (provider, model) 分桶;
2233
+ // 全部未计价时只报 token 并标注未计价,不显示 ¥0.00 金额行(DESIGN D4/D6)。
2234
+ const estimate = estimateSessionCostSnapshotCny({
2235
+ provider: channel.provider,
2236
+ main: channel.mainCost,
2237
+ subagents: channel.subagentCost,
2238
+ fallbackTokens: channel.tokens,
2239
+ fallbackModel: channel.model,
2240
+ });
2241
+ // 金额行与末尾口径共用同一判定:有已计价金额才显示金额行与"估算非账单"
2242
+ // 文案;无金额(无用量 / 全部未计价)只解释 token(#1089)。
2243
+ const hasAmount = estimate !== undefined && estimate.total > 0;
2244
+ if (estimate !== undefined) {
2245
+ if (hasAmount) {
2246
+ lines.push(t('cost-session-estimate', { cost: estimate.total.toFixed(2) }));
2247
+ lines.push(`${t('cost-split-main', { cost: estimate.main.toFixed(2) })} · ${t('cost-split-subagent', { cost: estimate.subagent.toFixed(2) })}`);
2248
+ }
2249
+ if (estimate.unpricedTokens > 0) {
2250
+ lines.push(t('cost-unpriced', { tokens: formatTokens(estimate.unpricedTokens) }));
2251
+ }
2252
+ }
2253
+ lines.push(t(hasAmount ? 'cost-note' : 'cost-note-no-amount'));
1919
2254
  setHelpOpen(false);
1920
2255
  channel.pushLocal('/cost', lines);
1921
2256
  return true;
@@ -1936,6 +2271,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1936
2271
  setSettingsOpen(true);
1937
2272
  return true;
1938
2273
  }
2274
+ case 'star': {
2275
+ // 一键 star:**只有用户主动敲 /star 才会跑**(绝不自动)。动作用
2276
+ // runStarAction(与开屏弹窗共用),异步执行、结果用 notify 报。
2277
+ setHelpOpen(false);
2278
+ runStarAction();
2279
+ return true;
2280
+ }
1939
2281
  case 'config': {
1940
2282
  const userHome = process.env.USERPROFILE ?? '';
1941
2283
  const lines = [
@@ -1953,6 +2295,60 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1953
2295
  setHelpOpen(false);
1954
2296
  channel.pushLocal('/doctor', channel.doctorInfo());
1955
2297
  return true;
2298
+ case 'migrate': {
2299
+ // Double entry points with the CLI. BARE `/migrate` opens the
2300
+ // multi-select picker; `/migrate <agent>` opens the CONFIRMATION
2301
+ // layer for that single source (PRD #2 — a bulk import is never one
2302
+ // keystroke away). Validity is decided against the adapter REGISTRY,
2303
+ // never the picker's row cache: on a fresh mount that cache is empty,
2304
+ // and deriving the answer from it made every `/migrate <agent>` report
2305
+ // an unknown source until the picker had been opened once.
2306
+ setHelpOpen(false);
2307
+ const command = resolveMigrateCommand(rawInput, MIGRATION_ADAPTERS.map(adapter => adapter.id));
2308
+ if (command.kind === 'unknown') {
2309
+ channel.notify(t('migrate-unknown-agent', { agent: command.agentId }), { color: 'error', timeoutMs: 8000 });
2310
+ return true;
2311
+ }
2312
+ if (command.kind === 'usage') {
2313
+ channel.notify(t('migrate-usage'), { color: 'error', timeoutMs: 8000 });
2314
+ return true;
2315
+ }
2316
+ if (command.kind === 'dry-run-needs-source') {
2317
+ channel.notify(t('migrate-dry-run-needs-source'), { color: 'error', timeoutMs: 8000 });
2318
+ return true;
2319
+ }
2320
+ if (command.kind === 'import') {
2321
+ void (async () => {
2322
+ // Rows carry the scannable count the confirmation line shows; a
2323
+ // warm cache from an earlier picker visit is reused as is.
2324
+ const rows = migrateRows ?? await collectMigrateRows();
2325
+ const row = rows.find(candidate => candidate.agentId === command.agentId);
2326
+ if (row === undefined) {
2327
+ channel.notify(t('migrate-unknown-agent', { agent: command.agentId }), { color: 'error', timeoutMs: 8000 });
2328
+ return;
2329
+ }
2330
+ if (command.dryRun) {
2331
+ spawnMigrateSources([row], true);
2332
+ return;
2333
+ }
2334
+ setMigratePending([row]);
2335
+ // Pin the checked set to the source this confirmation is about:
2336
+ // Esc returns to the picker, and a stale set from an earlier visit
2337
+ // would there contradict what the confirmation just showed.
2338
+ setMigrateChecked(new Set([row.agentId]));
2339
+ dispatchOverlay({ type: 'open', overlay: { kind: 'migrate-confirm' } });
2340
+ })();
2341
+ return true;
2342
+ }
2343
+ // Bare `/migrate` starts a NEW flow: drop the previous run's checks
2344
+ // (only Esc-out-of-confirm keeps them) and let the picker show its
2345
+ // scanning state until the rows land.
2346
+ setMigrateRows(null);
2347
+ setMigrateChecked(new Set());
2348
+ dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
2349
+ void collectMigrateRows().then(setMigrateRows);
2350
+ return true;
2351
+ }
1956
2352
  case 'plugins':
1957
2353
  // Plugin diagnostics (C-070): trust banner first, then descriptor /
1958
2354
  // grant matrix / ledger tail — or validate+negotiate for
@@ -2164,7 +2560,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2164
2560
  setHelpOpen(false);
2165
2561
  const tuiNamespace = channel.settingsHost()
2166
2562
  ?.listNamespaces()
2167
- .find(entry => entry.ns === 'dsh-tui');
2563
+ .find(entry => entry.ns === channel.settingsNamespace);
2168
2564
  const plan = planReload({
2169
2565
  envTheme: envThemeOverride(),
2170
2566
  envLang: isLang(process.env.DSH_TUI_LANG) ? process.env.DSH_TUI_LANG : undefined,
@@ -2259,7 +2655,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2259
2655
  setHelpOpen(false);
2260
2656
  channel.pushLocal('/terminal-setup', [
2261
2657
  t('terminal-setup-hint'),
2262
- t('terminal-paste-hint', { mod: modLabel }),
2658
+ t('terminal-paste-hint', { keys: effectiveComboDisplay('paste') }),
2263
2659
  ]);
2264
2660
  return true;
2265
2661
  case 'recap': {
@@ -2619,6 +3015,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2619
3015
  /** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
2620
3016
  const lastModalEnterAtRef = React.useRef(0);
2621
3017
  useInput((input, key, event) => {
3018
+ // 开屏"求 star"弹窗开着时键盘全归它(↑/↓/Enter/Esc 由它自己的
3019
+ // useInput 处理),滚轮也不许滚动它身后的转录——和下面的整屏界面
3020
+ // 同一套让位规则。
3021
+ if (starModal !== null)
3022
+ return;
2622
3023
  // Prompt-slot panels own the keyboard while visible. Their own useInput
2623
3024
  // handles the relevant keys; Chat registered first, so yielding here
2624
3025
  // still lets the panel receive them. PromptInput now stays mounted but
@@ -3050,6 +3451,81 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3050
3451
  }
3051
3452
  return;
3052
3453
  }
3454
+ // Armed migration hint (PRD #4): bare Enter while the hint notification
3455
+ // is up — no overlay and an EMPTY prompt — jumps into the picker with the
3456
+ // hinted source pre-checked; every other key disarms silently. The empty
3457
+ // check is explicit because the composer owns that state: an Enter that
3458
+ // submitted a written message must not also open the picker, and this
3459
+ // listener runs before PromptInput's, so the event is consumed here too.
3460
+ if (migrateHintAgent !== null && overlay.kind === 'none') {
3461
+ if (plainReturn && !(promptControllerRef.current?.hasText() ?? false)) {
3462
+ const agent = migrateHintAgent;
3463
+ event.stopImmediatePropagation();
3464
+ setMigrateHintAgent(null);
3465
+ setMigrateRows(null);
3466
+ setMigrateChecked(new Set([agent]));
3467
+ dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
3468
+ void collectMigrateRows().then(setMigrateRows);
3469
+ return;
3470
+ }
3471
+ setMigrateHintAgent(null);
3472
+ }
3473
+ if (overlay.kind === 'migrate') {
3474
+ const rows = migrateRows ?? [];
3475
+ if (key.upArrow || key.downArrow) {
3476
+ dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: rows.length });
3477
+ }
3478
+ else if (key.escape) {
3479
+ dispatchOverlay({ type: 'close' });
3480
+ }
3481
+ else if (rows.length > 0) {
3482
+ const row = rows[overlay.index];
3483
+ if (input === ' ' && row !== undefined) {
3484
+ setMigrateChecked(current => {
3485
+ const next = new Set(current);
3486
+ if (next.has(row.agentId))
3487
+ next.delete(row.agentId);
3488
+ else
3489
+ next.add(row.agentId);
3490
+ return next;
3491
+ });
3492
+ }
3493
+ else if (input === 'a' && !key.ctrl && !key.meta) {
3494
+ // All/none toggle: a checked-everything state collapses to none.
3495
+ setMigrateChecked(current => current.size >= rows.length ? new Set() : new Set(rows.map(candidate => candidate.agentId)));
3496
+ }
3497
+ else if (plainReturn) {
3498
+ // Checked set wins; the focused row acts as a single selection
3499
+ // when nothing is checked (PRD #1).
3500
+ const chosen = migrateChecked.size > 0
3501
+ ? rows.filter(candidate => migrateChecked.has(candidate.agentId))
3502
+ : row !== undefined ? [row] : [];
3503
+ if (chosen.length > 0) {
3504
+ setMigratePending(chosen);
3505
+ dispatchOverlay({ type: 'close' });
3506
+ dispatchOverlay({ type: 'open', overlay: { kind: 'migrate-confirm' } });
3507
+ }
3508
+ }
3509
+ }
3510
+ return;
3511
+ }
3512
+ if (overlay.kind === 'migrate-confirm') {
3513
+ if (key.escape) {
3514
+ // Back to the picker with the checked set preserved (PRD #2's
3515
+ // "cancel" reads cheapest as "let me change the selection").
3516
+ dispatchOverlay({ type: 'close' });
3517
+ dispatchOverlay({ type: 'open', overlay: { kind: 'migrate', index: 0 } });
3518
+ }
3519
+ else if (plainReturn) {
3520
+ dispatchOverlay({ type: 'close' });
3521
+ spawnMigrateSources(migratePending, false);
3522
+ }
3523
+ else if (input === 'd' && !key.ctrl && !key.meta) {
3524
+ dispatchOverlay({ type: 'close' });
3525
+ spawnMigrateSources(migratePending, true);
3526
+ }
3527
+ return;
3528
+ }
3053
3529
  if (overlay.kind === 'activity') {
3054
3530
  if (key.upArrow || key.downArrow) {
3055
3531
  dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: PRESET_NAMES.length });
@@ -3402,6 +3878,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3402
3878
  }
3403
3879
  event.stopImmediatePropagation();
3404
3880
  }
3881
+ else if (key.escape
3882
+ && !helpOpen
3883
+ && !channel.working
3884
+ && channel.compaction?.cancellable === true
3885
+ && !promptControllerRef.current?.vimActive()) {
3886
+ // Idle Esc otherwise falls through to the prompt's double-tap-clear;
3887
+ // while a manual compaction runs, stopping it is what the status row
3888
+ // promises (and the host closes the bracket cleanly on abort).
3889
+ channel.cancelCompact();
3890
+ event.stopImmediatePropagation();
3891
+ }
3405
3892
  else if (actionMatches('transcript', input, key) && !helpOpen) {
3406
3893
  // Leaving transcript mode (default Ctrl+O) — search was already
3407
3894
  // handled above. Help is modal: toggling this state behind the
@@ -3438,7 +3925,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3438
3925
  // CLEARS a non-empty prompt (single press) and only arms the
3439
3926
  // double-press exit when the input is empty; ctrl+d keeps the
3440
3927
  // time-based double-press exit regardless.
3441
- if (channel.working) {
3928
+ if (input === 'c' && !channel.working && channel.compaction?.cancellable === true) {
3929
+ // Ctrl+C during a manual compaction stops the compaction instead of
3930
+ // arming the double-press exit: exiting the process mid-bracket is how
3931
+ // a session log ends up with an unmatched `compaction/start`. Ctrl+D
3932
+ // keeps its exit meaning, and the next Ctrl+C behaves normally again.
3933
+ channel.cancelCompact();
3934
+ exitPendingRef.current = false;
3935
+ if (exitTimerRef.current)
3936
+ clearTimeout(exitTimerRef.current);
3937
+ }
3938
+ else if (channel.working) {
3442
3939
  // First press while working only interrupts. If that abort is still
3443
3940
  // converging (cancelPending) the next press is the user insisting on
3444
3941
  // leaving: go straight to the exit funnel. Without this, a stuck turn
@@ -3497,6 +3994,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3497
3994
  // Consume: same readline-shadowing rule as dashboard/showAll above.
3498
3995
  event.stopImmediatePropagation();
3499
3996
  }
3997
+ else if (actionMatches('star', input, key)) {
3998
+ // 一键 star(默认 Alt+S)——与 `/star`、开屏标语点击同一个动作。
3999
+ // 消费事件:alt 组合不该再落进输入框当普通字符。
4000
+ runStarAction();
4001
+ event.stopImmediatePropagation();
4002
+ }
3500
4003
  else if (plainReturn && !isSticky) {
3501
4004
  // Enter while scrolled up returns to the bottom: the
3502
4005
  // affordance now exists whenever the view is off the bottom, not
@@ -3524,6 +4027,14 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3524
4027
  // Working-activity line (spinner slot): context-pressure prefix shares the
3525
4028
  // StatusLine thresholds (amber ≥ 80, red ≥ 95).
3526
4029
  const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
4030
+ // Who owns the spinner slot: with the working-activity line on, that slot
4031
+ // draws the user's `/activity` preset, so the compaction row borrows the same
4032
+ // indicator instead of answering with the classic dot.
4033
+ const activitySlot = channel.activityEnabled && !channel.minimal;
4034
+ // An automatic compaction runs INSIDE the turn, so it rides whichever spinner
4035
+ // the slot shows as a badge instead of a second row (the spinner's timer is
4036
+ // the turn's, not the compaction's).
4037
+ const compactionBadge = channel.compaction === undefined ? undefined : t('compact-badge');
3527
4038
  // ── Interrupt lane ─────────────────────────────────────────────────────
3528
4039
  // The approval and ask_user_question panels park the agent until the user
3529
4040
  // answers, but they render inside the conversation layout — every screen
@@ -3537,8 +4048,38 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3537
4048
  // The panel elements are shared with the prompt-slot chain below so the
3538
4049
  // two mount sites cannot drift.
3539
4050
  const approvalPanelNode = approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, background: approvalSnapshot.agentId !== channel.agentId, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : null;
3540
- const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent(), onBack: questionSnapshot.canGoBack
3541
- ? draft => questionStore.backCurrent(draft)
4051
+ const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => {
4052
+ if (!questionStore.stillCurrent(questionSnapshot.key))
4053
+ return;
4054
+ questionStore.answerCurrent(selection);
4055
+ }, onCancel: () => questionStore.cancelCurrent(), onEscape: draft => {
4056
+ // Esc means "back" once a later question is showing, including when
4057
+ // → and Esc share one stdin batch and this panel was mounted for
4058
+ // question 1 (no onBack). Ctrl+C stays on onCancel: it cancels the
4059
+ // whole ask from any question, so a same-batch → must not swallow it.
4060
+ const live = questionStore.getSnapshot();
4061
+ if (live?.canGoBack) {
4062
+ // A same-batch → already saved this panel's draft on the question
4063
+ // it left. Passing that draft into backCurrent would write it onto
4064
+ // the question → just opened.
4065
+ questionStore.backCurrent(questionStore.stillCurrent(questionSnapshot.key) ? draft : undefined);
4066
+ return;
4067
+ }
4068
+ if (live !== null && questionStore.stillCurrent(questionSnapshot.key)) {
4069
+ questionStore.cancelCurrent();
4070
+ }
4071
+ }, onBack: questionSnapshot.canGoBack
4072
+ ? draft => {
4073
+ if (!questionStore.stillCurrent(questionSnapshot.key))
4074
+ return;
4075
+ questionStore.backCurrent(draft);
4076
+ }
4077
+ : undefined, onForward: questionSnapshot.canGoForward
4078
+ ? draft => {
4079
+ if (!questionStore.stillCurrent(questionSnapshot.key))
4080
+ return;
4081
+ questionStore.forwardCurrent(draft);
4082
+ }
3542
4083
  : undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
3543
4084
  const interruptPanel = approvalPanelNode ?? questionPanelNode;
3544
4085
  const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
@@ -3593,19 +4134,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3593
4134
  */
3594
4135
  const agentRowOf = (sessionId) => agentViewRows.find(row => row.id === sessionId);
3595
4136
  const supervisorNode = (_jsx(SessionSupervisor, { channel: channel, home: homeDir(), onClose: closeHome, approval: approvalSnapshot, onApprove: outcome => approvals.decide(outcome), onOpenSession: async (sessionId) => {
4137
+ // A refusal is reported by the screen itself (see `openSession`):
4138
+ // the composer that draws channel notifications is not mounted here.
3596
4139
  const result = await channel.resumeTo(sessionId);
3597
- if (!result.ok) {
3598
- const text = resumeFailureText(result);
3599
- if (text !== undefined)
3600
- channel.notify(text, { color: 'error', timeoutMs: 8000 });
3601
- return false;
3602
- }
4140
+ if (!result.ok)
4141
+ return result;
3603
4142
  channel.notify(t('resume-resumed'));
3604
4143
  suppressLogoIntroRef.current = true;
3605
4144
  setAgentViewReturnId(undefined);
3606
4145
  setSupervisorOpen(false);
3607
4146
  repaintTranscript();
3608
- return true;
4147
+ return result;
3609
4148
  }, onNewSession: async (target) => {
3610
4149
  const ok = await channel.switchWorkspace(target);
3611
4150
  if (ok) {
@@ -3695,7 +4234,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3695
4234
  || overlay.kind === 'tips'
3696
4235
  || (recap !== null && (!recap.auto || recap.expanded))
3697
4236
  || btw !== null
3698
- || questionPanelNode !== null;
4237
+ || questionPanelNode !== null
4238
+ || starModal !== null;
3699
4239
  // The trajectory scene replaces the conversation for as long as it is open.
3700
4240
  // Rendering it INSTEAD of (not above) the transcript is what makes it a
3701
4241
  // screen rather than an overlay: it owns the full viewport, and the
@@ -3720,11 +4260,13 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3720
4260
  presetOptionCount: presetOptions.length,
3721
4261
  }) && !(overlay.kind === 'permission'
3722
4262
  && (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null));
3723
- // The sticky header pins the turn owning the viewport top row
3724
- // (timeline.activeId, reported by MessageList) — scrolled up to an old
3725
- // turn, it carries THAT turn's prompt, not the latest one.
4263
+ // The sticky header pins the turn owning the viewport top row once its
4264
+ // prompt has scrolled out above it (timeline.pinnedId, reported by
4265
+ // MessageList) — scrolled up to an old turn, it carries THAT turn's
4266
+ // prompt, not the latest one. The row stays while scrolled up and goes
4267
+ // blank when nothing is pinned, so the viewport never shifts under it.
3726
4268
  // channel.rows is a live in-place array, so the lookup is per-render.
3727
- const anchorUserRowId = timeline.activeId;
4269
+ const anchorUserRowId = timeline.pinnedId;
3728
4270
  const anchorUserText = anchorUserRowId === null
3729
4271
  ? null
3730
4272
  : channel.rows.find(row => row.id === anchorUserRowId)?.text ?? null;
@@ -3748,7 +4290,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3748
4290
  ? () => setPeekSuppressed(peekKey(activePreview.image, activePreview.title))
3749
4291
  : () => dispatchOverlay({ type: 'close-if', kind: 'image-preview' }), region: imagePreviewRegion }))
3750
4292
  : null;
3751
- return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && anchorUserText && (_jsx(PinnedTurnHeader, { text: anchorUserText, onClick: () => {
4293
+ return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && timeline.activeId !== null && (_jsx(PinnedTurnHeader, { text: anchorUserText || null, onClick: () => {
3752
4294
  // Click snaps the pinned prompt to the viewport top. Jump by the
3753
4295
  // SAME content coordinate the
3754
4296
  // rail's tick uses (timeline turn top = the prompt TEXT top):
@@ -3763,7 +4305,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3763
4305
  seekRow(anchorUserRowId);
3764
4306
  else
3765
4307
  handle?.scrollToBottom();
3766
- } })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: -pageInsetX, children: [_jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd, whale: channel.whale, whaleIdle: channel.whaleIdle && whaleArtVisible, working: channel.working,
4308
+ } })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: -pageInsetX, children: [_jsxs(ScrollBox, { ref: setHandle, flexDirection: "column", flexGrow: 1, flexShrink: 1, stickyScroll: true, children: [_jsx(LogoHeader, { model: channel.model, effort: channel.reasoningEffort, cwd: channel.displayCwd,
4309
+ // 大字字面(设置项 `dsh-tui.splashFont`):`daily` 交回按天轮换
4310
+ // (`undefined`),其余 pin 住一款。
4311
+ fontId: splashFontIdOf(channel.splashFont), whale: channel.whale, whaleIdle: channel.whaleIdle && whaleArtVisible, whaleGirl: channel.whaleGirl, starred: starred, onStarClick: runStarAction, working: channel.working,
3767
4312
  // Resuming a long session skips the ~3.4s opening animation: it
3768
4313
  // keeps firing low-frequency React commits that compete with the
3769
4314
  // transcript mount batches (and the first wheel events) for the
@@ -3771,7 +4316,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3771
4316
  // sessions keep the full intro; restored ones settle instantly.
3772
4317
  // A remount after a whole screen closed also settles instantly
3773
4318
  // (see suppressLogoIntroRef).
3774
- skipIntro: suppressLogoIntroRef.current || 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, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, 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: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, sessionCwd: channel.cwd, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
4319
+ skipIntro: suppressLogoIntroRef.current || 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: primaryComboString('trajectory') }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, 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: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, sessionCwd: channel.cwd, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
3775
4320
  // Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
3776
4321
  // rail (default), the proportional scrollbar, or nothing. The
3777
4322
  // slot keeps its 2 columns in both rendered modes (Qwen's
@@ -3785,16 +4330,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3785
4330
  }
3786
4331
  return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: terminalColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
3787
4332
  })(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
3788
- (channel.activityEnabled &&
3789
- !channel.minimal &&
3790
- channel.workingActivity !== undefined &&
3791
- channel.workingActivity.line !== '' &&
3792
- channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
4333
+ (activitySlot &&
4334
+ workingActivity !== undefined &&
4335
+ workingActivity.line !== '' &&
4336
+ workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
3793
4337
  // Upload = real tokens of the last request; download =
3794
4338
  // the animated chars/4 estimate, matching the classic
3795
4339
  // spinner's counter (the suffix used raw chars before,
3796
- // inflating the reading next to a real upload number).
3797
- suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus }))), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), balance !== null && (_jsx(BalanceReportRow, { result: balance.result, refreshing: balance.refreshing, tokens: channel.tokens, model: channel.model, onRefresh: runBalance, onDismiss: () => setBalance(null) })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), activePreview === null && statusViews.map(view => (_jsx(PluginStatusViewBoundary, { viewKey: view.key, onError: (key, error) => statusContributions.reportViewError(key, error), children: _jsx(Box, { flexDirection: "column", flexShrink: 0, maxHeight: view.maxRows, overflow: "hidden", children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: React.createElement(view.component, {
4340
+ // inflating the reading next to a real upload number). An
4341
+ // automatic compaction mid-turn badges THIS line too — it is
4342
+ // the spinner slot whenever real activity data exists.
4343
+ suffix: `${lastUploadTokens > 0 ? ` · ↑ ${formatTokens(lastUploadTokens)}` : ''} · ↓ ${formatTokens(Math.round(channel.responseChars / 4))} tokens${compactionBadge === undefined ? '' : ` · ${compactionBadge}`}` }) })) : (_jsx(WorkingSpinner, { mode: channel.spinnerMode, hasActiveTools: channel.activeToolCount > 0, responseLengthRef: responseLengthRef, uploadTokensRef: uploadTokensRef, loadingStartTimeRef: loadingStartTimeRef, totalPausedMsRef: totalPausedMsRef, pauseStartTimeRef: pauseStartTimeRef, thinkingStatus: thinkingStatus, suffix: compactionBadge }))), !channel.working && channel.compaction !== undefined && (_jsx(CompactionStatusRow, { compaction: channel.compaction, activityPreset: activitySlot ? channel.activityFrames : undefined })), _jsx(GoalTodoPanel, { channel: channel, collapsed: todoCollapsed, onToggle: () => setTodoCollapsed(previous => !previous) }), recap !== null && recap.auto && !recap.expanded && (_jsx(AutoRecapRow, { summary: recap.summary, streaming: !recap.done, onExpand: () => setRecap(prev => (prev ? { ...prev, expanded: true } : prev)), onDismiss: () => closeRecap() })), balance !== null && (_jsx(BalanceReportRow, { result: balance.result, refreshing: balance.refreshing, tokens: channel.tokens, model: channel.model, provider: channel.provider, mainCost: channel.mainCost, subagentCost: channel.subagentCost, onRefresh: runBalance, onDismiss: () => setBalance(null) })), statusEntries.length > 0 && (_jsx(Text, { dimColor: true, wrap: "truncate", children: statusEntries.map(entry => entry.text).join(' · ') })), activePreview === null && statusViews.map(view => (_jsx(PluginStatusViewBoundary, { viewKey: view.key, onError: (key, error) => statusContributions.reportViewError(key, error), children: _jsx(Box, { flexDirection: "column", flexShrink: 0, maxHeight: view.maxRows, overflow: "hidden", children: _jsx(Box, { flexDirection: "column", flexShrink: 0, children: React.createElement(view.component, {
3798
4344
  React,
3799
4345
  ui: STATUS_VIEW_UI,
3800
4346
  }) }) }) }, `${view.key}:${view.registrationId}`))), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [approvalPanelNode !== null ? (approvalPanelNode) : dialogSnapshot !== null ? (_jsx(ExtensionDialog, { dialog: dialogSnapshot, onDecide: value => dialogs.decide(dialogSnapshot.key, value), onCancel: () => dialogs.cancel(dialogSnapshot.key) }, dialogSnapshot.key)) : overlay.kind === 'tips' ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(TipsPanel, { onClose: () => dispatchOverlay({ type: 'close-if', kind: 'tips' }) }) })) : recap !== null && (!recap.auto || recap.expanded) ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RecapPanel, { summary: recap.summary, title: recap.title, error: recap.error, streaming: !recap.done, titleApplied: recap.titleApplied, onClose: () => {
@@ -3840,11 +4386,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3840
4386
  // clock and shifting every row-count layout invariant.
3841
4387
  channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
3842
4388
  ? backgroundAgentsNeedingInput
3843
- : undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
4389
+ : undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, activity: workingActivity, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
3844
4390
  ? undefined
3845
4391
  : {
3846
4392
  band: wakeBand,
3847
- hint: trajectorySeen ? undefined : `${modLabel}t`,
4393
+ hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
3848
4394
  tick: Math.floor(wakeTime / 120),
3849
4395
  } }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: [overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
3850
4396
  // 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
@@ -3890,7 +4436,19 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3890
4436
  return;
3891
4437
  dispatchOverlay({ type: 'close' });
3892
4438
  void switchModelRecorded(model.provider, model.id, model.name);
3893
- } })) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
4439
+ } })) })), overlay.kind === 'migrate' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigratePicker, { rows: migrateRows ?? [], focusIndex: overlay.index, checked: migrateChecked, loading: migrateRows === null, onPick: (index) => {
4440
+ const row = (migrateRows ?? [])[index];
4441
+ if (!row)
4442
+ return;
4443
+ setMigrateChecked(current => {
4444
+ const next = new Set(current);
4445
+ if (next.has(row.agentId))
4446
+ next.delete(row.agentId);
4447
+ else
4448
+ next.add(row.agentId);
4449
+ return next;
4450
+ });
4451
+ } }) })), overlay.kind === 'migrate-confirm' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigrateConfirm, { rows: migratePending }) })), overlay.kind === 'skills' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: skillsList === null ? (_jsx(SkillsPickerLoading, {})) : (_jsx(SkillsPicker, { skills: skillsList, focusIndex: overlay.index, onPick: (index) => {
3894
4452
  const skill = skillsList[index];
3895
4453
  if (!skill)
3896
4454
  return;
@@ -3976,18 +4534,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3976
4534
  const path = overlay.path;
3977
4535
  dispatchOverlay({ type: 'close' });
3978
4536
  runFileAction(index, path);
3979
- } }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }), _jsx(TooltipLayer, { invalidationKey: `${overlay.kind}:${dialogOverlayOpen}:${btw !== null}`, subscribeInvalidation: subscribeTooltipInvalidation }), _jsx(PromptEditorLayer, {}), promptEditorOpen && imagePreviewNode] }));
4537
+ } }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }))] })] }), _jsx(TooltipLayer, { invalidationKey: `${overlay.kind}:${dialogOverlayOpen}:${btw !== null}`, subscribeInvalidation: subscribeTooltipInvalidation }), _jsx(PromptEditorLayer, {}), promptEditorOpen && imagePreviewNode, starModal !== null && STAR_MILESTONES[starModal.index] !== undefined && (_jsx(StarPrompt, { milestone: STAR_MILESTONES[starModal.index], actions: starModalActions, onClose: closeStarModal, initialPhase: starModal.phase }))] }));
3980
4538
  }
3981
4539
  /**
3982
4540
  * The pinned prompt header shown above the ScrollBox while the user has
3983
- * scrolled up. It pins the user message the transcript viewport is currently
3984
- * showing — the topmost visible user message, or the nearest one above when only assistant
3985
- * content fills the view — so it tracks which turn the user is reading
3986
- * instead of always carrying the latest prompt. Fixed at 1 row so the
3987
- * ScrollBox never shifts when the text changes.
4541
+ * scrolled up. It pins the prompt of the turn the viewport top is showing
4542
+ * once that prompt has scrolled out above it, so it tracks which turn the
4543
+ * user is reading instead of always carrying the latest prompt. With no
4544
+ * such prompt (it still sits on the top row, or the logo owns the top) the
4545
+ * row renders blank rather than repeat on-screen text. Fixed at 1 row so
4546
+ * the ScrollBox never shifts when the text changes or goes blank.
3988
4547
  */
3989
4548
  function PinnedTurnHeader({ text, onClick, }) {
3990
4549
  const { columns } = useTerminalSize();
4550
+ if (text === null)
4551
+ return _jsx(Box, { flexShrink: 0, width: "100%", height: 1 });
3991
4552
  // A one-row Box does not clip its children. Flatten hard line breaks before
3992
4553
  // truncating, otherwise later prompt lines paint down the transcript gutter.
3993
4554
  const label = cleanRenderText(`${POINTER} ${text}`, Math.max(1, columns - 1));