@deepseek-harness-tui/dsh-tui 0.11.2 → 0.13.0

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 (786) hide show
  1. package/README.md +60 -8
  2. package/assets/deepy/README.md +60 -0
  3. package/assets/deepy/frames.json +1 -0
  4. package/assets/whale-girl/whale-girl-happy.png +0 -0
  5. package/assets/whaleGirl/README.md +7 -0
  6. package/assets/whaleGirl/frames.json +1 -0
  7. package/assets/whaleGirl/img/building/00.png +0 -0
  8. package/assets/whaleGirl/img/building/01.png +0 -0
  9. package/assets/whaleGirl/img/building/02.png +0 -0
  10. package/assets/whaleGirl/img/building/03.png +0 -0
  11. package/assets/whaleGirl/img/building/04.png +0 -0
  12. package/assets/whaleGirl/img/building/05.png +0 -0
  13. package/assets/whaleGirl/img/building/06.png +0 -0
  14. package/assets/whaleGirl/img/building/07.png +0 -0
  15. package/assets/whaleGirl/img/building/08.png +0 -0
  16. package/assets/whaleGirl/img/building/09.png +0 -0
  17. package/assets/whaleGirl/img/building/10.png +0 -0
  18. package/assets/whaleGirl/img/building/11.png +0 -0
  19. package/assets/whaleGirl/img/building/12.png +0 -0
  20. package/assets/whaleGirl/img/building/13.png +0 -0
  21. package/assets/whaleGirl/img/building/14.png +0 -0
  22. package/assets/whaleGirl/img/building/15.png +0 -0
  23. package/assets/whaleGirl/img/building/16.png +0 -0
  24. package/assets/whaleGirl/img/building/17.png +0 -0
  25. package/assets/whaleGirl/img/building/18.png +0 -0
  26. package/assets/whaleGirl/img/building/19.png +0 -0
  27. package/assets/whaleGirl/img/building/20.png +0 -0
  28. package/assets/whaleGirl/img/building/21.png +0 -0
  29. package/assets/whaleGirl/img/building/22.png +0 -0
  30. package/assets/whaleGirl/img/building/23.png +0 -0
  31. package/assets/whaleGirl/img/carrying/00.png +0 -0
  32. package/assets/whaleGirl/img/carrying/01.png +0 -0
  33. package/assets/whaleGirl/img/carrying/02.png +0 -0
  34. package/assets/whaleGirl/img/carrying/03.png +0 -0
  35. package/assets/whaleGirl/img/carrying/04.png +0 -0
  36. package/assets/whaleGirl/img/carrying/05.png +0 -0
  37. package/assets/whaleGirl/img/carrying/06.png +0 -0
  38. package/assets/whaleGirl/img/carrying/07.png +0 -0
  39. package/assets/whaleGirl/img/compacting/00.png +0 -0
  40. package/assets/whaleGirl/img/compacting/01.png +0 -0
  41. package/assets/whaleGirl/img/compacting/02.png +0 -0
  42. package/assets/whaleGirl/img/compacting/03.png +0 -0
  43. package/assets/whaleGirl/img/compacting/04.png +0 -0
  44. package/assets/whaleGirl/img/compacting/05.png +0 -0
  45. package/assets/whaleGirl/img/compacting/06.png +0 -0
  46. package/assets/whaleGirl/img/compacting/07.png +0 -0
  47. package/assets/whaleGirl/img/compacting/08.png +0 -0
  48. package/assets/whaleGirl/img/compacting/09.png +0 -0
  49. package/assets/whaleGirl/img/conducting/00.png +0 -0
  50. package/assets/whaleGirl/img/conducting/01.png +0 -0
  51. package/assets/whaleGirl/img/conducting/02.png +0 -0
  52. package/assets/whaleGirl/img/conducting/03.png +0 -0
  53. package/assets/whaleGirl/img/conducting/04.png +0 -0
  54. package/assets/whaleGirl/img/conducting/05.png +0 -0
  55. package/assets/whaleGirl/img/conducting/06.png +0 -0
  56. package/assets/whaleGirl/img/conducting/07.png +0 -0
  57. package/assets/whaleGirl/img/conducting/08.png +0 -0
  58. package/assets/whaleGirl/img/conducting/09.png +0 -0
  59. package/assets/whaleGirl/img/conducting/10.png +0 -0
  60. package/assets/whaleGirl/img/conducting/11.png +0 -0
  61. package/assets/whaleGirl/img/drag/00.png +0 -0
  62. package/assets/whaleGirl/img/drag/01.png +0 -0
  63. package/assets/whaleGirl/img/drag/02.png +0 -0
  64. package/assets/whaleGirl/img/drag/03.png +0 -0
  65. package/assets/whaleGirl/img/drag/04.png +0 -0
  66. package/assets/whaleGirl/img/drag/05.png +0 -0
  67. package/assets/whaleGirl/img/drag/06.png +0 -0
  68. package/assets/whaleGirl/img/drag/07.png +0 -0
  69. package/assets/whaleGirl/img/drag/08.png +0 -0
  70. package/assets/whaleGirl/img/drag/09.png +0 -0
  71. package/assets/whaleGirl/img/error/00.png +0 -0
  72. package/assets/whaleGirl/img/error/01.png +0 -0
  73. package/assets/whaleGirl/img/error/02.png +0 -0
  74. package/assets/whaleGirl/img/error/03.png +0 -0
  75. package/assets/whaleGirl/img/error/04.png +0 -0
  76. package/assets/whaleGirl/img/error/05.png +0 -0
  77. package/assets/whaleGirl/img/error/06.png +0 -0
  78. package/assets/whaleGirl/img/error/07.png +0 -0
  79. package/assets/whaleGirl/img/error/08.png +0 -0
  80. package/assets/whaleGirl/img/error/09.png +0 -0
  81. package/assets/whaleGirl/img/happy/00.png +0 -0
  82. package/assets/whaleGirl/img/happy/01.png +0 -0
  83. package/assets/whaleGirl/img/happy/02.png +0 -0
  84. package/assets/whaleGirl/img/happy/03.png +0 -0
  85. package/assets/whaleGirl/img/happy/04.png +0 -0
  86. package/assets/whaleGirl/img/happy/05.png +0 -0
  87. package/assets/whaleGirl/img/happy/06.png +0 -0
  88. package/assets/whaleGirl/img/happy/07.png +0 -0
  89. package/assets/whaleGirl/img/happy/08.png +0 -0
  90. package/assets/whaleGirl/img/happy/09.png +0 -0
  91. package/assets/whaleGirl/img/happy/10.png +0 -0
  92. package/assets/whaleGirl/img/idle/00.png +0 -0
  93. package/assets/whaleGirl/img/idle/01.png +0 -0
  94. package/assets/whaleGirl/img/idle/02.png +0 -0
  95. package/assets/whaleGirl/img/idle/03.png +0 -0
  96. package/assets/whaleGirl/img/idle/04.png +0 -0
  97. package/assets/whaleGirl/img/idle/05.png +0 -0
  98. package/assets/whaleGirl/img/idle/06.png +0 -0
  99. package/assets/whaleGirl/img/idle/07.png +0 -0
  100. package/assets/whaleGirl/img/idle/08.png +0 -0
  101. package/assets/whaleGirl/img/idle/09.png +0 -0
  102. package/assets/whaleGirl/img/idle/10.png +0 -0
  103. package/assets/whaleGirl/img/idle/11.png +0 -0
  104. package/assets/whaleGirl/img/idle/12.png +0 -0
  105. package/assets/whaleGirl/img/idle/13.png +0 -0
  106. package/assets/whaleGirl/img/idle/14.png +0 -0
  107. package/assets/whaleGirl/img/idle/15.png +0 -0
  108. package/assets/whaleGirl/img/idle/16.png +0 -0
  109. package/assets/whaleGirl/img/idle/17.png +0 -0
  110. package/assets/whaleGirl/img/idle/18.png +0 -0
  111. package/assets/whaleGirl/img/idle/19.png +0 -0
  112. package/assets/whaleGirl/img/idle/20.png +0 -0
  113. package/assets/whaleGirl/img/idle-look/00.png +0 -0
  114. package/assets/whaleGirl/img/idle-look/01.png +0 -0
  115. package/assets/whaleGirl/img/idle-look/02.png +0 -0
  116. package/assets/whaleGirl/img/idle-look/03.png +0 -0
  117. package/assets/whaleGirl/img/idle-look/04.png +0 -0
  118. package/assets/whaleGirl/img/idle-look/05.png +0 -0
  119. package/assets/whaleGirl/img/idle-look/06.png +0 -0
  120. package/assets/whaleGirl/img/idle-look/07.png +0 -0
  121. package/assets/whaleGirl/img/idle-look/08.png +0 -0
  122. package/assets/whaleGirl/img/idle-look/09.png +0 -0
  123. package/assets/whaleGirl/img/idle-look/10.png +0 -0
  124. package/assets/whaleGirl/img/idle-look/11.png +0 -0
  125. package/assets/whaleGirl/img/idle-look/12.png +0 -0
  126. package/assets/whaleGirl/img/idle-spout/00.png +0 -0
  127. package/assets/whaleGirl/img/idle-spout/01.png +0 -0
  128. package/assets/whaleGirl/img/idle-spout/02.png +0 -0
  129. package/assets/whaleGirl/img/idle-spout/03.png +0 -0
  130. package/assets/whaleGirl/img/idle-spout/04.png +0 -0
  131. package/assets/whaleGirl/img/idle-spout/05.png +0 -0
  132. package/assets/whaleGirl/img/idle-spout/06.png +0 -0
  133. package/assets/whaleGirl/img/idle-spout/07.png +0 -0
  134. package/assets/whaleGirl/img/idle-spout/08.png +0 -0
  135. package/assets/whaleGirl/img/idle-spout/09.png +0 -0
  136. package/assets/whaleGirl/img/idle-spout/10.png +0 -0
  137. package/assets/whaleGirl/img/music/00.png +0 -0
  138. package/assets/whaleGirl/img/music/01.png +0 -0
  139. package/assets/whaleGirl/img/music/02.png +0 -0
  140. package/assets/whaleGirl/img/music/03.png +0 -0
  141. package/assets/whaleGirl/img/music/04.png +0 -0
  142. package/assets/whaleGirl/img/music/05.png +0 -0
  143. package/assets/whaleGirl/img/music/06.png +0 -0
  144. package/assets/whaleGirl/img/music/07.png +0 -0
  145. package/assets/whaleGirl/img/notification/00.png +0 -0
  146. package/assets/whaleGirl/img/notification/01.png +0 -0
  147. package/assets/whaleGirl/img/notification/02.png +0 -0
  148. package/assets/whaleGirl/img/notification/03.png +0 -0
  149. package/assets/whaleGirl/img/notification/04.png +0 -0
  150. package/assets/whaleGirl/img/notification/05.png +0 -0
  151. package/assets/whaleGirl/img/notification/06.png +0 -0
  152. package/assets/whaleGirl/img/notification/07.png +0 -0
  153. package/assets/whaleGirl/img/poke-left/00.png +0 -0
  154. package/assets/whaleGirl/img/poke-left/01.png +0 -0
  155. package/assets/whaleGirl/img/poke-left/02.png +0 -0
  156. package/assets/whaleGirl/img/poke-left/03.png +0 -0
  157. package/assets/whaleGirl/img/poke-left/04.png +0 -0
  158. package/assets/whaleGirl/img/poke-left/05.png +0 -0
  159. package/assets/whaleGirl/img/poke-left/06.png +0 -0
  160. package/assets/whaleGirl/img/poke-left/07.png +0 -0
  161. package/assets/whaleGirl/img/poke-left/08.png +0 -0
  162. package/assets/whaleGirl/img/poke-left/09.png +0 -0
  163. package/assets/whaleGirl/img/poke-left/10.png +0 -0
  164. package/assets/whaleGirl/img/poke-right/00.png +0 -0
  165. package/assets/whaleGirl/img/poke-right/01.png +0 -0
  166. package/assets/whaleGirl/img/poke-right/02.png +0 -0
  167. package/assets/whaleGirl/img/poke-right/03.png +0 -0
  168. package/assets/whaleGirl/img/poke-right/04.png +0 -0
  169. package/assets/whaleGirl/img/poke-right/05.png +0 -0
  170. package/assets/whaleGirl/img/poke-right/06.png +0 -0
  171. package/assets/whaleGirl/img/poke-right/07.png +0 -0
  172. package/assets/whaleGirl/img/poke-right/08.png +0 -0
  173. package/assets/whaleGirl/img/poke-right/09.png +0 -0
  174. package/assets/whaleGirl/img/poke-right/10.png +0 -0
  175. package/assets/whaleGirl/img/sleeping/00.png +0 -0
  176. package/assets/whaleGirl/img/sleeping/01.png +0 -0
  177. package/assets/whaleGirl/img/sleeping/02.png +0 -0
  178. package/assets/whaleGirl/img/sleeping/03.png +0 -0
  179. package/assets/whaleGirl/img/sleeping/04.png +0 -0
  180. package/assets/whaleGirl/img/sleeping/05.png +0 -0
  181. package/assets/whaleGirl/img/smile-hearts/00.png +0 -0
  182. package/assets/whaleGirl/img/smile-hearts/01.png +0 -0
  183. package/assets/whaleGirl/img/smile-hearts/02.png +0 -0
  184. package/assets/whaleGirl/img/smile-hearts/03.png +0 -0
  185. package/assets/whaleGirl/img/smile-hearts/04.png +0 -0
  186. package/assets/whaleGirl/img/smile-hearts/05.png +0 -0
  187. package/assets/whaleGirl/img/smile-hearts/06.png +0 -0
  188. package/assets/whaleGirl/img/smile-hearts/07.png +0 -0
  189. package/assets/whaleGirl/img/smile-hearts/08.png +0 -0
  190. package/assets/whaleGirl/img/smile-hearts/09.png +0 -0
  191. package/assets/whaleGirl/img/smile-hearts/10.png +0 -0
  192. package/assets/whaleGirl/img/smile-hearts/11.png +0 -0
  193. package/assets/whaleGirl/img/smile-hearts/12.png +0 -0
  194. package/assets/whaleGirl/img/smile-hearts/13.png +0 -0
  195. package/assets/whaleGirl/img/smile-hearts/14.png +0 -0
  196. package/assets/whaleGirl/img/smile-hearts/15.png +0 -0
  197. package/assets/whaleGirl/img/swim/00.png +0 -0
  198. package/assets/whaleGirl/img/swim/01.png +0 -0
  199. package/assets/whaleGirl/img/swim/02.png +0 -0
  200. package/assets/whaleGirl/img/swim/03.png +0 -0
  201. package/assets/whaleGirl/img/swim/04.png +0 -0
  202. package/assets/whaleGirl/img/swim/05.png +0 -0
  203. package/assets/whaleGirl/img/swim/06.png +0 -0
  204. package/assets/whaleGirl/img/swim/07.png +0 -0
  205. package/assets/whaleGirl/img/thinking/00.png +0 -0
  206. package/assets/whaleGirl/img/thinking/01.png +0 -0
  207. package/assets/whaleGirl/img/thinking/02.png +0 -0
  208. package/assets/whaleGirl/img/thinking/03.png +0 -0
  209. package/assets/whaleGirl/img/thinking/04.png +0 -0
  210. package/assets/whaleGirl/img/thinking/05.png +0 -0
  211. package/assets/whaleGirl/img/thinking/06.png +0 -0
  212. package/assets/whaleGirl/img/thinking/07.png +0 -0
  213. package/assets/whaleGirl/img/thinking/08.png +0 -0
  214. package/assets/whaleGirl/img/thinking/09.png +0 -0
  215. package/assets/whaleGirl/img/thinking/10.png +0 -0
  216. package/assets/whaleGirl/img/thinking/11.png +0 -0
  217. package/assets/whaleGirl/img/thinking/12.png +0 -0
  218. package/assets/whaleGirl/img/thinking/13.png +0 -0
  219. package/assets/whaleGirl/img/thinking/14.png +0 -0
  220. package/assets/whaleGirl/img/thinking/15.png +0 -0
  221. package/assets/whaleGirl/img/thinking/16.png +0 -0
  222. package/assets/whaleGirl/img/thumbs-up/00.png +0 -0
  223. package/assets/whaleGirl/img/thumbs-up/01.png +0 -0
  224. package/assets/whaleGirl/img/thumbs-up/02.png +0 -0
  225. package/assets/whaleGirl/img/thumbs-up/03.png +0 -0
  226. package/assets/whaleGirl/img/thumbs-up/04.png +0 -0
  227. package/assets/whaleGirl/img/thumbs-up/05.png +0 -0
  228. package/assets/whaleGirl/img/thumbs-up/06.png +0 -0
  229. package/assets/whaleGirl/img/thumbs-up/07.png +0 -0
  230. package/assets/whaleGirl/img/thumbs-up/08.png +0 -0
  231. package/assets/whaleGirl/img/thumbs-up/09.png +0 -0
  232. package/assets/whaleGirl/img/thumbs-up/10.png +0 -0
  233. package/assets/whaleGirl/img/thumbs-up/11.png +0 -0
  234. package/assets/whaleGirl/img/thumbs-up/12.png +0 -0
  235. package/assets/whaleGirl/img/tickle/00.png +0 -0
  236. package/assets/whaleGirl/img/tickle/01.png +0 -0
  237. package/assets/whaleGirl/img/tickle/02.png +0 -0
  238. package/assets/whaleGirl/img/tickle/03.png +0 -0
  239. package/assets/whaleGirl/img/tickle/04.png +0 -0
  240. package/assets/whaleGirl/img/tickle/05.png +0 -0
  241. package/assets/whaleGirl/img/tickle/06.png +0 -0
  242. package/assets/whaleGirl/img/tickle/07.png +0 -0
  243. package/assets/whaleGirl/img/tickle/08.png +0 -0
  244. package/assets/whaleGirl/img/tickle/09.png +0 -0
  245. package/assets/whaleGirl/img/tickle/10.png +0 -0
  246. package/assets/whaleGirl/img/tickle/11.png +0 -0
  247. package/assets/whaleGirl/img/tickle/12.png +0 -0
  248. package/assets/whaleGirl/img/tickle/13.png +0 -0
  249. package/assets/whaleGirl/img/timings.json +1 -0
  250. package/assets/whaleGirl/img/typing/00.png +0 -0
  251. package/assets/whaleGirl/img/typing/01.png +0 -0
  252. package/assets/whaleGirl/img/typing/02.png +0 -0
  253. package/assets/whaleGirl/img/typing/03.png +0 -0
  254. package/assets/whaleGirl/img/typing/04.png +0 -0
  255. package/assets/whaleGirl/img/typing/05.png +0 -0
  256. package/assets/whaleGirl/img/typing/06.png +0 -0
  257. package/assets/whaleGirl/img/typing/07.png +0 -0
  258. package/assets/whaleGirl/img/typing/08.png +0 -0
  259. package/assets/whaleGirl/img/typing/09.png +0 -0
  260. package/assets/whaleGirl/img/typing/10.png +0 -0
  261. package/assets/whaleGirl/img/typing/11.png +0 -0
  262. package/assets/whaleGirl/img/typing/12.png +0 -0
  263. package/assets/whaleGirl/img/typing/13.png +0 -0
  264. package/assets/whaleGirl/img/waking/00.png +0 -0
  265. package/assets/whaleGirl/img/waking/01.png +0 -0
  266. package/assets/whaleGirl/img/waking/02.png +0 -0
  267. package/assets/whaleGirl/img/waking/03.png +0 -0
  268. package/assets/whaleGirl/img/waking/04.png +0 -0
  269. package/assets/whaleGirl/img/waking/05.png +0 -0
  270. package/assets/whaleGirl/img/waking/06.png +0 -0
  271. package/assets/whaleGirl/img/waking/07.png +0 -0
  272. package/assets/whaleGirl/img/waking/08.png +0 -0
  273. package/assets/whaleGirl/img/waking/09.png +0 -0
  274. package/assets/whaleGirl/img/waking/10.png +0 -0
  275. package/bin/dsh-tui.js +38 -6
  276. package/cordis.patch.yml +31 -6
  277. package/guide/dsh-tui-guide/SKILL.md +37 -0
  278. package/guide/dsh-tui-guide/configuration.en.md +415 -0
  279. package/guide/dsh-tui-guide/configuration.md +354 -0
  280. package/guide/dsh-tui-guide/getting-started.en.md +401 -0
  281. package/guide/dsh-tui-guide/getting-started.md +399 -0
  282. package/guide/dsh-tui-guide/interaction.en.md +738 -0
  283. package/guide/dsh-tui-guide/interaction.md +793 -0
  284. package/guide/dsh-tui-guide/migrate.en.md +166 -0
  285. package/guide/dsh-tui-guide/migrate.md +132 -0
  286. package/guide/dsh-tui-guide/plugins.en.md +78 -0
  287. package/guide/dsh-tui-guide/plugins.md +64 -0
  288. package/guide/dsh-tui-guide/themes.en.md +197 -0
  289. package/guide/dsh-tui-guide/themes.md +188 -0
  290. package/guide/dsh-tui-guide/user-guide.en.md +711 -0
  291. package/guide/dsh-tui-guide/user-guide.md +666 -0
  292. package/guide/dsh-tui-guide/vscode.en.md +301 -0
  293. package/guide/dsh-tui-guide/vscode.md +272 -0
  294. package/lib/settings.json +219 -5
  295. package/lib/types/adapter/channel/ui-policy.d.ts +7 -2
  296. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  297. package/lib/types/adapter/channel/ui-policy.js +18 -1
  298. package/lib/types/adapter/index.d.ts +1 -1
  299. package/lib/types/adapter/index.js +1 -1
  300. package/lib/types/adapter/kernel/host-facade.d.ts +4 -1
  301. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  302. package/lib/types/adapter/kernel/host-facade.js +8 -1
  303. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  304. package/lib/types/adapter/kernel/runtime.js +13 -0
  305. package/lib/types/adapter/kernel/slices/index.d.ts +1 -0
  306. package/lib/types/adapter/kernel/slices/index.d.ts.map +1 -1
  307. package/lib/types/adapter/kernel/slices/index.js +3 -0
  308. package/lib/types/adapter/kernel/slices/panels.d.ts +3 -0
  309. package/lib/types/adapter/kernel/slices/panels.d.ts.map +1 -0
  310. package/lib/types/adapter/kernel/slices/panels.js +14 -0
  311. package/lib/types/adapter/ports/channel-capabilities.d.ts +48 -0
  312. package/lib/types/adapter/ports/channel-capabilities.d.ts.map +1 -0
  313. package/lib/types/adapter/ports/channel-capabilities.js +2 -0
  314. package/lib/types/adapter/ports/channel-catalog.d.ts +7 -0
  315. package/lib/types/adapter/ports/channel-catalog.d.ts.map +1 -1
  316. package/lib/types/adapter/ports/channel-display.d.ts +10 -0
  317. package/lib/types/adapter/ports/channel-display.d.ts.map +1 -1
  318. package/lib/types/adapter/ports/channel-settings.d.ts +2 -1
  319. package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
  320. package/lib/types/adapter/ports/channel-ui.d.ts +91 -4
  321. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  322. package/lib/types/adapter/ports/channel-view.d.ts +137 -9
  323. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  324. package/lib/types/adapter/ports/channel.d.ts +1 -1
  325. package/lib/types/adapter/ports/channel.js +1 -1
  326. package/lib/types/adapter/ports/descriptor.d.ts +1 -1
  327. package/lib/types/adapter/ports/descriptor.js +1 -1
  328. package/lib/types/adapter/ports/index.d.ts +1 -1
  329. package/lib/types/adapter/ports/index.js +1 -1
  330. package/lib/types/adapter/ports/panels.d.ts +28 -0
  331. package/lib/types/adapter/ports/panels.d.ts.map +1 -0
  332. package/lib/types/adapter/ports/panels.js +8 -0
  333. package/lib/types/adapter/ports/workspace.d.ts +1 -1
  334. package/lib/types/adapter/ports/workspace.js +1 -1
  335. package/lib/types/adapter/spec/index.d.ts +2 -2
  336. package/lib/types/adapter/spec/index.js +2 -2
  337. package/lib/types/adapter/spec/profile.d.ts +6 -6
  338. package/lib/types/adapter/spec/profile.d.ts.map +1 -1
  339. package/lib/types/adapter/spec/profile.js +6 -6
  340. package/lib/types/adapter/spec/protocol-constants.d.ts +3 -3
  341. package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -1
  342. package/lib/types/adapter/spec/protocol-constants.js +5 -5
  343. package/lib/types/adapter/spec/tui-contributions.d.ts +4 -4
  344. package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -1
  345. package/lib/types/adapter/spec/tui-contributions.js +5 -5
  346. package/lib/types/adapter/standard/descriptor.js +1 -1
  347. package/lib/types/adapter/standard/index.d.ts +3 -3
  348. package/lib/types/adapter/standard/index.js +3 -3
  349. package/lib/types/adapter/standard/registry.d.ts +3 -2
  350. package/lib/types/adapter/standard/registry.d.ts.map +1 -1
  351. package/lib/types/adapter/standard/registry.js +6 -5
  352. package/lib/types/adapter/standard/tui-extension.d.ts +1 -1
  353. package/lib/types/adapter/standard/tui-extension.js +1 -1
  354. package/lib/types/adapter/upstream/index.d.ts +1 -0
  355. package/lib/types/adapter/upstream/index.d.ts.map +1 -1
  356. package/lib/types/adapter/upstream/index.js +1 -0
  357. package/lib/types/adapter/upstream/panels-driver.d.ts +18 -0
  358. package/lib/types/adapter/upstream/panels-driver.d.ts.map +1 -0
  359. package/lib/types/adapter/upstream/panels-driver.js +159 -0
  360. package/lib/types/api.d.ts +2 -0
  361. package/lib/types/api.d.ts.map +1 -1
  362. package/lib/types/api.js +3 -1
  363. package/lib/types/commands.d.ts.map +1 -1
  364. package/lib/types/commands.js +12 -4
  365. package/lib/types/components/ActivityLine.d.ts +6 -9
  366. package/lib/types/components/ActivityLine.d.ts.map +1 -1
  367. package/lib/types/components/ActivityLine.js +7 -8
  368. package/lib/types/components/Chat/JobCard.d.ts +27 -1
  369. package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
  370. package/lib/types/components/Chat/JobCard.js +128 -33
  371. package/lib/types/components/Chat/JobGroupHeader.d.ts +27 -0
  372. package/lib/types/components/Chat/JobGroupHeader.d.ts.map +1 -0
  373. package/lib/types/components/Chat/JobGroupHeader.js +62 -0
  374. package/lib/types/components/Chat/SubagentMessage.js +6 -6
  375. package/lib/types/components/GoalTodoPanel.d.ts +13 -1
  376. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  377. package/lib/types/components/GoalTodoPanel.js +55 -14
  378. package/lib/types/components/JobsPanel.d.ts +42 -2
  379. package/lib/types/components/JobsPanel.d.ts.map +1 -1
  380. package/lib/types/components/JobsPanel.js +220 -40
  381. package/lib/types/components/LogoV2.d.ts +43 -3
  382. package/lib/types/components/LogoV2.d.ts.map +1 -1
  383. package/lib/types/components/LogoV2.js +42 -15
  384. package/lib/types/components/MessageList.d.ts +8 -2
  385. package/lib/types/components/MessageList.d.ts.map +1 -1
  386. package/lib/types/components/MessageList.js +215 -21
  387. package/lib/types/components/OverlayAbove.d.ts +11 -1
  388. package/lib/types/components/OverlayAbove.d.ts.map +1 -1
  389. package/lib/types/components/OverlayAbove.js +2 -12
  390. package/lib/types/components/PageMargin.d.ts.map +1 -1
  391. package/lib/types/components/PageMargin.js +3 -1
  392. package/lib/types/components/PluginSceneBoundary.d.ts +1 -1
  393. package/lib/types/components/PluginSceneBoundary.js +1 -1
  394. package/lib/types/components/PresetPicker.d.ts +4 -0
  395. package/lib/types/components/PresetPicker.d.ts.map +1 -1
  396. package/lib/types/components/PresetPicker.js +5 -1
  397. package/lib/types/components/PromptInput.d.ts +20 -22
  398. package/lib/types/components/PromptInput.d.ts.map +1 -1
  399. package/lib/types/components/PromptInput.js +425 -157
  400. package/lib/types/components/SearchBox.d.ts +22 -5
  401. package/lib/types/components/SearchBox.d.ts.map +1 -1
  402. package/lib/types/components/SearchBox.js +17 -13
  403. package/lib/types/components/StarPrompt.d.ts +6 -0
  404. package/lib/types/components/StarPrompt.d.ts.map +1 -1
  405. package/lib/types/components/StarPrompt.js +45 -0
  406. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  407. package/lib/types/components/SubagentCard.js +24 -9
  408. package/lib/types/components/SubagentDashboard.d.ts +18 -3
  409. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  410. package/lib/types/components/SubagentDashboard.js +53 -5
  411. package/lib/types/components/SubagentDetailScene.d.ts +7 -1
  412. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  413. package/lib/types/components/SubagentDetailScene.js +160 -13
  414. package/lib/types/components/SurfaceEdges.d.ts +21 -0
  415. package/lib/types/components/SurfaceEdges.d.ts.map +1 -0
  416. package/lib/types/components/SurfaceEdges.js +7 -0
  417. package/lib/types/components/ThemePicker.d.ts +9 -0
  418. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  419. package/lib/types/components/ThemePicker.js +74 -10
  420. package/lib/types/components/ThemePreviewPane.d.ts +26 -0
  421. package/lib/types/components/ThemePreviewPane.d.ts.map +1 -0
  422. package/lib/types/components/ThemePreviewPane.js +99 -0
  423. package/lib/types/components/WhaleGirl.d.ts +6 -0
  424. package/lib/types/components/WhaleGirl.d.ts.map +1 -1
  425. package/lib/types/components/WhaleGirl.js +18 -0
  426. package/lib/types/components/design-system/Divider.d.ts.map +1 -1
  427. package/lib/types/components/design-system/Divider.js +14 -9
  428. package/lib/types/components/launchpadActions.d.ts +73 -0
  429. package/lib/types/components/launchpadActions.d.ts.map +1 -0
  430. package/lib/types/components/launchpadActions.js +111 -0
  431. package/lib/types/components/launchpadLayout.d.ts +62 -0
  432. package/lib/types/components/launchpadLayout.d.ts.map +1 -0
  433. package/lib/types/components/launchpadLayout.js +176 -0
  434. package/lib/types/components/messages/AssistantThinkingMessage.js +4 -4
  435. package/lib/types/components/onboardingModel.d.ts +83 -0
  436. package/lib/types/components/onboardingModel.d.ts.map +1 -0
  437. package/lib/types/components/onboardingModel.js +128 -0
  438. package/lib/types/components/sidePanel/InfoPanel.d.ts +17 -0
  439. package/lib/types/components/sidePanel/InfoPanel.d.ts.map +1 -0
  440. package/lib/types/components/sidePanel/InfoPanel.js +124 -0
  441. package/lib/types/components/sidePanel/PanelBar.d.ts +45 -0
  442. package/lib/types/components/sidePanel/PanelBar.d.ts.map +1 -0
  443. package/lib/types/components/sidePanel/PanelBar.js +104 -0
  444. package/lib/types/components/sidePanel/PanelErrorBoundary.d.ts +21 -0
  445. package/lib/types/components/sidePanel/PanelErrorBoundary.d.ts.map +1 -0
  446. package/lib/types/components/sidePanel/PanelErrorBoundary.js +30 -0
  447. package/lib/types/components/sidePanel/PanelHost.d.ts +36 -0
  448. package/lib/types/components/sidePanel/PanelHost.d.ts.map +1 -0
  449. package/lib/types/components/sidePanel/PanelHost.js +81 -0
  450. package/lib/types/components/sidePanel/PanelPicker.d.ts +24 -0
  451. package/lib/types/components/sidePanel/PanelPicker.d.ts.map +1 -0
  452. package/lib/types/components/sidePanel/PanelPicker.js +45 -0
  453. package/lib/types/components/sidePanel/PanelStore.d.ts +55 -0
  454. package/lib/types/components/sidePanel/PanelStore.d.ts.map +1 -0
  455. package/lib/types/components/sidePanel/PanelStore.js +109 -0
  456. package/lib/types/components/sidePanel/SidePanelColumn.d.ts +31 -0
  457. package/lib/types/components/sidePanel/SidePanelColumn.d.ts.map +1 -0
  458. package/lib/types/components/sidePanel/SidePanelColumn.js +70 -0
  459. package/lib/types/components/sidePanel/SidePanelLayout.d.ts +54 -0
  460. package/lib/types/components/sidePanel/SidePanelLayout.d.ts.map +1 -0
  461. package/lib/types/components/sidePanel/SidePanelLayout.js +81 -0
  462. package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts +43 -0
  463. package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts.map +1 -0
  464. package/lib/types/components/sidePanel/SidePanelRuntimeContext.js +26 -0
  465. package/lib/types/components/sidePanel/TrajectoryPanel.d.ts +7 -0
  466. package/lib/types/components/sidePanel/TrajectoryPanel.d.ts.map +1 -0
  467. package/lib/types/components/sidePanel/TrajectoryPanel.js +263 -0
  468. package/lib/types/components/sidePanel/WorkspacePanel.d.ts +4 -0
  469. package/lib/types/components/sidePanel/WorkspacePanel.d.ts.map +1 -0
  470. package/lib/types/components/sidePanel/WorkspacePanel.js +171 -0
  471. package/lib/types/components/sidePanel/builtinPanels.d.ts +3 -0
  472. package/lib/types/components/sidePanel/builtinPanels.d.ts.map +1 -0
  473. package/lib/types/components/sidePanel/builtinPanels.js +229 -0
  474. package/lib/types/components/sidePanel/companion/CompanionPanel.d.ts +53 -0
  475. package/lib/types/components/sidePanel/companion/CompanionPanel.d.ts.map +1 -0
  476. package/lib/types/components/sidePanel/companion/CompanionPanel.js +528 -0
  477. package/lib/types/components/sidePanel/companion/SplashMascot.d.ts +32 -0
  478. package/lib/types/components/sidePanel/companion/SplashMascot.d.ts.map +1 -0
  479. package/lib/types/components/sidePanel/companion/SplashMascot.js +103 -0
  480. package/lib/types/components/sidePanel/companion/deepy.d.ts +75 -0
  481. package/lib/types/components/sidePanel/companion/deepy.d.ts.map +1 -0
  482. package/lib/types/components/sidePanel/companion/deepy.js +222 -0
  483. package/lib/types/components/sidePanel/companion/mood.d.ts +176 -0
  484. package/lib/types/components/sidePanel/companion/mood.d.ts.map +1 -0
  485. package/lib/types/components/sidePanel/companion/mood.js +212 -0
  486. package/lib/types/components/sidePanel/companion/pose.d.ts +43 -0
  487. package/lib/types/components/sidePanel/companion/pose.d.ts.map +1 -0
  488. package/lib/types/components/sidePanel/companion/pose.js +34 -0
  489. package/lib/types/components/sidePanel/companion/skins.d.ts +102 -0
  490. package/lib/types/components/sidePanel/companion/skins.d.ts.map +1 -0
  491. package/lib/types/components/sidePanel/companion/skins.js +408 -0
  492. package/lib/types/components/sidePanel/dimensions.d.ts +60 -0
  493. package/lib/types/components/sidePanel/dimensions.d.ts.map +1 -0
  494. package/lib/types/components/sidePanel/dimensions.js +75 -0
  495. package/lib/types/components/sidePanel/jobsFocusStore.d.ts +21 -0
  496. package/lib/types/components/sidePanel/jobsFocusStore.d.ts.map +1 -0
  497. package/lib/types/components/sidePanel/jobsFocusStore.js +30 -0
  498. package/lib/types/components/sidePanel/panelBridge.d.ts +44 -0
  499. package/lib/types/components/sidePanel/panelBridge.d.ts.map +1 -0
  500. package/lib/types/components/sidePanel/panelBridge.js +55 -0
  501. package/lib/types/components/sidePanel/pluginPanelAdapter.d.ts +22 -0
  502. package/lib/types/components/sidePanel/pluginPanelAdapter.d.ts.map +1 -0
  503. package/lib/types/components/sidePanel/pluginPanelAdapter.js +148 -0
  504. package/lib/types/components/sidePanel/types.d.ts +110 -0
  505. package/lib/types/components/sidePanel/types.d.ts.map +1 -0
  506. package/lib/types/components/sidePanel/types.js +1 -0
  507. package/lib/types/components/sidePanel/usePanelInput.d.ts +5 -0
  508. package/lib/types/components/sidePanel/usePanelInput.d.ts.map +1 -0
  509. package/lib/types/components/sidePanel/usePanelInput.js +44 -0
  510. package/lib/types/components/sidePanel/useSidePanel.d.ts +47 -0
  511. package/lib/types/components/sidePanel/useSidePanel.d.ts.map +1 -0
  512. package/lib/types/components/sidePanel/useSidePanel.js +284 -0
  513. package/lib/types/components/trajectory/MiniWake.d.ts +11 -1
  514. package/lib/types/components/trajectory/MiniWake.d.ts.map +1 -1
  515. package/lib/types/components/trajectory/MiniWake.js +9 -3
  516. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +3 -1
  517. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -1
  518. package/lib/types/components/workspaces/HomeWorkspaceRow.js +3 -2
  519. package/lib/types/deepseekPricing.d.ts +10 -9
  520. package/lib/types/deepseekPricing.d.ts.map +1 -1
  521. package/lib/types/deepseekPricing.js +18 -11
  522. package/lib/types/dsh-adapter/channel/attached-context.d.ts +58 -0
  523. package/lib/types/dsh-adapter/channel/attached-context.d.ts.map +1 -0
  524. package/lib/types/dsh-adapter/channel/attached-context.js +109 -0
  525. package/lib/types/dsh-adapter/channel/binding-events.d.ts +6 -0
  526. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  527. package/lib/types/dsh-adapter/channel/binding-events.js +43 -0
  528. package/lib/types/dsh-adapter/channel/capabilities.d.ts +82 -0
  529. package/lib/types/dsh-adapter/channel/capabilities.d.ts.map +1 -0
  530. package/lib/types/dsh-adapter/channel/capabilities.js +165 -0
  531. package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -1
  532. package/lib/types/dsh-adapter/channel/command-completions.js +8 -0
  533. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +4 -12
  534. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  535. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +9 -4
  536. package/lib/types/dsh-adapter/channel/external-commands.d.ts.map +1 -1
  537. package/lib/types/dsh-adapter/channel/external-commands.js +8 -2
  538. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +13 -2
  539. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  540. package/lib/types/dsh-adapter/channel/input-delivery.js +18 -1
  541. package/lib/types/dsh-adapter/channel/job-projection.d.ts +1 -0
  542. package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
  543. package/lib/types/dsh-adapter/channel/job-projection.js +88 -3
  544. package/lib/types/dsh-adapter/channel/mode-actions.d.ts +13 -3
  545. package/lib/types/dsh-adapter/channel/mode-actions.d.ts.map +1 -1
  546. package/lib/types/dsh-adapter/channel/mode-actions.js +55 -9
  547. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +8 -1
  548. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
  549. package/lib/types/dsh-adapter/channel/mode-permission-actions.js +25 -10
  550. package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
  551. package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
  552. package/lib/types/dsh-adapter/channel/preferences.js +26 -8
  553. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  554. package/lib/types/dsh-adapter/channel/projection.js +42 -37
  555. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  556. package/lib/types/dsh-adapter/channel/reports.js +13 -4
  557. package/lib/types/dsh-adapter/channel/session-reset.d.ts +5 -1
  558. package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -1
  559. package/lib/types/dsh-adapter/channel/session-reset.js +1 -0
  560. package/lib/types/dsh-adapter/channel/state.d.ts +21 -3
  561. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  562. package/lib/types/dsh-adapter/channel/state.js +3 -2
  563. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  564. package/lib/types/dsh-adapter/channel/subagent-projection.js +5 -0
  565. package/lib/types/dsh-adapter/channel/types.d.ts +1 -1
  566. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  567. package/lib/types/dsh-adapter/channel/usage.d.ts +18 -1
  568. package/lib/types/dsh-adapter/channel/usage.d.ts.map +1 -1
  569. package/lib/types/dsh-adapter/channel/usage.js +97 -2
  570. package/lib/types/dsh-adapter/channel.d.ts +1 -1
  571. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  572. package/lib/types/dsh-adapter/channel.js +72 -4
  573. package/lib/types/dsh-adapter/context-occupancy.d.ts +204 -0
  574. package/lib/types/dsh-adapter/context-occupancy.d.ts.map +1 -0
  575. package/lib/types/dsh-adapter/context-occupancy.js +299 -0
  576. package/lib/types/dsh-adapter/contract.d.ts +19 -4
  577. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  578. package/lib/types/dsh-adapter/contract.js +33 -3
  579. package/lib/types/dsh-adapter/index.d.ts +32 -0
  580. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  581. package/lib/types/dsh-adapter/index.js +20 -1
  582. package/lib/types/dsh-adapter/jobs.d.ts +82 -16
  583. package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
  584. package/lib/types/dsh-adapter/jobs.js +104 -9
  585. package/lib/types/dsh-adapter/migrate/sessionize.d.ts.map +1 -1
  586. package/lib/types/dsh-adapter/migrate/sessionize.js +11 -12
  587. package/lib/types/dsh-adapter/oauth/bonus.d.ts +27 -0
  588. package/lib/types/dsh-adapter/oauth/bonus.d.ts.map +1 -0
  589. package/lib/types/dsh-adapter/oauth/bonus.js +69 -0
  590. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.d.ts +2 -1
  591. package/lib/types/dsh-adapter/oauth/command.d.ts.map +1 -0
  592. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/command.js +7 -4
  593. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.d.ts +13 -10
  594. package/lib/types/dsh-adapter/oauth/credentials.d.ts.map +1 -0
  595. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/credentials.js +61 -23
  596. package/lib/types/dsh-adapter/oauth/deepseek.d.ts +39 -0
  597. package/lib/types/dsh-adapter/oauth/deepseek.d.ts.map +1 -0
  598. package/lib/types/dsh-adapter/oauth/deepseek.js +189 -0
  599. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.d.ts +24 -16
  600. package/lib/types/dsh-adapter/oauth/index.d.ts.map +1 -0
  601. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/index.js +34 -20
  602. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/interaction.d.ts +14 -12
  603. package/lib/types/dsh-adapter/oauth/interaction.d.ts.map +1 -0
  604. package/lib/types/dsh-adapter/oauth/interaction.js +283 -0
  605. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.d.ts +2 -1
  606. package/lib/types/dsh-adapter/oauth/opener.d.ts.map +1 -0
  607. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/opener.js +1 -1
  608. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.d.ts +10 -3
  609. package/lib/types/dsh-adapter/oauth/pi-ai.d.ts.map +1 -0
  610. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/pi-ai.js +8 -3
  611. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.d.ts +6 -3
  612. package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -0
  613. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/profiles.js +11 -3
  614. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.d.ts +14 -7
  615. package/lib/types/dsh-adapter/oauth/service.d.ts.map +1 -0
  616. package/{node_modules/@deepseek-harness-tui/dsh-auth/lib → lib/types/dsh-adapter/oauth}/service.js +82 -17
  617. package/lib/types/dsh-adapter/panel-runtime-registry.d.ts +39 -0
  618. package/lib/types/dsh-adapter/panel-runtime-registry.d.ts.map +1 -0
  619. package/lib/types/dsh-adapter/panel-runtime-registry.js +84 -0
  620. package/lib/types/dsh-adapter/panels.d.ts +251 -0
  621. package/lib/types/dsh-adapter/panels.d.ts.map +1 -0
  622. package/lib/types/dsh-adapter/panels.js +407 -0
  623. package/lib/types/dsh-adapter/plugin-host.js +1 -1
  624. package/lib/types/dsh-adapter/plugin.d.ts +25 -2
  625. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  626. package/lib/types/dsh-adapter/plugin.js +241 -44
  627. package/lib/types/dsh-adapter/presets.d.ts +26 -4
  628. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  629. package/lib/types/dsh-adapter/presets.js +29 -5
  630. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  631. package/lib/types/dsh-adapter/providerWizard.js +6 -2
  632. package/lib/types/dsh-adapter/startup-args.d.ts +8 -0
  633. package/lib/types/dsh-adapter/startup-args.d.ts.map +1 -0
  634. package/lib/types/dsh-adapter/startup-args.js +34 -0
  635. package/lib/types/dsh-adapter/subagents.d.ts +1 -0
  636. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  637. package/lib/types/dsh-adapter/subagents.js +10 -1
  638. package/lib/types/history.d.ts +24 -7
  639. package/lib/types/history.d.ts.map +1 -1
  640. package/lib/types/history.js +77 -18
  641. package/lib/types/i18n.d.ts +908 -33
  642. package/lib/types/i18n.d.ts.map +1 -1
  643. package/lib/types/i18n.js +355 -17
  644. package/lib/types/ink/components/App.d.ts +14 -1
  645. package/lib/types/ink/components/App.d.ts.map +1 -1
  646. package/lib/types/ink/components/App.js +71 -61
  647. package/lib/types/ink/components/TerminalSizeContext.d.ts +7 -0
  648. package/lib/types/ink/components/TerminalSizeContext.d.ts.map +1 -1
  649. package/lib/types/ink/hooks/use-input.d.ts +11 -0
  650. package/lib/types/ink/hooks/use-input.d.ts.map +1 -1
  651. package/lib/types/ink/hooks/use-input.js +8 -2
  652. package/lib/types/ink/hooks/use-terminal-viewport.d.ts.map +1 -1
  653. package/lib/types/ink/hooks/use-terminal-viewport.js +3 -1
  654. package/lib/types/ink/ink.d.ts +18 -0
  655. package/lib/types/ink/ink.d.ts.map +1 -1
  656. package/lib/types/ink/ink.js +51 -8
  657. package/lib/types/ink/output.d.ts +10 -1
  658. package/lib/types/ink/output.d.ts.map +1 -1
  659. package/lib/types/ink/output.js +3 -3
  660. package/lib/types/ink/parse-keypress.d.ts +71 -3
  661. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  662. package/lib/types/ink/parse-keypress.js +815 -133
  663. package/lib/types/ink/render-node-to-output.d.ts +1 -1
  664. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  665. package/lib/types/ink/render-node-to-output.js +37 -5
  666. package/lib/types/ink/screen.d.ts +26 -1
  667. package/lib/types/ink/screen.d.ts.map +1 -1
  668. package/lib/types/ink/screen.js +96 -1
  669. package/lib/types/ink/selection.d.ts +23 -1
  670. package/lib/types/ink/selection.d.ts.map +1 -1
  671. package/lib/types/ink/selection.js +102 -26
  672. package/lib/types/ink/sixel-codec.d.ts +11 -0
  673. package/lib/types/ink/sixel-codec.d.ts.map +1 -1
  674. package/lib/types/ink/sixel-codec.js +42 -8
  675. package/lib/types/ink/terminal-querier.d.ts +25 -0
  676. package/lib/types/ink/terminal-querier.d.ts.map +1 -1
  677. package/lib/types/ink/terminal-querier.js +42 -0
  678. package/lib/types/ink/update-overflow-guard.d.ts +24 -0
  679. package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
  680. package/lib/types/ink/update-overflow-guard.js +25 -3
  681. package/lib/types/math/renderer.d.ts +2 -0
  682. package/lib/types/math/renderer.d.ts.map +1 -1
  683. package/lib/types/math/renderer.js +4 -0
  684. package/lib/types/minimalUiMode.d.ts +19 -0
  685. package/lib/types/minimalUiMode.d.ts.map +1 -0
  686. package/lib/types/minimalUiMode.js +23 -0
  687. package/lib/types/oauth.d.ts +5 -10
  688. package/lib/types/oauth.d.ts.map +1 -1
  689. package/lib/types/oauth.js +5 -10
  690. package/lib/types/onboardingPrefs.d.ts +52 -0
  691. package/lib/types/onboardingPrefs.d.ts.map +1 -0
  692. package/lib/types/onboardingPrefs.js +107 -0
  693. package/lib/types/panels.d.ts +4 -0
  694. package/lib/types/panels.d.ts.map +1 -0
  695. package/lib/types/panels.js +4 -0
  696. package/lib/types/screens/Chat.d.ts +25 -1
  697. package/lib/types/screens/Chat.d.ts.map +1 -1
  698. package/lib/types/screens/Chat.js +1149 -302
  699. package/lib/types/screens/Launchpad.d.ts +194 -0
  700. package/lib/types/screens/Launchpad.d.ts.map +1 -0
  701. package/lib/types/screens/Launchpad.js +607 -0
  702. package/lib/types/screens/Onboarding.d.ts +37 -0
  703. package/lib/types/screens/Onboarding.d.ts.map +1 -0
  704. package/lib/types/screens/Onboarding.js +492 -0
  705. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
  706. package/lib/types/screens/SessionSupervisor.js +18 -12
  707. package/lib/types/screens/StatusLine.d.ts +4 -0
  708. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  709. package/lib/types/screens/StatusLine.js +43 -25
  710. package/lib/types/screens/StatusMetrics.d.ts +22 -0
  711. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  712. package/lib/types/screens/StatusMetrics.js +20 -0
  713. package/lib/types/screens/chatOverlay.d.ts +18 -1
  714. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  715. package/lib/types/screens/chatOverlay.js +3 -0
  716. package/lib/types/screens/sessionSupervisor/model.d.ts +1 -2
  717. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -1
  718. package/lib/types/screens/sessionSupervisor/model.js +4 -2
  719. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
  720. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +7 -3
  721. package/lib/types/settings/definitions.d.ts +93 -0
  722. package/lib/types/settings/definitions.d.ts.map +1 -1
  723. package/lib/types/settings/definitions.js +87 -4
  724. package/lib/types/tips.d.ts.map +1 -1
  725. package/lib/types/tips.js +13 -7
  726. package/lib/types/tuiDisplayPrefs.d.ts +42 -2
  727. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  728. package/lib/types/tuiDisplayPrefs.js +74 -0
  729. package/lib/types/update.d.ts +53 -9
  730. package/lib/types/update.d.ts.map +1 -1
  731. package/lib/types/update.js +103 -46
  732. package/lib/types/utils/bmp.d.ts.map +1 -1
  733. package/lib/types/utils/bmp.js +8 -1
  734. package/lib/types/utils/clipboard.d.ts.map +1 -1
  735. package/lib/types/utils/clipboard.js +25 -6
  736. package/lib/types/utils/credentials.d.ts.map +1 -1
  737. package/lib/types/utils/credentials.js +5 -1
  738. package/lib/types/utils/draftWordBoundary.d.ts +23 -0
  739. package/lib/types/utils/draftWordBoundary.d.ts.map +1 -0
  740. package/lib/types/utils/draftWordBoundary.js +161 -0
  741. package/lib/types/utils/fileTarget.d.ts.map +1 -1
  742. package/lib/types/utils/fileTarget.js +7 -9
  743. package/lib/types/utils/inputPaste.d.ts +62 -0
  744. package/lib/types/utils/inputPaste.d.ts.map +1 -0
  745. package/lib/types/utils/inputPaste.js +111 -0
  746. package/lib/types/utils/intl.d.ts +8 -0
  747. package/lib/types/utils/intl.d.ts.map +1 -1
  748. package/lib/types/utils/intl.js +11 -0
  749. package/lib/types/utils/keymap.d.ts +13 -3
  750. package/lib/types/utils/keymap.d.ts.map +1 -1
  751. package/lib/types/utils/keymap.js +27 -5
  752. package/lib/types/utils/pastedImagePath.d.ts +8 -0
  753. package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
  754. package/lib/types/utils/pastedImagePath.js +29 -5
  755. package/package.json +128 -106
  756. package/presets/liangshen/agent.cordis.yml +4 -1
  757. package/presets/liangshen/preset.yml +1 -1
  758. package/{dsh-ecosystem-spec → tui-profile}/registry/README.md +1 -1
  759. package/{dsh-ecosystem-spec → tui-profile}/registry/registry-0.15.json +1 -1
  760. package/lib/types/minimalMode.d.ts +0 -13
  761. package/lib/types/minimalMode.d.ts.map +0 -1
  762. package/lib/types/minimalMode.js +0 -17
  763. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +0 -21
  764. package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +0 -143
  765. package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +0 -11
  766. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +0 -44
  767. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +0 -193
  768. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +0 -69
  769. /package/{dsh-ecosystem-spec → tui-profile}/protocols/profile-definitions.d.ts +0 -0
  770. /package/{dsh-ecosystem-spec → tui-profile}/protocols/profile-definitions.js +0 -0
  771. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel-http.d.ts +0 -0
  772. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel-http.js +0 -0
  773. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel.d.ts +0 -0
  774. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel.js +0 -0
  775. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-contributions.d.ts +0 -0
  776. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-contributions.js +0 -0
  777. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/decision-events-v1alpha1.json +0 -0
  778. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/scene-v1alpha1.json +0 -0
  779. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/settings-section-v1alpha1.json +0 -0
  780. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/tui-channel-v1alpha1.json +0 -0
  781. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/workspace-provider-v1alpha1.json +0 -0
  782. /package/{dsh-ecosystem-spec → tui-profile}/registry/host-descriptor.tui.example.json +0 -0
  783. /package/{dsh-ecosystem-spec → tui-profile}/registry/permissions-0.1.json +0 -0
  784. /package/{dsh-ecosystem-spec → tui-profile}/schemas/conformance-claim.schema.json +0 -0
  785. /package/{dsh-ecosystem-spec → tui-profile}/schemas/effect-ledger-record.schema.json +0 -0
  786. /package/{dsh-ecosystem-spec → tui-profile}/schemas/host-descriptor.schema.json +0 -0
@@ -1,6 +1,8 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import React from 'react';
3
3
  import { t, getLang, setLang, isLang, writeLangPref, readLangPref, subscribeLang, LANGS } from '../i18n.js';
4
+ import { checkForTuiUpdate, installedTuiVersion } from '../update.js';
5
+ import { installedKernelVersion } from '../dsh-adapter/contract.js';
4
6
  import { readThemePref } from '../themePrefs.js';
5
7
  import { readPresetPref } from '../presetPrefs.js';
6
8
  import { readModelPref } from '../modelPrefs.js';
@@ -43,8 +45,8 @@ import { useSelection } from '../ink/hooks/use-selection.js';
43
45
  import { NoSelect } from '../ink/components/NoSelect.js';
44
46
  import { LogoHeader, MessageList } from '../components/MessageList.js';
45
47
  import { splashFontIdOf } from '../components/splashFonts.js';
46
- import { StarPrompt } from '../components/StarPrompt.js';
47
- import { dueStarModal, markStarAsked, STAR_MILESTONES } from '../usageStats.js';
48
+ import { StarPrompt, WhaleCouponPrompt } from '../components/StarPrompt.js';
49
+ import { dueStarModal, markStarAsked, pendingStarMilestone, readUsage, STAR_MILESTONES } from '../usageStats.js';
48
50
  import { TimelineRail } from '../components/TimelineRail.js';
49
51
  import { ScrollbarGutter } from '../components/ScrollbarGutter.js';
50
52
  import { normalizeScrollGutter } from '../tuiDisplayPrefs.js';
@@ -53,15 +55,22 @@ import { TooltipLayer } from '../components/Tooltip.js';
53
55
  import { PromptInput } from '../components/PromptInput.js';
54
56
  import { PromptEditorLayer, usePromptEditorOpen } from '../components/PromptEditor.js';
55
57
  import { GoalTodoPanel } from '../components/GoalTodoPanel.js';
58
+ import { useSidePanel } from '../components/sidePanel/useSidePanel.js';
59
+ import { jobsFocusStore } from '../components/sidePanel/jobsFocusStore.js';
60
+ import { SidePanelLayout } from '../components/sidePanel/SidePanelLayout.js';
61
+ import { SidePanelColumn } from '../components/sidePanel/SidePanelColumn.js';
62
+ import { PanelPicker, usePanelPickerRows } from '../components/sidePanel/PanelPicker.js';
56
63
  import { AutoRecapRow } from '../components/AutoRecapRow.js';
57
64
  import { CompactionStatusRow } from '../components/CompactionStatusRow.js';
58
65
  import { BalanceReportRow } from '../components/BalanceReportRow.js';
59
66
  import { estimateSessionCostSnapshotCny } from '../deepseekPricing.js';
60
67
  import { LoadedContextPanel } from '../components/LoadedContextPanel.js';
61
68
  import { StatusLine } from './StatusLine.js';
69
+ import { channelContextOccupancy } from './StatusMetrics.js';
62
70
  import { WorkingSpinner, useThinkingStatus } from '../components/WorkingSpinner.js';
63
71
  import { ActivityLine, contextPressurePct } from '../components/ActivityLine.js';
64
72
  import { ModelPicker } from '../components/ModelPicker.js';
73
+ import { HelpMenu } from '../components/HelpMenu.js';
65
74
  import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
66
75
  import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
67
76
  import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
@@ -108,6 +117,12 @@ import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
108
117
  import { useExternalVersion } from '../hooks/useExternalVersion.js';
109
118
  import { TrajectoryScene } from './TrajectoryScene.js';
110
119
  import { markHomeSeen } from '../homePrefs.js';
120
+ import { markOnboardingDone } from '../onboardingPrefs.js';
121
+ import { Launchpad, launchpadVisible } from './Launchpad.js';
122
+ import { resolveLaunchpadActions } from '../components/launchpadActions.js';
123
+ import { Onboarding } from './Onboarding.js';
124
+ import { appendHistory } from '../history.js';
125
+ import { isHiddenCommandName, isLocalCommandName, parseCommandName } from '../commands.js';
111
126
  import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
112
127
  import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
113
128
  import { readTrajectorySeen, writeTrajectorySeen } from '../trajectoryPrefs.js';
@@ -140,6 +155,21 @@ const COMMAND_RESULT_CELLS = 200;
140
155
  * file, but a healthy import of thousands of conversations finishes well
141
156
  * inside this; without a cap a wedged child would hang the loop forever. */
142
157
  const MIGRATE_CHILD_TIMEOUT_MS = 30 * 60 * 1000;
158
+ /**
159
+ * 落地页参数行四段能点开的既有选择器(第五版):模型 → /model、思考深度 →
160
+ * /effort、模式 → /plan、权限预设 → /permission。盖在落地页之上时键盘归
161
+ * 选择器(见 useInput 的 launchpad 分支注释);其余 overlay 类型不在此列,
162
+ * 落地页期间照旧整块让位。
163
+ */
164
+ const LAUNCHPAD_OVERLAY_KINDS = new Set([
165
+ 'model', 'effort', 'plan', 'preset', 'permission',
166
+ // 第七版:左下角工作目录铭牌点开的工作区菜单(及其二级选择器/流程层)
167
+ // 也是「盖在落地页之上」的姿态——同一套 pickerPanels 挂载,Esc 回落地页。
168
+ 'workspace-menu', 'workspace-picker', 'workspace-flow',
169
+ // 第八版:帮助入口也走「盖在落地页之上」的浮层姿态(overlay kind 'help',
170
+ // HelpMenu 经 pickerPanels 挂进 OverlayAbove)——不再收掉落地页进对话页。
171
+ 'help',
172
+ ]);
143
173
  function cleanCommandError(error) {
144
174
  try {
145
175
  if (error instanceof Error) {
@@ -178,6 +208,27 @@ const NO_ROWS = [];
178
208
  function capitalize(text) {
179
209
  return text.length === 0 ? text : text[0].toUpperCase() + text.slice(1);
180
210
  }
211
+ /**
212
+ * The occupancy line `/tokens` and `/status` print.
213
+ *
214
+ * Occupancy has ONE source (see `dsh-adapter/context-occupancy.ts`): this is
215
+ * the same reading the footer's ctx field, the segmented bar and the
216
+ * context-low warning use — never the session's cumulative uncached input,
217
+ * which is a different quantity by orders of magnitude.
218
+ * @param channel - Live channel surface.
219
+ * @returns The localized line, or `undefined` when no window is known.
220
+ */
221
+ function contextOccupancyLine(channel) {
222
+ const occupancy = channelContextOccupancy(channel);
223
+ if (occupancy === undefined || occupancy.contextWindow === undefined || occupancy.contextWindow <= 0)
224
+ return undefined;
225
+ const percent = Math.max(0, Math.min(100, Math.round((occupancy.usedTokens / occupancy.contextWindow) * 100)));
226
+ return t('context-occupancy', {
227
+ percent,
228
+ used: formatTokens(occupancy.usedTokens),
229
+ window: formatTokens(occupancy.contextWindow),
230
+ });
231
+ }
181
232
  /** Terminal-title spinner frames. */
182
233
  const TITLE_SPINNER_FRAMES = ['⠂', '⠐'];
183
234
  /** Searchable transcript text for one row (`/` incsearch):
@@ -223,12 +274,14 @@ let fallbackDialogStore;
223
274
  let fallbackStatusStore;
224
275
  /** Standalone mounts (tests, bare embeds) without the composition root's store. */
225
276
  let fallbackActivityStore;
277
+ const noCouponSubscription = () => () => undefined;
278
+ const noCouponSnapshot = () => null;
226
279
  /** Identity of one caret-preview dismissal: the token (its title) on the
227
280
  * image, so the same image staged twice is dismissed per token. */
228
281
  function peekKey(image, title) {
229
282
  return `${title ?? ''} ${image.id}`;
230
283
  }
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, }) {
284
+ export function Chat({ channel, questionStore, approvalStore, extensionDialogs, bonusNotices, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, launchpadOnBoot, onboardingOnBoot, starPrompt, }) {
232
285
  const writeRaw = React.useContext(TerminalWriteContext);
233
286
  // Re-render whenever the channel mutates; rows/status are read fresh below.
234
287
  // DEFAULT lane on purpose (useExternalVersion): the channel version bumps
@@ -258,6 +311,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
258
311
  // extensions row share one inert store that never holds a dialog.
259
312
  const dialogs = extensionDialogs ?? (fallbackDialogStore ??= new TuiDialogStore());
260
313
  const dialogSnapshot = React.useSyncExternalStore(listener => dialogs.subscribe(listener), () => dialogs.getSnapshot());
314
+ const coupon = React.useSyncExternalStore(bonusNotices?.subscribe ?? noCouponSubscription, bonusNotices?.getSnapshot ?? noCouponSnapshot);
261
315
  // Plugin status contributions: text keys join into one line; bounded rich
262
316
  // views keep their own rows immediately above the prompt.
263
317
  const statusContributions = extensionStatus ?? (fallbackStatusStore ??= new TuiStatusStore());
@@ -405,7 +459,111 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
405
459
  * on" has not been answered yet. Every later launch starts on the chat
406
460
  * screen, and the screen stays reachable.
407
461
  */
408
- const [supervisorOpen, setSupervisorOpen] = React.useState(openHomeOnBoot === true);
462
+ // 第七版:启动页在开时**不再**预开会话浏览器。旧姿态是「先收落地页再开
463
+ // 整屏」,浏览器必须提前藏在下面;现在整屏(会话/设置/任务面板)盖在
464
+ // 落地页**之上**、Esc 退回落地页,按需打开即可——boot 时同时为真反而会
465
+ // 让浏览器盖住落地页(渲染顺序见各 early-return)。
466
+ const [supervisorOpen, setSupervisorOpen] = React.useState(openHomeOnBoot === true && launchpadOnBoot !== true);
467
+ /**
468
+ * The launchpad: the landing page every ordinary launch starts on.
469
+ *
470
+ * Seeded from `launchpadOnBoot`, and the screen that closes it is the one
471
+ * that decides what comes next — a submitted line hands over to the chat
472
+ * screen, an action hands over to whatever surface that action opens.
473
+ */
474
+ const [launchpadOpen, setLaunchpadOpen] = React.useState(launchpadOnBoot === true);
475
+ /**
476
+ * The launchpad's draft. It lives HERE, not inside the screen, because the
477
+ * screen unmounts the moment the user submits: a draft owned by an
478
+ * unmounting component would be lost in exactly the transition it exists
479
+ * to carry.
480
+ */
481
+ const [launchpadDraft, setLaunchpadDraft] = React.useState('');
482
+ const [launchpadCaret, setLaunchpadCaret] = React.useState(0);
483
+ const [launchpadFocus, setLaunchpadFocus] = React.useState(-1);
484
+ /**
485
+ * 「整屏盖启动页」的显式授权(第七版防御位,用户实测回归:启动页一闪而过
486
+ * 被顶掉)。整屏分支排在落地页**之前**,任何一处状态在开机后被异步置真
487
+ * (杂散输入/宿主事件/未来新代码)都会把落地页挤掉。授权位只有**从落地页
488
+ * 出发的交互**(入口行/空输入 Esc/目录铭牌/命令面板里的整屏命令、Continue
489
+ * 的兜底浏览器)才置真;覆盖屏全部收起时自动落 false。开机后即便某个整屏
490
+ * 状态被误置真,落地页仍在最上层——「浮层可以盖、整屏必须经授权」。
491
+ */
492
+ const launchpadCoverRef = React.useRef(false);
493
+ /**
494
+ * 这一帧到底出不出落地页:状态开着还不够,minimal 模式(`dsh-tui.minimal`)
495
+ * 下它整块不存在——`minimalMode.ts` 的标志由 channel 在设置落地后写入,
496
+ * 建 state 时读不到,所以判定必须放在**渲染期**读(与其它 minimal 门同一口径)。
497
+ * 键盘守卫、渲染分支与"有没有整屏界面"的判定都认这一个值,三者不会分叉。
498
+ */
499
+ /**
500
+ * 右下角铭牌的版本号:`installedTuiVersion()` 每次都要读一遍 package.json,
501
+ * 而它在一个进程里不会变——mount 时读一次就够。
502
+ */
503
+ const tuiVersion = React.useMemo(() => installedTuiVersion(), []);
504
+ /**
505
+ * 内核(dsh)版本(第七版:落地页右下角双版本铭牌)。真实来源见
506
+ * `contract.installedKernelVersion`(宿主 CLI 的 manifest,回落内核线包);
507
+ * 两级都读不到 = undefined → 铭牌只画 TUI 段,绝不编造。
508
+ */
509
+ const kernelVersion = React.useMemo(() => installedKernelVersion(), []);
510
+ const launchpadShown = launchpadOpen && launchpadVisible();
511
+ /**
512
+ * 落地页之上是否盖着一个参数选择器(第五版:参数行四段各自可点,点开的
513
+ * 是聊天页同一套 /model · /effort · /plan · /permission overlay)。这时
514
+ * 键盘归选择器(Esc 关它回到落地页),渲染也要把 pickerPanels 带进
515
+ * 落地页分支——不然整屏 early-return 把 overlay 吞了,"点了没反应"。
516
+ */
517
+ const launchpadOverlayUp = launchpadShown && LAUNCHPAD_OVERLAY_KINDS.has(overlay.kind);
518
+ /**
519
+ * 渲染在**覆盖层**(而不是整屏 early-return)的那几个命令。
520
+ *
521
+ * 落地页的快捷入口与向导招式卡的「试一下」都走 `runCommand`,而 supervisor /
522
+ * settings / help 的 early-return 排在两个界面**之后**:不收掉当前界面就是
523
+ * "点了没反应",状态还滞留着、等界面关掉才突然弹出来。默认收,白名单只留给覆盖层。
524
+ */
525
+ const overlayCommandNames = React.useMemo(() => new Set(['model', 'effort', 'plan', 'preset', 'permission']), []);
526
+ /**
527
+ * 打开**整屏界面**的命令(第七版):从落地页触发这些命令时**不收掉落地页**
528
+ * ——整屏盖在落地页之上渲染(它们的 early-return 排在落地页分支之前),
529
+ * Esc 退出整屏回到落地页(草稿/参数/焦点原样保留)。这是「从启动页进入
530
+ * 对话页的唯一路径 = Enter 提交一条非命令消息」的落地:任何返回键都不再
531
+ * 把人甩到对话页。其余命令(star / update / help / 转录输出类)的反馈在
532
+ * 对话页,仍按旧约收掉落地页再执行。
533
+ */
534
+ const launchpadScreenCommands = React.useMemo(() => new Set(['home', 'resume', 'agentview', 'settings', 'jobs', 'tree', 'agents', 'setup', 'bg', 'background']), []);
535
+ /**
536
+ * 落地页条件位②(有新版本):`checkForTuiUpdate()`(与 /update 同一条
537
+ * 判定,src/update.ts)在启动页第一次挂起时后台探一次——registry 延迟
538
+ * 不许拖慢第一帧;失败/离线静默为 false(绝不放假按钮)。
539
+ */
540
+ const [launchpadUpdateAvailable, setLaunchpadUpdateAvailable] = React.useState(false);
541
+ const launchpadUpdateProbedRef = React.useRef(false);
542
+ React.useEffect(() => {
543
+ if (!launchpadShown || launchpadUpdateProbedRef.current)
544
+ return;
545
+ launchpadUpdateProbedRef.current = true;
546
+ void checkForTuiUpdate().then(update => {
547
+ setLaunchpadUpdateAvailable(update !== undefined);
548
+ }).catch(() => undefined);
549
+ }, [launchpadShown]);
550
+ /**
551
+ * 落地页条件位③(投喂一颗 Star):与开屏求 star 弹窗**同一口径**——
552
+ * `usageStats`(~/.dsh-tui/usage.json)里有未报过的已达档里程碑
553
+ * (`pendingStarMilestone`,首档 24h)且本进程尚未 star 成功。star 成功
554
+ * 后(`starred` 翻真)按钮立即消失;记账过的档不再纠缠。
555
+ */
556
+ /**
557
+ * The first-run guide. Renders above the launchpad (see the prop docs): a
558
+ * launch that needs setup has not answered the launchpad's question yet.
559
+ */
560
+ const [onboardingOpen, setOnboardingOpen] = React.useState(onboardingOnBoot === true);
561
+ /**
562
+ * 落地页那条"第一次用?跑一遍引导"的横幅认的是**还欠一次引导**,而不是启动快照:
563
+ * 完成(写进 onboarding.json)之后立刻收掉;跳过刻意保留——没记账,下次启动还会问,
564
+ * 横幅说的正是这件事。
565
+ */
566
+ const [onboardingPending, setOnboardingPending] = React.useState(onboardingOnBoot === true);
409
567
  /** `/tree` opens the session family tree (pi's Session Tree): every rewind
410
568
  * fork stitched back onto the message it diverged from, hover previews,
411
569
  * and per-node rewind/fork/adopt actions. Like the supervisor, a screen. */
@@ -445,6 +603,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
445
603
  * `null` 显式关闭,传 actions 覆写两个按钮(不跑真 gh、不开真浏览器)。 */
446
604
  /** 本次会话是否已经 star 成功(开屏彩蛋标题切「捡到小星星啦」)。 */
447
605
  const [starred, setStarred] = React.useState(false);
606
+ /**
607
+ * 落地页条件位③(投喂一颗 Star):与开屏求 star 弹窗**同一口径**——
608
+ * `usageStats`(~/.dsh-tui/usage.json)里有未报过的已达档里程碑
609
+ * (`pendingStarMilestone`,首档 24h)且本进程尚未 star 成功。star 成功
610
+ * 后(`starred` 翻真)按钮立即消失;记账过的档不再纠缠。readUsage 是
611
+ * 文件读,只在 starred 翻真或测试缝变化时重算,不逐帧读盘。
612
+ */
613
+ const launchpadStarDue = React.useMemo(() => !starred && pendingStarMilestone(readUsage(starPrompt?.dir)) !== null,
614
+ // eslint-disable-next-line react-hooks/exhaustive-deps -- dir 经测试缝注入
615
+ [starred, starPrompt]);
448
616
  const [starModal, setStarModal] = React.useState(null);
449
617
  // 单发闩:只在第一个"安静的开屏视口"上武装定时器。700ms 窗口内整屏
450
618
  // 界面打开 → cleanup 掐掉定时器且**不再重臂**(记账只发生在回调里,
@@ -459,7 +627,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
459
627
  return;
460
628
  if (starPrompt === null)
461
629
  return;
462
- if (supervisorOpen || treeOpen || settingsOpen || channel.working)
630
+ if (supervisorOpen || treeOpen || settingsOpen || launchpadShown || onboardingOpen || channel.working)
463
631
  return;
464
632
  starModalArmedRef.current = true;
465
633
  // 让开屏先画半秒:弹窗压在介绍动画之上,而不是同抢第一帧。
@@ -481,7 +649,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
481
649
  }, 700);
482
650
  return () => { clearTimeout(timer); };
483
651
  // eslint-disable-next-line react-hooks/exhaustive-deps -- 只在整屏界面开合时重判;闩保证只武装一次
484
- }, [supervisorOpen, treeOpen, settingsOpen]);
652
+ }, [supervisorOpen, treeOpen, settingsOpen, launchpadShown, onboardingOpen]);
485
653
  /** `/star` 命令、开屏标语的点击/`Alt+S` 共用的一键动作:异步跑 gh,界面
486
654
  * 全程不阻塞,结果回来按四类各报一句(成功 / 没装 gh / 没登录 / 失败)。
487
655
  * `starPrompt.onStar` 存在时走同一条测试缝(夹具因此不会真的去 star)。 */
@@ -590,10 +758,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
590
758
  const workspaceFlowAbortRef = React.useRef(null);
591
759
  /** `/preset` agent-preset roster (issue #8): loads async, persists. */
592
760
  const [presetOptions, setPresetOptions] = React.useState([]);
761
+ /**
762
+ * 落地页参数行的模式段(第六版设计 1)显示 preset 的**显示名**
763
+ * (Standard/PTC/极简…),名册是异步的——落地页出来时顺手预热一次
764
+ * (空名册不写;失败静默,段缺省不画)。/preset 自己的加载路径不动。
765
+ */
766
+ React.useEffect(() => {
767
+ if (!launchpadShown || presetOptions.length > 0)
768
+ return;
769
+ let cancelled = false;
770
+ channel.listPresets()
771
+ .then(list => { if (!cancelled && list.length > 0)
772
+ setPresetOptions(list); })
773
+ .catch(() => { });
774
+ return () => { cancelled = true; };
775
+ }, [launchpadShown, presetOptions.length, channel]);
593
776
  /** `/effort` adapter levels: load async before the slider opens. */
594
777
  const [effortOptions, setEffortOptions] = React.useState([]);
595
778
  const [themeName, setTheme] = useTheme();
596
779
  const { rows: terminalRows } = useTerminalSize();
780
+ /**
781
+ * 帮助盖屏(第八版,overlay kind 'help')的滚动视口:HelpMenu 自带
782
+ * ScrollBox,键盘(↑/↓/PgUp/PgDn/Home/End)由 Chat 的 overlay 分支驱动。
783
+ * 视口预算与 PromptInput 的 helpViewportHeight 同式(PR #446 的口径)。
784
+ */
785
+ const helpCoverScrollRef = React.useRef(null);
786
+ const helpCoverViewportHeight = Math.max(3, Math.min(terminalRows - 7, 15));
597
787
  const [showAllMessages, setShowAllMessages] = React.useState(false);
598
788
  /** Scope the fold to this question: an aborted ask can promote its queued
599
789
  * successor without ever publishing an idle (null) snapshot. */
@@ -769,9 +959,40 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
769
959
  /** Subagent dashboard (Ctrl+A): displays active/completed subagents. */
770
960
  const [subagentDashboardOpen, setSubagentDashboardOpen] = React.useState(false);
771
961
  const [jobsPanelOpen, setJobsPanelOpen] = React.useState(false);
962
+ /** Job id the panel should focus on open: set by a transcript card click
963
+ * (open the panel AT that job), cleared on close so the keyboard/command
964
+ * path reopens at the top. */
965
+ const [jobsPanelFocusId, setJobsPanelFocusId] = React.useState(null);
966
+ // Side-panel routing needs the controller, which is created further down;
967
+ // a ref keeps this identity-stable callback fresh anyway.
968
+ const sidePanelRef = React.useRef(null);
772
969
  // MessageList forwards these open handlers to every memoized row. Their
773
970
  // identities must survive token/metrics updates, including for tool rows.
774
- const openJobsPanel = React.useCallback(() => setJobsPanelOpen(true), []);
971
+ const openJobsPanel = React.useCallback((focusId) => {
972
+ const sidePanel = sidePanelRef.current;
973
+ // Split mode: open the jobs side panel at the requested job (the focus
974
+ // lane carries the id; a fresh nonce refocuses even for the same id).
975
+ if (sidePanel !== null && sidePanel.split && sidePanel.enabledPanelIds.includes('jobs')) {
976
+ if (typeof focusId === 'string' && focusId !== '')
977
+ jobsFocusStore.request(focusId);
978
+ sidePanel.openPanel('jobs', { focus: true });
979
+ return;
980
+ }
981
+ // Narrow / inline fallback: the full-screen overlay (unchanged).
982
+ if (typeof focusId === 'string' && focusId !== '')
983
+ setJobsPanelFocusId(focusId);
984
+ setJobsPanelOpen(true);
985
+ }, []);
986
+ /** Ctrl+A / detail 回退:侧栏分栏且 agents 已启用时打开右栏 Panel(内部
987
+ * dashboard ↔ detail 二级路由自己管);窄屏 / inline 保留整屏形态。 */
988
+ const openSubagentDashboard = React.useCallback(() => {
989
+ const sidePanel = sidePanelRef.current;
990
+ if (sidePanel !== null && sidePanel.split && sidePanel.enabledPanelIds.includes('agents')) {
991
+ sidePanel.openPanel('agents', { focus: true });
992
+ return;
993
+ }
994
+ setSubagentDashboardOpen(true);
995
+ }, []);
775
996
  /** Detail view for a specific subagent (opened from dashboard). */
776
997
  const [subagentDetailId, setSubagentDetailId] = React.useState(null);
777
998
  /**
@@ -799,14 +1020,56 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
799
1020
  const closeScene = React.useCallback(() => {
800
1021
  setSceneOpen(false);
801
1022
  }, []);
802
- /** Open the scene, mark failures seen, and retire the key hint for good. */
803
- const openScene = React.useCallback(() => {
1023
+ /**
1024
+ * 整屏分支的「盖启动页」闸门(第七版防御位,用户实测回归:启动页一闪
1025
+ * 而过被顶掉)。落地页在屏上时,只有**经 `launchpadCoverRef` 授权**
1026
+ * (= 从落地页出发的交互打开)的整屏才盖它;否则该整屏状态被无视
1027
+ * (渲染落到落地页),启动页永远不被开机期的杂散状态挤掉。落地页不在
1028
+ * 屏上时闸门恒开(普通姿态与从前逐字节一致)。
1029
+ *
1030
+ * ⚠ 这一段必须排在组件**所有** early-return 之前(hooks 规则):曾放在
1031
+ * onboarding/interrupt 分支之后,向导一开就少跑这个 effect,React 直接
1032
+ * 报 hooks 乱序(verify-launchpad-onboarding-chat A2/E5/H2 全红)。
1033
+ */
1034
+ const launchpadGate = () => !launchpadShown || launchpadCoverRef.current;
1035
+ // 覆盖屏全部收起时收回授权:下一次打开必须再经过落地页自己的交互。
1036
+ const launchpadCoverScreenUp = supervisorOpen || treeOpen || settingsOpen
1037
+ || jobsPanelOpen || subagentDashboardOpen || subagentDetailId !== null || sceneOpen;
1038
+ React.useEffect(() => {
1039
+ if (!launchpadCoverScreenUp)
1040
+ launchpadCoverRef.current = false;
1041
+ }, [launchpadCoverScreenUp]);
1042
+ /**
1043
+ * 从落地页出发的交互要开整屏前先授权(配合 `launchpadGate`):调用点只在
1044
+ * 落地页自己的回调里(onAction / onCommandPick / onEscape 的会话浏览路)。
1045
+ * 异步误置真的整屏状态没有这道授权 → 闸门挡下,落地页留在最上层。
1046
+ */
1047
+ const authorizeLaunchpadCover = () => { launchpadCoverRef.current = true; };
1048
+ /**
1049
+ * Open the trajectory, mark failures seen, and retire the key hint for good.
1050
+ *
1051
+ * Ctrl+T and `/trace` share this one entry point, and it is SPLIT-AWARE the
1052
+ * same way `/jobs` and `/agents` already are: while the sidebar is
1053
+ * rendering, the trajectory opens as the `trajectory` panel inside it
1054
+ * instead of taking the whole screen. `options.fullscreen` is the escape
1055
+ * hatch the panel's own ⤢ button uses — without it that button would route
1056
+ * straight back into the panel it is trying to leave.
1057
+ */
1058
+ const openScene = React.useCallback((options) => {
804
1059
  seenFailuresRef.current = trajectoryRef.current?.counts.errors ?? 0;
805
1060
  setTrajectorySeen(previous => {
806
1061
  if (!previous)
807
1062
  writeTrajectorySeen();
808
1063
  return true;
809
1064
  });
1065
+ const controller = sidePanelRef.current;
1066
+ if (options?.fullscreen !== true
1067
+ && controller !== null
1068
+ && controller.split
1069
+ && controller.enabledPanelIds.includes('trajectory')) {
1070
+ controller.openPanel('trajectory', { focus: true });
1071
+ return;
1072
+ }
810
1073
  setSceneOpen(true);
811
1074
  }, []);
812
1075
  /**
@@ -837,6 +1100,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
837
1100
  }).catch(() => undefined);
838
1101
  }
839
1102
  }, [agentViewReturnId, channel, repaintTranscript]);
1103
+ /**
1104
+ * Leave the first-run guide.
1105
+ *
1106
+ * `done` writes the one-shot marker; `skipped` deliberately does NOT — the
1107
+ * user has not answered, and a later launch is exactly when they might.
1108
+ * Both cases land on the launchpad rather than the transcript: the wizard
1109
+ * interrupted a launch, so the launch resumes where it left off.
1110
+ *
1111
+ * The write is best-effort by design (`markOnboardingDone` returns false
1112
+ * when the data directory is unwritable); a read-only install gets one more
1113
+ * offer next launch, which is the recoverable end of the trade.
1114
+ */
1115
+ const closeOnboarding = React.useCallback((outcome) => {
1116
+ setOnboardingOpen(false);
1117
+ if (outcome === 'done') {
1118
+ const written = markOnboardingDone();
1119
+ setOnboardingPending(false);
1120
+ channel.notify(t(written ? 'onboarding-finished' : 'onboarding-write-failed'), {
1121
+ color: written ? 'success' : 'warning',
1122
+ timeoutMs: written ? 3000 : 6000,
1123
+ });
1124
+ }
1125
+ else {
1126
+ channel.notify(t('onboarding-skipped'), { timeoutMs: 4000 });
1127
+ }
1128
+ }, [channel]);
840
1129
  /** The startup summary gives way to transcript rows after the first local command or message. */
841
1130
  const loadedContextVisible = channel.rows.length === 0 && channel.loadedContext !== undefined;
842
1131
  /** Startup context panel: collapsed by default, toggled with Ctrl+P. */
@@ -1631,6 +1920,69 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1631
1920
  : t('migrate-failed', { n: failures }), failures === 0 ? { timeoutMs: 6000 } : { color: 'error', timeoutMs: 10000 });
1632
1921
  })();
1633
1922
  };
1923
+ /**
1924
+ * Close the launchpad and hand the draft to the chat screen.
1925
+ *
1926
+ * THREE cases, and they are genuinely different:
1927
+ *
1928
+ * - a slash command → `runCommand`, the same dispatch a typed command
1929
+ * takes in the composer. The line is NOT submitted to the model.
1930
+ * Recognition is the composer's OWN rule (第六版 BUG 1 修复): the merged
1931
+ * command list (locals + plugin/registry commands via channel.commandList)
1932
+ * decides whether the line is a command — isLocalCommandName alone missed
1933
+ * registry-only names (e.g. /plan, /goal), which then fell through to
1934
+ * channel.submit and went to the model as a user message.
1935
+ * - ordinary text → SENT DIRECTLY (fifth revision, user-reported bug:
1936
+ * "按了回车就直接进入流式输出"). The line rides the composer's own
1937
+ * submit path (`channel.submit`, which queues through the DSH inbox
1938
+ * while a turn is running) — no draft is parked anywhere, the composer
1939
+ * mounts EMPTY because the content is already gone as the first turn.
1940
+ * - empty → nothing to send; just show the conversation.
1941
+ *
1942
+ * History is appended for the two non-empty cases (matching what PromptInput
1943
+ * does on submit) so the launchpad's first line is reachable with ↑ later.
1944
+ */
1945
+ const closeLaunchpad = React.useCallback((submit) => {
1946
+ const text = submit.trim();
1947
+ setLaunchpadOpen(false);
1948
+ // 首启时 openHomeOnBoot 与落地页同时为真:会话浏览器已经开着、只是被落地页盖住。
1949
+ // 提交首句后必须把它收掉,否则用户落到浏览器而不是"草稿就在眼前的对话",
1950
+ // 与本函数 doc 承诺的落点直接矛盾。
1951
+ setSupervisorOpen(false);
1952
+ setLaunchpadFocus(-1);
1953
+ setLaunchpadDraft('');
1954
+ setLaunchpadCaret(0);
1955
+ if (text === '')
1956
+ return;
1957
+ void appendHistory(text);
1958
+ const parsed = text.startsWith('/') ? parseCommandName(text) : undefined;
1959
+ // 第八版:/help 在落地页上也是盖屏浮层(补全面板被 Esc 收掉后直接
1960
+ // Enter 的那条路)——不收落地页、不进对话页。聊天页里 /help 的行为
1961
+ // 不变(那边不走这个回调)。
1962
+ if (parsed !== undefined && parsed.name === 'help' && launchpadOpen) {
1963
+ dispatchOverlay({ type: 'open', overlay: { kind: 'help' } });
1964
+ return;
1965
+ }
1966
+ // 与 composer 的 tryRunCommand 同一条判定:合并命令表(LOCAL_COMMANDS +
1967
+ // channel.commandList 的插件/registry 命令)里有名字才是命令;hidden
1968
+ // 命令照旧认。判定之外的 / 开头行才走 submit(与聊天页 Enter 行为一致)。
1969
+ if (parsed !== undefined && (isLocalCommandName(parsed.name)
1970
+ || isHiddenCommandName(parsed.name)
1971
+ || channel.commandList.some(entry => entry.name === parsed.name))) {
1972
+ void runCommand(parsed.name, parsed.rawInput);
1973
+ return;
1974
+ }
1975
+ // 直接发送:与 composer 回车同一条提交路径。发出去之后输入框是空的
1976
+ // (内容已作为首轮发出,绝不"既发了又留在框里"),也没有交接提示——
1977
+ // 没有草稿要交,一句"已放进输入框"的 toast 反而是假的。
1978
+ channel.submit(text);
1979
+ }, [channel, launchpadOpen]);
1980
+ /**
1981
+ * The screen's command dispatcher. Every entry point reaches this one
1982
+ * closure — PromptInput's `onRunCommand`, the completion menu, this screen's
1983
+ * own `/` cases, and the launchpad handoff above. There is deliberately no
1984
+ * second dispatcher for the landing page.
1985
+ */
1634
1986
  const runCommand = (name, rawInput = '', images = []) => {
1635
1987
  switch (name) {
1636
1988
  case 'activity': {
@@ -1913,9 +2265,24 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1913
2265
  setSelectedId(null);
1914
2266
  setSelectionActive(false);
1915
2267
  return true;
1916
- case 'compact':
1917
- channel.compact();
2268
+ case 'compact': {
2269
+ // The TUI's own transaction is the primary path: it owns the
2270
+ // `tui/compact` decision event, the progress row with live token
2271
+ // count, Esc cancellation, and the settle before a session switch
2272
+ // (issue #1092) — the official `dsh-command-compact` command has none
2273
+ // of that. A composition that mounts no compaction service falls back
2274
+ // to the registry command; one with neither says WHY up front instead
2275
+ // of looking usable and failing on use (channel/capabilities.ts).
2276
+ const compact = channel.capabilities().compact;
2277
+ if (compact.route === 'local') {
2278
+ channel.compact();
2279
+ return true;
2280
+ }
2281
+ if (compact.route === 'registry')
2282
+ return runExternalCommand('compact', rawInput, images);
2283
+ channel.notify(t('capability-unavailable', { name: 'compact', reason: t(compact.reasonKey) }), { color: 'warning', timeoutMs: 8000 });
1918
2284
  return true;
2285
+ }
1919
2286
  case 'trace':
1920
2287
  // `/trace` is kept as the discoverable spelling of Ctrl+T: the
1921
2288
  // command menu is where a user finds out the trajectory exists.
@@ -2075,14 +2442,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2075
2442
  });
2076
2443
  return true;
2077
2444
  case 'tokens': {
2078
- const usage = t('tokens-usage', { in: formatTokens(channel.tokens.input), out: formatTokens(channel.tokens.output) });
2079
- if (channel.contextWindow === undefined) {
2080
- channel.notify(usage);
2081
- }
2082
- else {
2083
- const percent = Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
2084
- channel.notify(t('tokens-usage-context', { usage, percent }));
2445
+ // Three separately-labelled facts, never two measures side by side:
2446
+ // what THIS request uploaded (the provider's mutually-exclusive prompt
2447
+ // buckets), what the session has accumulated (the token counters), and
2448
+ // how full the window is (the channel's single occupancy reading — the
2449
+ // same number the footer and the context-low warning show).
2450
+ const usage = channel.lastUsage;
2451
+ const lines = [];
2452
+ if (usage !== undefined) {
2453
+ const upload = usage.input + usage.cacheRead + usage.cacheWrite;
2454
+ const rate = upload > 0 ? ((usage.cacheRead / upload) * 100).toFixed(1) : '0.0';
2455
+ lines.push(t('tokens-request-upload', {
2456
+ upload: formatTokens(upload),
2457
+ input: formatTokens(usage.input),
2458
+ read: formatTokens(usage.cacheRead),
2459
+ write: formatTokens(usage.cacheWrite),
2460
+ rate,
2461
+ }));
2085
2462
  }
2463
+ lines.push(t('tokens-session-breakdown', {
2464
+ input: formatTokens(channel.tokens.input),
2465
+ output: formatTokens(channel.tokens.output),
2466
+ read: formatTokens(channel.tokens.cacheRead),
2467
+ write: formatTokens(channel.tokens.cacheWrite),
2468
+ }));
2469
+ const occupancyLine = contextOccupancyLine(channel);
2470
+ if (occupancyLine !== undefined)
2471
+ lines.push(occupancyLine);
2472
+ channel.notify(lines.join('\n'));
2086
2473
  return true;
2087
2474
  }
2088
2475
  case 'resume':
@@ -2095,7 +2482,20 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2095
2482
  * commands is about muscle memory, not about three surfaces — every one
2096
2483
  * of them lands here, on the same runtime.
2097
2484
  */
2098
- case 'home':
2485
+ case 'home': {
2486
+ setHelpOpen(false);
2487
+ // Split-aware, like /trace and /jobs: while the sidebar is rendering
2488
+ // and the workspace panel is enabled, the workspace view opens THERE.
2489
+ // The panel's own ⤢ goes back to the full-screen home.
2490
+ const controller = sidePanelRef.current;
2491
+ if (controller !== null && controller.split && controller.enabledPanelIds.includes('workspace')) {
2492
+ controller.openPanel('workspace', { focus: true });
2493
+ return true;
2494
+ }
2495
+ agentViewOpenSessionRef.current = channel.agentId;
2496
+ setSupervisorOpen(true);
2497
+ return true;
2498
+ }
2099
2499
  case 'agentview': {
2100
2500
  setHelpOpen(false);
2101
2501
  // The screen opens immediately and loads its own list. Waiting for the
@@ -2196,23 +2596,24 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2196
2596
  return true;
2197
2597
  case 'status': {
2198
2598
  const usage = channel.lastUsage;
2199
- const pct = channel.contextWindow === undefined
2200
- ? undefined
2201
- : Math.max(0, Math.min(100, Math.round((channel.tokens.input / channel.contextWindow) * 100)));
2202
2599
  const lines = [
2203
2600
  `${t('status-model', { model: channel.model })}${channel.reasoningEffort ? ` · ${capitalize(channel.reasoningEffort)} effort` : ''}`,
2204
2601
  `${t('status-state', { state: channel.working ? t('status-working') : t('status-idle') })}`,
2205
2602
  `${t('status-session', { id: channel.agentId })}`,
2206
2603
  `${t('status-dir', { cwd: channel.displayCwd })}${channel.gitBranch ? ` · ${channel.gitBranch}` : ''}`,
2207
- `Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
2604
+ t('tokens-session-total', {
2605
+ input: formatTokens(channel.tokens.input),
2606
+ output: formatTokens(channel.tokens.output),
2607
+ }),
2208
2608
  ];
2209
2609
  if (usage !== undefined) {
2210
2610
  const total = usage.input + usage.cacheRead + usage.cacheWrite;
2211
2611
  const rate = total > 0 ? ((usage.cacheRead / total) * 100).toFixed(1) : '0.0';
2212
2612
  lines.push(t('cost-cache-rate', { rate, read: formatTokens(usage.cacheRead), write: formatTokens(usage.cacheWrite) }));
2213
2613
  }
2214
- if (pct !== undefined)
2215
- lines.push(t('cost-context', { pct }));
2614
+ const occupancyLine = contextOccupancyLine(channel);
2615
+ if (occupancyLine !== undefined)
2616
+ lines.push(occupancyLine);
2216
2617
  if (channel.sessionTitle)
2217
2618
  lines.push(t('status-title', { title: channel.sessionTitle }));
2218
2619
  setHelpOpen(false);
@@ -2222,7 +2623,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2222
2623
  case 'cost': {
2223
2624
  const usage = channel.lastUsage;
2224
2625
  const lines = [
2225
- `Tokens ${formatTokens(channel.tokens.input)} in → ${formatTokens(channel.tokens.output)} out`,
2626
+ t('tokens-session-total', {
2627
+ input: formatTokens(channel.tokens.input),
2628
+ output: formatTokens(channel.tokens.output),
2629
+ }),
2226
2630
  ];
2227
2631
  if (usage !== undefined) {
2228
2632
  const total = usage.input + usage.cacheRead + usage.cacheWrite;
@@ -2271,6 +2675,54 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2271
2675
  setSettingsOpen(true);
2272
2676
  return true;
2273
2677
  }
2678
+ case 'continue': {
2679
+ // 落地页第四版的 Continue(最高频动作):继续**最近一条可继续会话**。
2680
+ // 数据是真的——`agentViewRows`(含持久化名册,listing 落地后含全部历史)
2681
+ // 里挑 updatedAt 最新的非当前行;没有可继续的就去会话名册挑,失败也不
2682
+ // 静默(notify + 打开 supervisor 让用户自己挑),绝不点了个没反应。
2683
+ setHelpOpen(false);
2684
+ const candidates = agentViewRows.filter(row => !row.current && row.id !== channel.agentId && row.title.trim() !== '');
2685
+ const latest = candidates.reduce((acc, row) => (acc === undefined || row.updatedAt > acc.updatedAt ? row : acc), undefined);
2686
+ if (latest === undefined) {
2687
+ channel.notify(t('launchpad-continue-none'), { color: 'error', timeoutMs: 6000 });
2688
+ agentViewOpenSessionRef.current = channel.agentId;
2689
+ // 第七版:无可继续会话时浏览器盖在落地页之上(不收落地页)——Esc
2690
+ // 回启动页,与合并入口「会话与工作区」同一条姿态。
2691
+ setSupervisorOpen(true);
2692
+ return true;
2693
+ }
2694
+ setLaunchpadOpen(false);
2695
+ void channel.resumeTo(latest.id)
2696
+ .then((result) => {
2697
+ if (!result.ok) {
2698
+ channel.notify(t('launchpad-continue-failed'), { color: 'error', timeoutMs: 8000 });
2699
+ agentViewOpenSessionRef.current = channel.agentId;
2700
+ setSupervisorOpen(true);
2701
+ return;
2702
+ }
2703
+ channel.notify(t('resume-resumed'));
2704
+ suppressLogoIntroRef.current = true;
2705
+ setAgentViewReturnId(undefined);
2706
+ repaintTranscript();
2707
+ })
2708
+ .catch(() => {
2709
+ channel.notify(t('launchpad-continue-failed'), { color: 'error', timeoutMs: 8000 });
2710
+ });
2711
+ return true;
2712
+ }
2713
+ case 'setup': {
2714
+ // `/setup` re-runs the first-run guide. Unlike the boot path this is
2715
+ // an EXPLICIT request, so it opens unconditionally — a user who typed
2716
+ // it has already decided the wizard is what they want. It reuses the
2717
+ // same screen and the same `onClose`, so completing it here also
2718
+ // (re)writes the one-shot marker.
2719
+ // 第七版:向导的 early-return 在落地页**之前**,天然盖在落地页之上;
2720
+ // 不再收掉落地页——Esc/跳过/完成都回到启动页(旧姿态收掉落地页后,
2721
+ // 向导关掉就落到对话页,正是用户报的 bug 形态之一)。
2722
+ setHelpOpen(false);
2723
+ setOnboardingOpen(true);
2724
+ return true;
2725
+ }
2274
2726
  case 'star': {
2275
2727
  // 一键 star:**只有用户主动敲 /star 才会跑**(绝不自动)。动作用
2276
2728
  // runStarAction(与开屏弹窗共用),异步执行、结果用 notify 报。
@@ -2373,10 +2825,52 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2373
2825
  channel.notify(t('agentsmd-created', { result }));
2374
2826
  return true;
2375
2827
  }
2376
- case 'jobs':
2828
+ case 'jobs': {
2377
2829
  setHelpOpen(false);
2378
- setJobsPanelOpen(true);
2830
+ // Split mode routes to the side panel; narrow terminals keep the
2831
+ // full-screen overlay.
2832
+ if (sidePanel.split && sidePanel.enabledPanelIds.includes('jobs')) {
2833
+ sidePanel.openPanel('jobs', { focus: true });
2834
+ }
2835
+ else {
2836
+ setJobsPanelFocusId(null);
2837
+ setJobsPanelOpen(true);
2838
+ }
2379
2839
  return true;
2840
+ }
2841
+ case 'panel': {
2842
+ setHelpOpen(false);
2843
+ // 无参 → 面板选择器(overlay 互斥结构内);有参 → 侧栏命令分发。
2844
+ if (rawInput.trim() === '' && sidePanel.splitAvailable && panelPickerRows.length > 0) {
2845
+ const current = panelPickerRows.findIndex(row => row.id === sidePanel.activePanelId);
2846
+ dispatchOverlay({ type: 'open', overlay: { kind: 'panel', index: Math.max(0, current) } });
2847
+ return true;
2848
+ }
2849
+ if (sidePanel.command(rawInput))
2850
+ return true;
2851
+ // 分栏不可用(窄屏 / inline / 编辑器展开 / splitEnabled=false)
2852
+ // 或参数未知时的整屏回退与兜底:已知面板走整屏形态,其余消费
2853
+ // 掉并提示——绝不原样返回 false,否则 PromptInput 会把
2854
+ // "/panel …" 当普通消息发给模型。
2855
+ const arg = rawInput.trim().toLowerCase();
2856
+ const target = arg === '' || arg === 'focus' || arg === 'toggle' || arg === 'zoom'
2857
+ ? sidePanel.activePanelId
2858
+ : arg;
2859
+ if (target === 'jobs') {
2860
+ setJobsPanelFocusId(null);
2861
+ setJobsPanelOpen(true);
2862
+ return true;
2863
+ }
2864
+ if (target === 'agents') {
2865
+ setSubagentDashboardOpen(true);
2866
+ return true;
2867
+ }
2868
+ if (arg === 'toggle' || arg === 'focus' || arg === 'zoom' || arg === '' || target !== undefined) {
2869
+ channel.notify(t('panel-unavailable-hint'));
2870
+ return true;
2871
+ }
2872
+ return false;
2873
+ }
2380
2874
  case 'agents':
2381
2875
  setHelpOpen(false);
2382
2876
  void channel.listSubagents().then((lines) => {
@@ -2415,7 +2909,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2415
2909
  ...oauth.map(row => t('login-oauth-row', {
2416
2910
  provider: row.provider,
2417
2911
  state: row.signedIn
2418
- ? t('login-oauth-in', { time: new Date(row.expiresAt ?? 0).toISOString() })
2912
+ ? row.expiresAt === undefined
2913
+ ? t('login-oauth-in-no-expiry')
2914
+ : t('login-oauth-in', { time: new Date(row.expiresAt).toISOString() })
2419
2915
  : row.expired
2420
2916
  ? t('login-oauth-expired')
2421
2917
  : t('login-oauth-signed-out'),
@@ -2499,9 +2995,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2499
2995
  // Registered by dsh-plan-mode: bare `/plan` opens an on/off picker
2500
2996
  // marked with the current state instead of toggling blindly; Enter
2501
2997
  // dispatches `/plan` or `/plan off`. Arguments pass through verbatim
2502
- // (`/plan off`), and an unmounted row falls back to the default
2503
- // external path.
2504
- const mounted = channel.commandList.some(command => command.external && command.name === 'plan');
2998
+ // (`/plan off`). Availability comes from the shared capability facts
2999
+ // (the same read Shift+Tab uses), not from a second command-list
3000
+ // scan; with no registry command the line falls through to the model,
3001
+ // exactly as before.
3002
+ const plan = channel.capabilities().plan;
3003
+ const mounted = plan.route !== 'none';
2505
3004
  const parts = rawInput.trim().split(/\s+/).filter(Boolean);
2506
3005
  if (mounted && parts.length === 0) {
2507
3006
  setHelpOpen(false);
@@ -2849,7 +3348,56 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2849
3348
  */
2850
3349
  const { columns: terminalColumns } = useTerminalSize();
2851
3350
  const pageInsetX = usePageInset().x;
2852
- const wakeWidth = miniWakeWidth(terminalColumns);
3351
+ // 侧栏控制器:几何(chatColumns/panelColumns)、焦点与键盘分发都在
3352
+ // 这个 hook 里(设计文档 §16.5——Chat 只多一次调用、一处键盘让位、
3353
+ // 一条 runCommand case)。编辑器展开时几何强制 collapsed。
3354
+ const sidePanel = useSidePanel({
3355
+ columns: terminalColumns,
3356
+ fullscreen,
3357
+ editorOpen: promptEditorOpen,
3358
+ });
3359
+ /**
3360
+ * 「全屏」出口(PanelBar 的 ⤢):把侧栏里那个面板的内容切到它原本的整屏
3361
+ * 形态。只映射**真的有整屏对应物**的面板 id(能力位 capabilities.fullscreen
3362
+ * 决定按钮画不画,这里决定点了去哪);没映射到的 id 静默无操作——按钮与
3363
+ * 落地页、键盘入口共用同一个状态位,所以从整屏返回时侧栏还在原处。
3364
+ */
3365
+ const openPanelFullscreen = React.useCallback((panelId) => {
3366
+ if (panelId === 'jobs') {
3367
+ sidePanelRef.current?.focusChat();
3368
+ setJobsPanelOpen(true);
3369
+ return;
3370
+ }
3371
+ if (panelId === 'agents') {
3372
+ sidePanelRef.current?.focusChat();
3373
+ setSubagentDashboardOpen(true);
3374
+ return;
3375
+ }
3376
+ if (panelId === 'trajectory') {
3377
+ sidePanelRef.current?.focusChat();
3378
+ // 强制整屏:默认的 openScene 在分屏下会路由回面板(见它的注释)。
3379
+ openScene({ fullscreen: true });
3380
+ return;
3381
+ }
3382
+ if (panelId === 'workspace') {
3383
+ sidePanelRef.current?.focusChat();
3384
+ setSupervisorOpen(true);
3385
+ }
3386
+ }, [openScene]);
3387
+ // openJobsPanel(在上方、identity 稳定)经 ref 读取最新控制器。
3388
+ sidePanelRef.current = sidePanel;
3389
+ /**
3390
+ * 宠物「代言」判定:分屏开着且 companion 是活动面板时,新通知由宠物头顶
3391
+ * 气泡说出,输入框上方的 toast 不再重复同一条。error 色恒不压制(可能
3392
+ * 要行动)。渲染期读控制器状态——与气泡同一提交,无先闪后消的竞态。
3393
+ */
3394
+ const petSaysNotices = React.useCallback((item) => item.color !== 'error' && sidePanel.split && sidePanel.activePanelId === 'companion', [sidePanel.split, sidePanel.activePanelId]);
3395
+ // 聊天列宽:收起时 = 内容区全宽(与现状逐字节一致),分栏时 = 左栏宽。
3396
+ // 所有显式下传的宽度(gutter / 图片预览区 / wake 条)都改用它;转录
3397
+ // 子树则经 SidePanelLayout 的 TerminalSizeContext 覆盖自动拿到。
3398
+ const chatColumns = sidePanel.chatColumns;
3399
+ const wakeWidth = miniWakeWidth(chatColumns);
3400
+ const panelPickerRows = usePanelPickerRows(sidePanel);
2853
3401
  const wakeBand = React.useMemo(() => wakeWidth === 0
2854
3402
  ? undefined
2855
3403
  // `sequence`, not the scene's `compressed`: at sixteen columns an idle
@@ -3014,11 +3562,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3014
3562
  }, []);
3015
3563
  /** Deduplicate terminals that report one Enter as parsed Return then raw CR/LF. */
3016
3564
  const lastModalEnterAtRef = React.useRef(0);
3565
+ const couponVisible = coupon !== null && starModal === null
3566
+ && approvalSnapshot === null && dialogSnapshot === null && questionSnapshot === null
3567
+ && overlay.kind === 'none' && btw === null && recap === null
3568
+ && !supervisorOpen && !treeOpen && !settingsOpen && !jobsPanelOpen
3569
+ && !sceneOpen && !subagentDashboardOpen && subagentDetailId === null;
3570
+ const markCouponShown = React.useCallback((orderId) => {
3571
+ bonusNotices?.shown(orderId);
3572
+ }, [bonusNotices]);
3573
+ const closeCoupon = React.useCallback(() => {
3574
+ if (coupon !== null)
3575
+ bonusNotices?.dismiss(coupon.orderId);
3576
+ }, [bonusNotices, coupon]);
3017
3577
  useInput((input, key, event) => {
3018
3578
  // 开屏"求 star"弹窗开着时键盘全归它(↑/↓/Enter/Esc 由它自己的
3019
3579
  // useInput 处理),滚轮也不许滚动它身后的转录——和下面的整屏界面
3020
3580
  // 同一套让位规则。
3021
- if (starModal !== null)
3581
+ if (starModal !== null || couponVisible)
3022
3582
  return;
3023
3583
  // Prompt-slot panels own the keyboard while visible. Their own useInput
3024
3584
  // handles the relevant keys; Chat registered first, so yielding here
@@ -3029,6 +3589,23 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3029
3589
  || overlay.kind === 'tips'
3030
3590
  || (recap !== null && (!recap.auto || recap.expanded)))
3031
3591
  return;
3592
+ // The first-run guide owns the whole terminal while it is up: it is a
3593
+ // wizard with its own step navigation, its own focus ring inside the
3594
+ // pickers it borrows, and no free-text input at all. Registered ABOVE the
3595
+ // launchpad because it renders above it.
3596
+ if (onboardingOpen)
3597
+ return;
3598
+ // The launchpad owns the whole terminal while it is up — including the
3599
+ // plain letters that would otherwise reach the composer, which is exactly
3600
+ // the point: it IS the composer on this screen, and its draft is submitted
3601
+ // directly (see closeLaunchpad). ONE exception (fifth revision): a picker
3602
+ // opened from the param row renders ABOVE the launchpad and therefore owns
3603
+ // the keyboard — fall through to the overlay branches below (Esc closes the
3604
+ // picker back onto the launchpad). The launchpad's own useInput is paused
3605
+ // via inputPaused for exactly this window, so keys the picker does not
3606
+ // consume cannot leak into the draft.
3607
+ if (launchpadShown && !launchpadOverlayUp)
3608
+ return;
3032
3609
  // The session tree owns the whole terminal while it is up: plain letters
3033
3610
  // drive its search, clicks and Enter drive its action menu.
3034
3611
  if (treeOpen)
@@ -3076,7 +3653,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3076
3653
  // Events only arrive with mouse tracking on; inline mode never sees
3077
3654
  // them, so this is a no-op there.
3078
3655
  if (key.wheelUp || key.wheelDown) {
3079
- if (helpOpen)
3656
+ // 焦点在右栏时滚轮不回落到转录:落在面板矩形上的滚轮已由命中
3657
+ // 测试路由给面板自己的 ScrollBox,到达这里的兜底事件不应在
3658
+ // 用户操作右栏时误滚左栏。
3659
+ if (helpOpen || sidePanel.focus === 'panel')
3080
3660
  return;
3081
3661
  // Any open transient dialog is modal to the wheel; the one exception
3082
3662
  // mirrors the render gate — a workspace picker whose target list has
@@ -3113,7 +3693,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3113
3693
  // pending. The panels bind ↑/↓/Space/Tab/Enter/Esc and never these keys,
3114
3694
  // so paging cannot steal anything from them.
3115
3695
  if ((key.pageUp || key.pageDown) && fullscreen) {
3116
- if (helpOpen)
3696
+ // 焦点在右栏时 PgUp/PgDn 属于活动面板(经侧栏键盘分发)。
3697
+ if (helpOpen || sidePanel.focus === 'panel')
3117
3698
  return;
3118
3699
  const overlayModal = overlay.kind !== 'none' &&
3119
3700
  (overlay.kind !== 'workspace-picker' || workspaceTargets.length > 0);
@@ -3152,6 +3733,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3152
3733
  }
3153
3734
  return;
3154
3735
  }
3736
+ // 侧栏键盘分发(v2.1 优先级):上面的审批 / 问卷 / 对话框守卫仍然
3737
+ // 最先,其次是侧栏全局快捷键(Ctrl+B / Alt+Z,两种焦点都生效),
3738
+ // 然后焦点在右栏时一切按键归侧栏——活动面板的业务键优先,宿主
3739
+ // 回退键(←/→ 切面板、z、+/-、Esc 回聊天)兜底。
3740
+ if (sidePanel.handleKey(input, key, event))
3741
+ return;
3155
3742
  const returnCandidate = isPlainReturnInput(input, key);
3156
3743
  const returnNow = Date.now();
3157
3744
  const plainReturn = returnCandidate && returnNow - lastModalEnterAtRef.current >= 80;
@@ -3246,6 +3833,32 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3246
3833
  event.stopImmediatePropagation();
3247
3834
  return;
3248
3835
  }
3836
+ if (overlay.kind === 'help') {
3837
+ // 帮助盖屏(第八版):Esc/Ctrl+C/Enter 收起回落地页;纵向导航归这个
3838
+ // 视口(与 PromptInput 的 helpOpen 分支同一套键位),其余按键模态吞掉
3839
+ // ——绝不漏进落地页草稿。
3840
+ const page = Math.max(1, helpCoverViewportHeight - 2);
3841
+ if (key.upArrow || key.wheelUp) {
3842
+ helpCoverScrollRef.current?.scrollBy(key.wheelUp ? -3 : -1);
3843
+ }
3844
+ else if (key.downArrow || key.wheelDown) {
3845
+ helpCoverScrollRef.current?.scrollBy(key.wheelDown ? 3 : 1);
3846
+ }
3847
+ else if (key.pageUp || key.pageDown) {
3848
+ helpCoverScrollRef.current?.scrollBy(key.pageUp ? -page : page);
3849
+ }
3850
+ else if (key.home) {
3851
+ helpCoverScrollRef.current?.scrollTo(0);
3852
+ }
3853
+ else if (key.end) {
3854
+ helpCoverScrollRef.current?.scrollTo(Number.MAX_SAFE_INTEGER);
3855
+ }
3856
+ else if (key.escape || (key.ctrl && (input === 'c' || input === 'd')) || plainReturn) {
3857
+ dispatchOverlay({ type: 'close' });
3858
+ }
3859
+ event.stopImmediatePropagation();
3860
+ return;
3861
+ }
3249
3862
  if (overlay.kind === 'thinking') {
3250
3863
  if (key.upArrow || key.downArrow) {
3251
3864
  dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: 2 });
@@ -3644,6 +4257,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3644
4257
  }
3645
4258
  return;
3646
4259
  }
4260
+ if (overlay.kind === 'panel') {
4261
+ if (key.upArrow || key.downArrow) {
4262
+ dispatchOverlay({ type: 'move', delta: key.upArrow ? -1 : 1, count: panelPickerRows.length });
4263
+ }
4264
+ else if (plainReturn) {
4265
+ const row = panelPickerRows[overlay.index];
4266
+ dispatchOverlay({ type: 'close' });
4267
+ if (row !== undefined)
4268
+ sidePanel.openPanel(row.id, { focus: true });
4269
+ }
4270
+ else if (key.escape) {
4271
+ dispatchOverlay({ type: 'close' });
4272
+ }
4273
+ return;
4274
+ }
3647
4275
  if (overlay.kind === 'theme') {
3648
4276
  const options = getThemeOptions(themeHost);
3649
4277
  if (key.upArrow || key.downArrow) {
@@ -3815,11 +4443,12 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3815
4443
  return;
3816
4444
  }
3817
4445
  if (actionMatches('dashboard', input, key)) {
3818
- // The subagent dashboard key (default Ctrl+A) opens the dashboard.
3819
- // Consume the key: without the stop the prompt editor's readline
3820
- // binding ALSO fires (Ctrl+A moves the caret to line start), so one
3821
- // press both opens the overlay and jumps the cursor.
3822
- setSubagentDashboardOpen(true);
4446
+ // The subagent dashboard key (default Ctrl+A) opens the dashboard —
4447
+ // split mode routes it to the agents side panel instead. Consume the
4448
+ // key: without the stop the prompt editor's readline binding ALSO
4449
+ // fires (Ctrl+A moves the caret to line start), so one press both
4450
+ // opens the view and jumps the cursor.
4451
+ openSubagentDashboard();
3823
4452
  event.stopImmediatePropagation();
3824
4453
  return;
3825
4454
  }
@@ -3831,7 +4460,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3831
4460
  return;
3832
4461
  }
3833
4462
  if (actionMatches('history', input, key) && !helpOpen) {
3834
- setHistoryEntries(loadHistory());
4463
+ setHistoryEntries(loadHistory(channel.cwd));
3835
4464
  dispatchOverlay({
3836
4465
  type: 'open',
3837
4466
  overlay: { kind: 'history', query: '', cursor: 0, focus: 0 },
@@ -3856,6 +4485,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3856
4485
  setSelectedId(null);
3857
4486
  }
3858
4487
  }
4488
+ else if (key.escape && promptControllerRef.current?.consumeEscape()) {
4489
+ // Selection/editor Esc stays with the composer even though Chat's
4490
+ // global listener runs first. Consume it before interrupting the turn.
4491
+ event.stopImmediatePropagation();
4492
+ }
3859
4493
  else if (key.escape && channel.working && !helpOpen && !promptControllerRef.current?.vimActive()) {
3860
4494
  // Esc interrupts a running turn (the prompt input
3861
4495
  // only sees esc when idle, where it has the double-tap-clear meaning).
@@ -4023,14 +4657,21 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4023
4657
  // (wired to the toast below).
4024
4658
  event.stopImmediatePropagation();
4025
4659
  }
4660
+ }, {
4661
+ // Chat's global layer must see every key before the composer and the
4662
+ // panels it hosts (the handler's yield guards and readline shadowing
4663
+ // both assume it), whether or not they mounted in the same
4664
+ // commit — child effects run first, so append order would put a
4665
+ // first-mount PromptInput ahead of Chat (#1155).
4666
+ prepend: true,
4026
4667
  });
4027
4668
  // Working-activity line (spinner slot): context-pressure prefix shares the
4028
- // StatusLine thresholds (amber ≥ 80, red ≥ 95).
4029
- const activityWarnPct = contextPressurePct(channel.lastUsage, channel.contextWindow);
4669
+ // StatusLine thresholds (amber ≥ 80, red ≥ 95) and its occupancy source.
4670
+ const activityWarnPct = contextPressurePct(channelContextOccupancy(channel));
4030
4671
  // Who owns the spinner slot: with the working-activity line on, that slot
4031
4672
  // draws the user's `/activity` preset, so the compaction row borrows the same
4032
4673
  // indicator instead of answering with the classic dot.
4033
- const activitySlot = channel.activityEnabled && !channel.minimal;
4674
+ const activitySlot = channel.activityEnabled && !channel.minimalUi;
4034
4675
  // An automatic compaction runs INSIDE the turn, so it rides whichever spinner
4035
4676
  // the slot shows as a badge instead of a second row (the spinner's timer is
4036
4677
  // the turn's, not the compaction's).
@@ -4082,8 +4723,171 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4082
4723
  }
4083
4724
  : undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
4084
4725
  const interruptPanel = approvalPanelNode ?? questionPanelNode;
4726
+ /**
4727
+ * Transient picker panels (pickers/dialogs) - this JSX feeds TWO mount
4728
+ * points since the fifth launchpad revision: the chat page OverlayAbove
4729
+ * (above the input cluster) and the launchpad itself (above its input
4730
+ * card, see the launchpad branch). Defined once so the two never drift.
4731
+ */
4732
+ const pickerPanels = (_jsxs(_Fragment, { children: [overlay.kind === 'help' && (_jsx(Box, { flexDirection: "column", marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList, viewportHeight: helpCoverViewportHeight, viewportWidth: chatColumns, scrollRef: helpCoverScrollRef, onCommandPick: (name) => {
4733
+ // 点击命令行 = 把 /name 填进落地页草稿(聊天页 Tab 补全的
4734
+ // 鼠标等价),浮层收起、人还在启动页(第八版:不许进对话页)。
4735
+ dispatchOverlay({ type: 'close' });
4736
+ const filled = '/' + name + ' ';
4737
+ setLaunchpadDraft(filled);
4738
+ setLaunchpadCaret(filled.length);
4739
+ setLaunchpadFocus(-1);
4740
+ } }) })), overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
4741
+ // 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
4742
+ const visible = index === 0;
4743
+ setThinkingVisible(visible);
4744
+ dispatchOverlay({ type: 'close' });
4745
+ channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
4746
+ } })), overlay.kind === 'workspace-picker' && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: overlay.index, currentCwd: channel.cwd, onPick: (index) => {
4747
+ // 点击行 = 切换该行目标(与 Enter 同一条路径)
4748
+ const target = workspaceTargets[index];
4749
+ dispatchOverlay({ type: 'close' });
4750
+ if (target !== undefined)
4751
+ void channel.switchWorkspace(target);
4752
+ } }) })), overlay.kind === 'workspace-menu' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceMenuPicker, { options: workspaceMenuOptions, focusIndex: overlay.index, onPick: (index) => {
4753
+ // 点击行 = 执行该行(与 Enter 同一条路径)
4754
+ runWorkspaceMenuOption(workspaceMenuOptions[index]);
4755
+ } }) })), overlay.kind === 'workspace-flow' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: overlay.flow.title, choices: overlay.flow.choices, focusIndex: overlay.index, busy: overlay.busy, input: overlay.input, onPick: (index) => {
4756
+ // 点击行 = 设焦点 + 执行分支(与 Enter 同一条路径);
4757
+ // busy/输入态在组件侧禁点
4758
+ const choice = overlay.flow.choices[index];
4759
+ if (choice === undefined)
4760
+ return;
4761
+ dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
4762
+ runWorkspaceFlowAction(signal => choice.choose(signal));
4763
+ } }) })), overlay.kind === 'model' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : activeModelGroup === undefined ? (_jsx(ModelPicker, { groups: modelGroups, focusIndex: overlay.index, currentProvider: channel.provider, onPick: (index) => {
4764
+ // 点击分组行 = 进入该组(与 Enter 同一条路径)
4765
+ const group = modelGroups[index];
4766
+ if (!group)
4767
+ return;
4768
+ setModelGroup(group.provider);
4769
+ if (group.provider === RECENTS_GROUP_PROVIDER) {
4770
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
4771
+ return;
4772
+ }
4773
+ const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
4774
+ dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
4775
+ } })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
4776
+ ? t('picker-group-recent')
4777
+ : modelGroups.find(group => group.provider === activeModelGroup)?.label, showBack: modelGroups.length > 1 && !modelPickerDirect, showProviderPrefix: activeModelGroup === RECENTS_GROUP_PROVIDER, focusIndex: overlay.index, currentModel: `${channel.provider}/${channel.model}`, onPick: (index) => {
4778
+ // 点击行 = 应用该行模型(与 Enter 同一条路径)
4779
+ const model = groupModels[index];
4780
+ if (!model)
4781
+ return;
4782
+ dispatchOverlay({ type: 'close' });
4783
+ void switchModelRecorded(model.provider, model.id, model.name);
4784
+ } })) })), overlay.kind === 'migrate' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(MigratePicker, { rows: migrateRows ?? [], focusIndex: overlay.index, checked: migrateChecked, loading: migrateRows === null, onPick: (index) => {
4785
+ const row = (migrateRows ?? [])[index];
4786
+ if (!row)
4787
+ return;
4788
+ setMigrateChecked(current => {
4789
+ const next = new Set(current);
4790
+ if (next.has(row.agentId))
4791
+ next.delete(row.agentId);
4792
+ else
4793
+ next.add(row.agentId);
4794
+ return next;
4795
+ });
4796
+ } }) })), 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) => {
4797
+ const skill = skillsList[index];
4798
+ if (!skill)
4799
+ return;
4800
+ dispatchOverlay({ type: 'close' });
4801
+ if (skill.userInvocable)
4802
+ setHistoryFill(`/${skill.name} `);
4803
+ } })) })), overlay.kind === 'activity' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: overlay.index, currentPreset: channel.activityFrames, onPick: (index) => {
4804
+ dispatchOverlay({ type: 'close' });
4805
+ const name = PRESET_NAMES[index];
4806
+ if (name)
4807
+ channel.setActivityFrames(name);
4808
+ } }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
4809
+ dispatchOverlay({ type: 'close' });
4810
+ const name = SESSION_COLOR_NAMES[index];
4811
+ if (name) {
4812
+ channel.setSessionColor(name);
4813
+ channel.notify(t('color-set', { name }), { color: 'success' });
4814
+ }
4815
+ } }) })), overlay.kind === 'panel' && panelPickerRows.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PanelPicker, { rows: panelPickerRows, focusIndex: overlay.index, activeId: sidePanel.activePanelId, onPick: (index) => {
4816
+ const row = panelPickerRows[index];
4817
+ dispatchOverlay({ type: 'close' });
4818
+ if (row !== undefined)
4819
+ sidePanel.openPanel(row.id, { focus: true });
4820
+ } }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
4821
+ // 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
4822
+ onPick: (index) => {
4823
+ dispatchOverlay({ type: 'set-index', kind: 'effort', index });
4824
+ const option = effortOptions[index];
4825
+ if (option)
4826
+ void channel.setEffort(option.id);
4827
+ } }) })), overlay.kind === 'preset' && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: overlay.index, currentPreset: channel.agentPreset, onPick: (index) => {
4828
+ dispatchOverlay({ type: 'close' });
4829
+ const option = presetOptions[index];
4830
+ if (option)
4831
+ void channel.switchPreset(option.id);
4832
+ } }) })), overlay.kind === 'permission' && overlay.snapshot.options.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PermissionsPicker, { options: overlay.snapshot.options, focusIndex: overlay.index, currentValue: overlay.snapshot.current?.value, cwd: channel.cwd, onPick: (index) => {
4833
+ if (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null)
4834
+ return;
4835
+ const option = overlay.snapshot.options[index];
4836
+ dispatchOverlay({ type: 'close' });
4837
+ if (option !== undefined)
4838
+ void runPermissionCommand(` ${option.value}`);
4839
+ } }) })), overlay.kind === 'plan' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PlanPicker, { focusIndex: overlay.index, currentOn: channel.mode.plan === true, onPick: (index) => {
4840
+ dispatchOverlay({ type: 'close' });
4841
+ const on = index === 0;
4842
+ void runExternalCommand('plan', on ? '' : ' off');
4843
+ } }) })), overlay.kind === 'lang' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(LangPicker, { focusIndex: overlay.index, currentLang: getLang(), onPick: (index) => {
4844
+ const lang = LANGS[index];
4845
+ if (lang === undefined)
4846
+ return;
4847
+ dispatchOverlay({ type: 'close' });
4848
+ applyLang(lang);
4849
+ } }) })), overlay.kind === 'theme' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: overlay.index, currentTheme: themeName, themeHost: themeHost, onPick: (index) => {
4850
+ dispatchOverlay({ type: 'close' });
4851
+ const name = getThemeOptions(themeHost)[index]?.value;
4852
+ if (name !== undefined) {
4853
+ const ok = setTheme(name);
4854
+ channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
4855
+ }
4856
+ } }) })), overlay.kind === 'history' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: overlay.query, cursorOffset: overlay.cursor, matches: historyMatches, focusIndex: overlay.focus, onPick: (index) => {
4857
+ // 点击行 = 填入该历史命令(与 Enter 同路径)
4858
+ const entry = historyMatches[index];
4859
+ if (entry) {
4860
+ setHistoryFill(entry.text);
4861
+ dispatchOverlay({ type: 'close' });
4862
+ }
4863
+ } }) })), overlay.kind === 'rewind' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: overlay.index, confirmRow: overlay.confirm, modes: overlay.modes, modeIndex: overlay.modeIndex, busy: overlay.busy, onPickRow: (index) => {
4864
+ // 列表页点击只选中:进入确认态保留键盘 Enter 显式触发
4865
+ dispatchOverlay({ type: 'set-index', kind: 'rewind', index });
4866
+ }, onConfirm: () => {
4867
+ // 确认页即显式确认层,点击直接执行(与 Enter 同路径)
4868
+ const row = overlay.confirm;
4869
+ if (row === null)
4870
+ return;
4871
+ dispatchOverlay({ type: 'close' });
4872
+ void performRewind(row);
4873
+ }, onPickMode: (index) => {
4874
+ // 模式列表点击直接执行该模式(与 Enter 同路径)
4875
+ const row = overlay.confirm;
4876
+ if (row === null)
4877
+ return;
4878
+ // 模式页仅当 modes 非空才渲染,这里空安全取值
4879
+ const mode = index === 0 ? null : (overlay.modes?.[index - 1]?.id ?? null);
4880
+ dispatchOverlay({ type: 'close' });
4881
+ void performRewind(row, mode);
4882
+ } }) })), overlay.kind === 'file-actions' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(FileActionsPanel, { path: overlay.path, isDir: overlay.isDir, focusIndex: overlay.index, onPick: (index) => {
4883
+ // 点击行直接执行该动作(与 Enter 同路径)
4884
+ const path = overlay.path;
4885
+ dispatchOverlay({ type: 'close' });
4886
+ runFileAction(index, path);
4887
+ } }) })), overlay.kind === 'search' && _jsx(TranscriptSearch, { query: searchQuery, cursorOffset: searchCursor, count: searchCount, current: searchCurrent })] }));
4085
4888
  const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
4086
- || subagentDetailId !== null || subagentDashboardOpen || sceneOpen;
4889
+ || subagentDetailId !== null || subagentDashboardOpen || sceneOpen
4890
+ || launchpadShown || onboardingOpen;
4087
4891
  if (interruptPanel !== null && screenOpen) {
4088
4892
  const node = (_jsx(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: interruptPanel }));
4089
4893
  return fullscreen ? node : _jsx(AlternateScreen, { children: node });
@@ -4109,6 +4913,29 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4109
4913
  }, children: renderScene ? renderScene(pluginScene.id, channel) : _jsxs(Text, { children: ["Scene unavailable: ", pluginScene.id] }) }));
4110
4914
  return fullscreen ? node : _jsx(AlternateScreen, { children: node });
4111
4915
  }
4916
+ /**
4917
+ * The first-run guide — the FIRST frame of a fresh installation.
4918
+ *
4919
+ * It sits above every other screen because it answers a question that comes
4920
+ * before all of them: "is this thing wired up at all". A launch that needs
4921
+ * setup has nothing useful to show behind a browser or a landing page.
4922
+ *
4923
+ * `activeModel` is deliberately undefined here: the wizard's model step
4924
+ * switches models through `channel.switchModel`, which is the same path the
4925
+ * chat screen uses, so a switch made inside the wizard is already live when
4926
+ * the user lands on the launcher.
4927
+ */
4928
+ if (onboardingOpen) {
4929
+ const node = (_jsx(Onboarding, { channel: channel, themeHost: themeHost, onClose: closeOnboarding, onApplyLang: applyLang, onRunCommand: (name) => {
4930
+ // 与落地页的 onAction 同一条规则:打开整屏界面的命令要先把向导收掉。
4931
+ // 走 `closeOnboarding('skipped')` 而不是直接置 false——用户是去试命令、
4932
+ // 没答完引导,按 skipped 的口径不记账(下次启动还会问)。
4933
+ if (!overlayCommandNames.has(name))
4934
+ closeOnboarding('skipped');
4935
+ void runCommand(name, '');
4936
+ } }));
4937
+ return fullscreen ? node : _jsx(AlternateScreen, { children: node });
4938
+ }
4112
4939
  /**
4113
4940
  * The session supervisor: a screen in the same sense as the tree — an early
4114
4941
  * return after every hook above has run, so there is no transcript
@@ -4125,7 +4952,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4125
4952
  * while they read the list, which is why closing the screen only repaints
4126
4953
  * the transcript when the attached session actually changed.
4127
4954
  */
4128
- if (supervisorOpen) {
4955
+ if (supervisorOpen && launchpadGate()) {
4129
4956
  /**
4130
4957
  * Live state per session, from the channel's own agent-view projection.
4131
4958
  * Reading the projection rather than a parallel source is what keeps this
@@ -4143,6 +4970,15 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4143
4970
  suppressLogoIntroRef.current = true;
4144
4971
  setAgentViewReturnId(undefined);
4145
4972
  setSupervisorOpen(false);
4973
+ // 第七版:明确选中一个会话 = 有意导航(与 Esc「退出」相对)——浏览页
4974
+ // 与盖在它底下的落地页**一起收**,人落在那个会话的聊天页。只收浏览页
4975
+ // 会露出启动页,正是用户实测的「选完会话还是回到启动页」。落地页的
4976
+ // 草稿/焦点也按 closeLaunchpad 同一口径清掉(进入的是别的会话,旧草稿
4977
+ // 不该跟过去)。
4978
+ setLaunchpadOpen(false);
4979
+ setLaunchpadFocus(-1);
4980
+ setLaunchpadDraft('');
4981
+ setLaunchpadCaret(0);
4146
4982
  repaintTranscript();
4147
4983
  return result;
4148
4984
  }, onNewSession: async (target) => {
@@ -4151,6 +4987,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4151
4987
  suppressLogoIntroRef.current = true;
4152
4988
  setAgentViewReturnId(undefined);
4153
4989
  setSupervisorOpen(false);
4990
+ // 同 onOpenSession:新建/切工作区会话也是有意导航,落地页一并收。
4991
+ setLaunchpadOpen(false);
4992
+ setLaunchpadFocus(-1);
4993
+ setLaunchpadDraft('');
4994
+ setLaunchpadCaret(0);
4154
4995
  repaintTranscript();
4155
4996
  }
4156
4997
  return ok;
@@ -4168,7 +5009,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4168
5009
  // conversation (an early return after every hook above has run), so there
4169
5010
  // is no transcript underneath to be repainted or bled through. The dropped
4170
5011
  // turn's prompt returns through the same fill path a rewind picker uses.
4171
- if (treeOpen) {
5012
+ if (treeOpen && launchpadGate()) {
4172
5013
  const tree = (_jsx(SessionTree, { channel: channel, currentSessionId: channel.agentId, onClose: () => setTreeOpen(false), onRestoreText: (text) => {
4173
5014
  // The tree rewound to a node and is handing that turn's prompt back,
4174
5015
  // exactly like the picker does. It belongs to the binding the tree
@@ -4181,18 +5022,18 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4181
5022
  // The settings screen follows the browser's rule exactly: it REPLACES the
4182
5023
  // conversation (an early return after every hook above has run), so there
4183
5024
  // is no transcript underneath to be repainted or bled through.
4184
- if (settingsOpen) {
5025
+ if (settingsOpen && launchpadGate()) {
4185
5026
  const screen = _jsx(Settings, { channel: channel, onClose: () => setSettingsOpen(false) });
4186
5027
  return fullscreen ? screen : _jsx(AlternateScreen, { children: screen });
4187
5028
  }
4188
5029
  // Subagent detail scene: displays detailed view of a specific subagent.
4189
5030
  // Like the browser and settings, it replaces the conversation entirely.
4190
- if (subagentDetailId !== null) {
5031
+ if (subagentDetailId !== null && launchpadGate()) {
4191
5032
  const subagent = channel.subagents.find(s => s.agentId === subagentDetailId);
4192
5033
  if (!subagent) {
4193
- // Agent not found, go back to dashboard
5034
+ // Agent not found, go back to the dashboard (side panel when split).
4194
5035
  setSubagentDetailId(null);
4195
- setSubagentDashboardOpen(true);
5036
+ openSubagentDashboard();
4196
5037
  return null;
4197
5038
  }
4198
5039
  const scene = (_jsx(SubagentDetailScene, { subagent: subagent, onInterrupt: (id) => channel.subagentControl.interrupt(id), onBack: () => {
@@ -4203,8 +5044,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4203
5044
  }
4204
5045
  // Jobs panel: background jobs (running/killed) with kill/inspect actions.
4205
5046
  // Like the browser and settings, it replaces the conversation entirely.
4206
- if (jobsPanelOpen) {
4207
- const panel = (_jsx(JobsPanel, { jobs: channel.backgroundJobs ?? [], onClose: () => setJobsPanelOpen(false), onKill: (id) => {
5047
+ if (jobsPanelOpen && launchpadGate()) {
5048
+ const panel = (_jsx(JobsPanel, { jobs: channel.backgroundJobs ?? [], initialFocusId: jobsPanelFocusId ?? undefined, onClose: () => { setJobsPanelOpen(false); setJobsPanelFocusId(null); }, onKill: (id) => {
4208
5049
  // Stub channels (verify harnesses) have no jobControl — surface
4209
5050
  // the same failure toast as a refused kill instead of throwing.
4210
5051
  if (channel.jobControl?.kill(id) !== true) {
@@ -4215,7 +5056,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4215
5056
  }
4216
5057
  // Subagent dashboard: displays all active and completed subagents.
4217
5058
  // Like the browser and settings, it replaces the conversation entirely.
4218
- if (subagentDashboardOpen) {
5059
+ if (subagentDashboardOpen && launchpadGate()) {
4219
5060
  const dashboard = (_jsx(SubagentDashboard, { subagents: [...channel.subagents], onSelect: (id) => {
4220
5061
  setSubagentDashboardOpen(false);
4221
5062
  setSubagentDetailId(id);
@@ -4235,7 +5076,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4235
5076
  || (recap !== null && (!recap.auto || recap.expanded))
4236
5077
  || btw !== null
4237
5078
  || questionPanelNode !== null
4238
- || starModal !== null;
5079
+ || starModal !== null
5080
+ || couponVisible;
4239
5081
  // The trajectory scene replaces the conversation for as long as it is open.
4240
5082
  // Rendering it INSTEAD of (not above) the transcript is what makes it a
4241
5083
  // screen rather than an overlay: it owns the full viewport, and the
@@ -4244,10 +5086,146 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4244
5086
  // `<AlternateScreen>` is skipped when the app is already fullscreen —
4245
5087
  // nesting it would emit a second DEC 1049, and its unmount would drop the
4246
5088
  // whole app back to the main screen.
4247
- if (sceneOpen) {
5089
+ if (sceneOpen && launchpadGate()) {
4248
5090
  const scene = _jsx(TrajectoryScene, { channel: channel, build: trajectory, onClose: closeScene });
4249
5091
  return fullscreen ? scene : _jsx(AlternateScreen, { children: scene });
4250
5092
  }
5093
+ /**
5094
+ * The launchpad — the landing page an ordinary launch starts on.
5095
+ *
5096
+ * 第七版渲染姿态:early-return 排在**所有整屏界面(会话浏览器 / 设置 /
5097
+ * 任务面板 / 家谱 / 子代理 / 轨迹场景)之后**——从落地页打开的整屏盖在
5098
+ * 落地页之上,Esc 退出整屏回到这里(草稿/参数/焦点原样保留)。硬约束:
5099
+ * 从启动页进入对话页的唯一路径是**提交一条非命令消息**(Enter 发送);
5100
+ * 任何 Esc/返回都回到启动页。参数行点开的选择器经 `pickerPanels` +
5101
+ * `inputPaused` 渲染在本屏之上,Esc 关掉它也回到这里。
5102
+ */
5103
+ if (launchpadShown) {
5104
+ // 参数行的「模式/权限」两段(第三版):模式看 channel.mode.plan;权限看
5105
+ // permissionPresets() 的当前身份——只认 runtime 名册,legacy/unavailable 与
5106
+ // 抛错都按「拿不到」处理(缺省不画那一段,两段都可选)。
5107
+ let launchpadPermission;
5108
+ try {
5109
+ const snapshot = channel.permissionPresets();
5110
+ launchpadPermission = snapshot.availability === 'runtime' ? snapshot.current?.name : undefined;
5111
+ }
5112
+ catch {
5113
+ launchpadPermission = undefined;
5114
+ }
5115
+ // 第七版动作表:状态快照全部来自既有数据源(详见 launchpadActions.ts)——
5116
+ // - lastSessionTitle = agentViewRows(含持久化名册)里最近一条非当前会话
5117
+ // 的标题——listing 是异步的,落地前没有 Continue、落地后自动长出来;
5118
+ // - jobsRunning / updateAvailable / starDue = 条件位三连(后台任务面 /
5119
+ // checkForTuiUpdate / usageStats 里程碑口径)。
5120
+ const resumableRows = agentViewRows
5121
+ .filter(row => !row.current && row.id !== channel.agentId && row.title.trim() !== '');
5122
+ const latestRow = resumableRows.reduce((best, row) => (best === undefined || row.updatedAt > best.updatedAt ? row : best), undefined);
5123
+ const launchpadActions = resolveLaunchpadActions({
5124
+ lastSessionTitle: latestRow?.title,
5125
+ // 条件位①:后台任务面是真数据(JobsPanel 同一个 channel.backgroundJobs),
5126
+ // running/stopping 都算「在跑」。
5127
+ jobsRunning: (channel.backgroundJobs ?? []).some(job => job.status === 'running' || job.status === 'stopping'),
5128
+ updateAvailable: launchpadUpdateAvailable,
5129
+ starDue: launchpadStarDue,
5130
+ });
5131
+ const node = (_jsx(Launchpad, { query: launchpadDraft, cursorOffset: launchpadCaret, focusIndex: launchpadFocus, isTerminalFocused: terminalFocused, whale: channel.whale, whaleIdle: channel.whaleIdle, whaleGirl: channel.whaleGirl, fontId: splashFontIdOf(channel.splashFont), starred: starred, onStarClick: runStarAction, firstRun: onboardingPending, actions: launchpadActions,
5132
+ // 参数行四段点开的既有选择器(第五版):pickerPanels 与聊天页共用
5133
+ // 同一份 JSX,盖在落地页之上;选择器开着时落地页键盘让位(inputPaused)。
5134
+ overlayPanel: launchpadOverlayUp ? pickerPanels : undefined, inputPaused: launchpadOverlayUp, onParamPick: (segment) => {
5135
+ // 四段 → 既有命令:model→/model、effort→/effort、preset→/preset、
5136
+ // permission→/permission。全部在 overlayCommandNames 白名单里,
5137
+ // 落地页不收,选择器盖上来。
5138
+ // 第八版切换式(用户原话「点一下是展开 再点一下收起来」):**同一段**
5139
+ // 再点/再按 Enter = 收起(展开 ↔ 收起,键盘与鼠标同一条 onParamPick)。
5140
+ // 点另一段仍直接切换,点空白/Esc 仍收起(上一版契约不回退)。
5141
+ if (overlay.kind === segment) {
5142
+ dispatchOverlay({ type: 'close' });
5143
+ return;
5144
+ }
5145
+ // BUG 3(点另一段直接切换):/effort 的选择器是异步 open-if(when:
5146
+ // ['none']),盖着别的选择器时会被丢弃——这就是"点了没反应"的根因。
5147
+ // 先把屏上的参数选择器收掉再开新的,一个在屏、且就是点的那段。
5148
+ if (overlay.kind !== 'none') {
5149
+ dispatchOverlay({ type: 'close' });
5150
+ }
5151
+ void runCommand(segment, '');
5152
+ }, onQueryChange: (text, cursor) => {
5153
+ setLaunchpadDraft(text);
5154
+ setLaunchpadCaret(cursor);
5155
+ }, onSubmit: closeLaunchpad, onFocusChange: setLaunchpadFocus, onAction: (action) => {
5156
+ // 第八版(用户实测:「刚点帮助,不知道为什么直接进入聊天页面了」):
5157
+ // 帮助**不属于**离开启动页的两条路(Enter 发非命令消息 / 会话浏览
5158
+ // 里选中会话)——它盖在落地页之上(overlay kind 'help'),再点同一
5159
+ // 入口 = 收起(与参数段同一条切换语义)。
5160
+ if (action.command === 'help') {
5161
+ dispatchOverlay(overlay.kind === 'help'
5162
+ ? { type: 'close' }
5163
+ : { type: 'open', overlay: { kind: 'help' } });
5164
+ return;
5165
+ }
5166
+ // 第七版姿态:覆盖层命令(overlayCommandNames)盖在落地页之上;
5167
+ // **整屏命令**(launchpadScreenCommands:会话与工作区 / 设置 / 后台
5168
+ // 任务 / 家谱 / 子代理 / 引导)也盖在落地页之上——落地页不收,
5169
+ // Esc 退出整屏回到落地页(草稿/参数/焦点都在)。整屏要先过
5170
+ // launchpadGate 的授权位(防御:异步误置的整屏状态盖不走启动页)。
5171
+ // 其余命令(star / update——反馈在对话页的转录/通知)仍收掉落地页
5172
+ // 再执行,这是用户主动执行命令,不是「返回」。
5173
+ if (launchpadScreenCommands.has(action.command)) {
5174
+ authorizeLaunchpadCover();
5175
+ }
5176
+ else if (!overlayCommandNames.has(action.command)) {
5177
+ setLaunchpadOpen(false);
5178
+ }
5179
+ void runCommand(action.command, '');
5180
+ }, onEscape: (intent) => {
5181
+ if (intent === 'exit') {
5182
+ requestExit();
5183
+ return;
5184
+ }
5185
+ // 空输入按 Esc:这一屏的"下一步"通常是去挑工作区/会话。第七版:
5186
+ // 会话浏览器**盖在落地页之上**(落地页不收)——Esc 退出浏览器回到
5187
+ // 落地页,绝不落到对话页。这是从落地页出发的交互:过闸门授权。
5188
+ agentViewOpenSessionRef.current = channel.agentId;
5189
+ authorizeLaunchpadCover();
5190
+ setSupervisorOpen(true);
5191
+ }, onBlankClick: () => {
5192
+ // BUG 3(点别处关掉选择器):沿用"点空白收回焦点"的兜底路径——
5193
+ // 有参数选择器盖在落地页之上时,空白点击先把选择器收掉(焦点照旧
5194
+ // 收回输入框)。选择器内部的点击已被 Launchpad 的浮层包装拦住
5195
+ // 冒泡,不会走到这里。
5196
+ if (overlay.kind !== 'none')
5197
+ dispatchOverlay({ type: 'close' });
5198
+ setLaunchpadFocus(-1);
5199
+ }, model: channel.model, effort: channel.reasoningEffort, preset: channel.agentPreset === undefined
5200
+ ? undefined
5201
+ : presetOptions.find(option => option.id === channel.agentPreset)?.name ?? channel.agentPreset, permission: launchpadPermission, commands: launchpadOverlayUp || !launchpadDraft.startsWith('/')
5202
+ ? undefined
5203
+ : channel.commandCompletions(launchpadDraft), onCommandPick: (commandLine) => {
5204
+ // 补全面板选中(Enter/Tab/点击):走 runCommand,与快捷入口同一条
5205
+ // 白名单口径——覆盖层与整屏命令(第七版 launchpadScreenCommands)
5206
+ // 不收落地页(盖在它之上),其余收掉再执行。/help 与快捷入口同一条
5207
+ // 拦截:盖屏浮层,不进对话页(第八版)。
5208
+ const parsed = parseCommandName(commandLine);
5209
+ if (parsed === undefined)
5210
+ return;
5211
+ if (parsed.name === 'help') {
5212
+ dispatchOverlay({ type: 'open', overlay: { kind: 'help' } });
5213
+ return;
5214
+ }
5215
+ if (launchpadScreenCommands.has(parsed.name)) {
5216
+ authorizeLaunchpadCover();
5217
+ }
5218
+ else if (!overlayCommandNames.has(parsed.name)) {
5219
+ setLaunchpadOpen(false);
5220
+ }
5221
+ void runCommand(parsed.name, parsed.rawInput);
5222
+ }, cwd: channel.displayCwd, branch: channel.gitBranch, tuiVersion: tuiVersion, kernelVersion: kernelVersion,
5223
+ // 左下角工作目录铭牌(第七版):点开/回车开既有 /workspace 菜单——
5224
+ // workspace-menu 在 LAUNCHPAD_OVERLAY_KINDS 里,选择器盖在落地页之上,
5225
+ // Esc 回落地页(与参数行选择器同一姿态),不新造面板。
5226
+ onOpenWorkspace: () => { void runCommand('workspace', ''); } }));
5227
+ return fullscreen ? node : _jsx(AlternateScreen, { children: node });
5228
+ }
4251
5229
  // 浮层整体挂载条件:与内部各面板的可见条件同值(数据门在
4252
5230
  // dialogOverlayVisible 里逐面板镜像)。关闭时把整个 absolute 浮层从树里
4253
5231
  // 移除——渲染器的"移除 absolute 节点"检测只看被移除子树自身的
@@ -4258,6 +5236,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4258
5236
  workspaceTargetCount: workspaceTargets.length,
4259
5237
  effortOptionCount: effortOptions.length,
4260
5238
  presetOptionCount: presetOptions.length,
5239
+ panelCount: panelPickerRows.length,
4261
5240
  }) && !(overlay.kind === 'permission'
4262
5241
  && (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null));
4263
5242
  // The sticky header pins the turn owning the viewport top row once its
@@ -4280,8 +5259,8 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4280
5259
  // the transcript viewport height from the ScrollBox handle and the content
4281
5260
  // column width. The full-screen (editor-open) placement uses the terminal.
4282
5261
  const imagePreviewRegion = promptEditorOpen
4283
- ? { columns: terminalColumns, rows: terminalRows }
4284
- : { columns: terminalColumns, rows: handle?.getViewportHeight() ?? terminalRows };
5262
+ ? { columns: chatColumns, rows: terminalRows }
5263
+ : { columns: chatColumns, rows: handle?.getViewportHeight() ?? terminalRows };
4285
5264
  const imagePreviewNode = activePreview !== null && (activePreview.peek || imagePreviewOwned)
4286
5265
  ? (_jsx(ImagePreviewOverlay, { image: activePreview.image, title: activePreview.title, navigation: previewGallery.length > 1 && previewIndex >= 0 ? {
4287
5266
  index: previewIndex, total: previewGallery.length,
@@ -4290,251 +5269,119 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
4290
5269
  ? () => setPeekSuppressed(peekKey(activePreview.image, activePreview.title))
4291
5270
  : () => dispatchOverlay({ type: 'close-if', kind: 'image-preview' }), region: imagePreviewRegion }))
4292
5271
  : null;
4293
- return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [!isSticky && timeline.activeId !== null && (_jsx(PinnedTurnHeader, { text: anchorUserText || null, onClick: () => {
4294
- // Click snaps the pinned prompt to the viewport top. Jump by the
4295
- // SAME content coordinate the
4296
- // rail's tick uses (timeline turn top = the prompt TEXT top):
4297
- // the element-based seek lands the row wrapper's margin at the
4298
- // top instead — one row shy of the text top the anchor rule
4299
- // compares against — and the header would flip to the previous
4300
- // turn immediately after the click.
4301
- const turn = timeline.turns.find(t => t.id === anchorUserRowId);
4302
- if (turn)
4303
- handle?.scrollTo(turn.top);
4304
- else if (anchorUserRowId !== null)
4305
- seekRow(anchorUserRowId);
4306
- else
4307
- handle?.scrollToBottom();
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,
4312
- // Resuming a long session skips the ~3.4s opening animation: it
4313
- // keeps firing low-frequency React commits that compete with the
4314
- // transcript mount batches (and the first wheel events) for the
4315
- // frame budget right when the user wants to read history. Fresh
4316
- // sessions keep the full intro; restored ones settle instantly.
4317
- // A remount after a whole screen closed also settles instantly
4318
- // (see suppressLogoIntroRef).
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 })] }), (() => {
4320
- // Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
4321
- // rail (default), the proportional scrollbar, or nothing. The
4322
- // slot keeps its 2 columns in both rendered modes (Qwen's
4323
- // permanent-gutter rule — an appearing/disappearing gutter
4324
- // changes the transcript width and rewraps everything).
4325
- const gutter = normalizeScrollGutter(channel.scrollGutter);
4326
- if (gutter === 'hidden')
4327
- return null;
4328
- if (gutter === 'scrollbar') {
4329
- return _jsx(ScrollbarGutter, { handle: handle, terminalWidth: terminalColumns });
4330
- }
4331
- return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: terminalColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
4332
- })(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
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,
4337
- // Upload = real tokens of the last request; download =
4338
- // the animated chars/4 estimate, matching the classic
4339
- // spinner's counter (the suffix used raw chars before,
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, {
4344
- React,
4345
- ui: STATUS_VIEW_UI,
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: () => {
4347
- // An expanded auto recap collapses back to its dim row;
4348
- // a manual /recap closes outright.
4349
- if (recap.auto) {
4350
- setRecap(prev => (prev ? { ...prev, expanded: false } : prev));
4351
- }
4352
- else {
4353
- closeRecap();
4354
- }
4355
- }, onCopy: () => {
4356
- void setClipboard(recap.summary ?? '').then(raw => { if (raw)
4357
- writeRaw?.(raw); });
4358
- channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
4359
- }, onApplyTitle: () => {
4360
- if (recap.title === undefined || recap.titleApplied)
4361
- return;
4362
- channel.renameSession(recap.title);
4363
- setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
4364
- channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
4365
- } }) })) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
4366
- void setClipboard(btw.answer ?? '').then(raw => { if (raw)
4367
- writeRaw?.(raw); });
4368
- channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
4369
- } }) })) : questionPanelNode !== null ? (questionPanelNode) : null, _jsx(PromptInput, { channel: channel, suspended: promptReplacementOpen, draftCache: promptDraftRef.current, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => setHistoryFill(null), onRewindRequest: openRewind, onBackgroundRequest: backgroundToAgentView,
4370
- // The 🏠 at the head of the input row opens the same session screen
4371
- // `/resume` and `/agentview` open — one surface, three doors. It is
4372
- // gated on this prop rather than a setting, so hosts that mount the
4373
- // prompt without a session screen (and the layout regressions that
4374
- // pin the row's column budget) keep the row they had.
4375
- onOpenSessions: () => {
4376
- agentViewOpenSessionRef.current = channel.agentId;
4377
- setSupervisorOpen(true);
4378
- }, backgroundAgentsNeedingInput:
4379
- // Only the real channel supplies the seam; pre-agent-view test
4380
- // stubs must not grow the footer row (layout-dependent
4381
- // regressions pin the visible row count). The footer only
4382
- // renders while some session actually waits (N > 0): a
4383
- // permanent idle row would steal a transcript row on every
4384
- // real channel — one row is enough to scroll the startup
4385
- // header fully off a short terminal, pausing its viewport
4386
- // clock and shifting every row-count layout invariant.
4387
- channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
4388
- ? backgroundAgentsNeedingInput
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
4390
- ? undefined
4391
- : {
4392
- band: wakeBand,
4393
- hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
4394
- tick: Math.floor(wakeTime / 120),
4395
- } }), dialogOverlayOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: [overlay.kind === 'thinking' && (_jsx(ThinkingToggle, { currentValue: thinkingVisible, focusIndex: overlay.focus, onPick: (index) => {
4396
- // 点击行 = 设焦点 + 应用(与 Enter 同一条路径)
4397
- const visible = index === 0;
4398
- setThinkingVisible(visible);
4399
- dispatchOverlay({ type: 'close' });
4400
- channel.notify(t('thinking-toggled', { state: visible ? t('thinking-on') : t('thinking-off') }));
4401
- } })), overlay.kind === 'workspace-picker' && workspaceTargets.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspacePicker, { targets: workspaceTargets, focusIndex: overlay.index, currentCwd: channel.cwd, onPick: (index) => {
4402
- // 点击行 = 切换该行目标(与 Enter 同一条路径)
4403
- const target = workspaceTargets[index];
4404
- dispatchOverlay({ type: 'close' });
4405
- if (target !== undefined)
4406
- void channel.switchWorkspace(target);
4407
- } }) })), overlay.kind === 'workspace-menu' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceMenuPicker, { options: workspaceMenuOptions, focusIndex: overlay.index, onPick: (index) => {
4408
- // 点击行 = 执行该行(与 Enter 同一条路径)
4409
- runWorkspaceMenuOption(workspaceMenuOptions[index]);
4410
- } }) })), overlay.kind === 'workspace-flow' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(WorkspaceFlowPicker, { title: overlay.flow.title, choices: overlay.flow.choices, focusIndex: overlay.index, busy: overlay.busy, input: overlay.input, onPick: (index) => {
4411
- // 点击行 = 设焦点 + 执行分支(与 Enter 同一条路径);
4412
- // busy/输入态在组件侧禁点
4413
- const choice = overlay.flow.choices[index];
4414
- if (choice === undefined)
4415
- return;
4416
- dispatchOverlay({ type: 'set-index', kind: 'workspace-flow', index });
4417
- runWorkspaceFlowAction(signal => choice.choose(signal));
4418
- } }) })), overlay.kind === 'model' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: models.length === 0 ? (_jsx(ModelPickerLoading, {})) : activeModelGroup === undefined ? (_jsx(ModelPicker, { groups: modelGroups, focusIndex: overlay.index, currentProvider: channel.provider, onPick: (index) => {
4419
- // 点击分组行 = 进入该组(与 Enter 同一条路径)
4420
- const group = modelGroups[index];
4421
- if (!group)
4422
- return;
4423
- setModelGroup(group.provider);
4424
- if (group.provider === RECENTS_GROUP_PROVIDER) {
4425
- dispatchOverlay({ type: 'set-index', kind: 'model', index: 0 });
4426
- return;
5272
+ return (_jsxs(Box, { ref: wakeTickRef, flexDirection: "column", flexGrow: 1, width: "100%", children: [_jsxs(SidePanelLayout, { geometry: sidePanel.geometry, focus: sidePanel.focus, onActivateChat: sidePanel.focusChat, onActivatePanel: sidePanel.focusPanel, side: _jsx(SidePanelColumn, { width: sidePanel.panelColumns, controller: sidePanel, channel: channel, activity: workingActivity, attention: {
5273
+ approvals: approvalSnapshot !== null ? 1 : 0,
5274
+ questions: questionSnapshot !== null ? 1 : 0,
5275
+ }, trajectory: trajectory, onExpand: openPanelFullscreen }), children: [!isSticky && timeline.activeId !== null && (_jsx(PinnedTurnHeader, { text: anchorUserText || null, onClick: () => {
5276
+ // Click snaps the pinned prompt to the viewport top. Jump by the
5277
+ // SAME content coordinate the
5278
+ // rail's tick uses (timeline turn top = the prompt TEXT top):
5279
+ // the element-based seek lands the row wrapper's margin at the
5280
+ // top instead — one row shy of the text top the anchor rule
5281
+ // compares against — and the header would flip to the previous
5282
+ // turn immediately after the click.
5283
+ const turn = timeline.turns.find(t => t.id === anchorUserRowId);
5284
+ if (turn)
5285
+ handle?.scrollTo(turn.top);
5286
+ else if (anchorUserRowId !== null)
5287
+ seekRow(anchorUserRowId);
5288
+ else
5289
+ handle?.scrollToBottom();
5290
+ } })), _jsxs(Box, { flexDirection: "row", flexGrow: 1, flexShrink: 1, marginRight: sidePanel.split ? 0 : -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,
5291
+ // 大字字面(设置项 `dsh-tui.splashFont`):`daily` 交回按天轮换
5292
+ // (`undefined`),其余 pin 住一款。
5293
+ fontId: splashFontIdOf(channel.splashFont), whale: channel.whale, whaleIdle: channel.whaleIdle && whaleArtVisible, whaleGirl: channel.whaleGirl, starred: starred, onStarClick: runStarAction, working: channel.working,
5294
+ // Resuming a long session skips the ~3.4s opening animation: it
5295
+ // keeps firing low-frequency React commits that compete with the
5296
+ // transcript mount batches (and the first wheel events) for the
5297
+ // frame budget right when the user wants to read history. Fresh
5298
+ // sessions keep the full intro; restored ones settle instantly.
5299
+ // A remount after a whole screen closed also settles instantly
5300
+ // (see suppressLogoIntroRef).
5301
+ 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, jobGroupFold: channel.jobGroupFold, 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 })] }), (() => {
5302
+ // Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
5303
+ // rail (default), the proportional scrollbar, or nothing. The
5304
+ // slot keeps its 2 columns in both rendered modes (Qwen's
5305
+ // permanent-gutter rule — an appearing/disappearing gutter
5306
+ // changes the transcript width and rewraps everything).
5307
+ const gutter = normalizeScrollGutter(channel.scrollGutter);
5308
+ if (gutter === 'hidden')
5309
+ return null;
5310
+ if (gutter === 'scrollbar') {
5311
+ return _jsx(ScrollbarGutter, { handle: handle, terminalWidth: chatColumns });
5312
+ }
5313
+ return (_jsx(TimelineRail, { handle: handle, turns: timeline.turns, activeId: timeline.activeId, upId: timeline.upId, downId: timeline.downId, terminalWidth: chatColumns, hoverEnabled: !promptSelectionActive, onRevealTurn: revealAndSeekRow }));
5314
+ })(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
5315
+ (activitySlot &&
5316
+ workingActivity !== undefined &&
5317
+ workingActivity.line !== '' &&
5318
+ workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
5319
+ // Upload = real tokens of the last request; download =
5320
+ // the animated chars/4 estimate, matching the classic
5321
+ // spinner's counter (the suffix used raw chars before,
5322
+ // inflating the reading next to a real upload number). An
5323
+ // automatic compaction mid-turn badges THIS line too — it is
5324
+ // the spinner slot whenever real activity data exists.
5325
+ 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 })), !(sidePanel.split && sidePanel.enabledPanelIds.includes('todo')) && (_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, {
5326
+ React,
5327
+ ui: STATUS_VIEW_UI,
5328
+ }) }) }) }, `${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: () => {
5329
+ // An expanded auto recap collapses back to its dim row;
5330
+ // a manual /recap closes outright.
5331
+ if (recap.auto) {
5332
+ setRecap(prev => (prev ? { ...prev, expanded: false } : prev));
4427
5333
  }
4428
- const landing = modelPickerLanding(models.filter(model => model.provider === group.provider), channel.provider, channel.model);
4429
- dispatchOverlay({ type: 'set-index', kind: 'model', index: landing.index });
4430
- } })) : (_jsx(ModelPicker, { models: groupModels, groupLabel: activeModelGroup === RECENTS_GROUP_PROVIDER
4431
- ? t('picker-group-recent')
4432
- : modelGroups.find(group => group.provider === activeModelGroup)?.label, showBack: modelGroups.length > 1 && !modelPickerDirect, showProviderPrefix: activeModelGroup === RECENTS_GROUP_PROVIDER, focusIndex: overlay.index, currentModel: `${channel.provider}/${channel.model}`, onPick: (index) => {
4433
- // 点击行 = 应用该行模型(与 Enter 同一条路径)
4434
- const model = groupModels[index];
4435
- if (!model)
4436
- return;
4437
- dispatchOverlay({ type: 'close' });
4438
- void switchModelRecorded(model.provider, model.id, model.name);
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) => {
4452
- const skill = skillsList[index];
4453
- if (!skill)
4454
- return;
4455
- dispatchOverlay({ type: 'close' });
4456
- if (skill.userInvocable)
4457
- setHistoryFill(`/${skill.name} `);
4458
- } })) })), overlay.kind === 'activity' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ActivityPicker, { focusIndex: overlay.index, currentPreset: channel.activityFrames, onPick: (index) => {
4459
- dispatchOverlay({ type: 'close' });
4460
- const name = PRESET_NAMES[index];
4461
- if (name)
4462
- channel.setActivityFrames(name);
4463
- } }) })), overlay.kind === 'color' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ColorPicker, { focusIndex: overlay.index, currentColor: channel.sessionColor, onPick: (index) => {
4464
- dispatchOverlay({ type: 'close' });
4465
- const name = SESSION_COLOR_NAMES[index];
4466
- if (name) {
4467
- channel.setSessionColor(name);
4468
- channel.notify(t('color-set', { name }), { color: 'success' });
4469
- }
4470
- } }) })), overlay.kind === 'effort' && effortOptions.length > 1 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(EffortSlider, { options: effortOptions, focusIndex: overlay.index, currentId: channel.reasoningEffort,
4471
- // 点击档位 = 移到该档并即时应用(与 ←/→ 同语义)
4472
- onPick: (index) => {
4473
- dispatchOverlay({ type: 'set-index', kind: 'effort', index });
4474
- const option = effortOptions[index];
4475
- if (option)
4476
- void channel.setEffort(option.id);
4477
- } }) })), overlay.kind === 'preset' && presetOptions.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PresetPicker, { presets: presetOptions, focusIndex: overlay.index, currentPreset: channel.agentPreset, onPick: (index) => {
4478
- dispatchOverlay({ type: 'close' });
4479
- const option = presetOptions[index];
4480
- if (option)
4481
- void channel.switchPreset(option.id);
4482
- } }) })), overlay.kind === 'permission' && overlay.snapshot.options.length > 0 && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PermissionsPicker, { options: overlay.snapshot.options, focusIndex: overlay.index, currentValue: overlay.snapshot.current?.value, cwd: channel.cwd, onPick: (index) => {
4483
- if (approvalSnapshot !== null || questionSnapshot !== null || dialogSnapshot !== null)
4484
- return;
4485
- const option = overlay.snapshot.options[index];
4486
- dispatchOverlay({ type: 'close' });
4487
- if (option !== undefined)
4488
- void runPermissionCommand(` ${option.value}`);
4489
- } }) })), overlay.kind === 'plan' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(PlanPicker, { focusIndex: overlay.index, currentOn: channel.mode.plan === true, onPick: (index) => {
4490
- dispatchOverlay({ type: 'close' });
4491
- const on = index === 0;
4492
- void runExternalCommand('plan', on ? '' : ' off');
4493
- } }) })), overlay.kind === 'lang' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(LangPicker, { focusIndex: overlay.index, currentLang: getLang(), onPick: (index) => {
4494
- const lang = LANGS[index];
4495
- if (lang === undefined)
4496
- return;
4497
- dispatchOverlay({ type: 'close' });
4498
- applyLang(lang);
4499
- } }) })), overlay.kind === 'theme' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(ThemePicker, { focusIndex: overlay.index, currentTheme: themeName, themeHost: themeHost, onPick: (index) => {
4500
- dispatchOverlay({ type: 'close' });
4501
- const name = getThemeOptions(themeHost)[index]?.value;
4502
- if (name !== undefined) {
4503
- const ok = setTheme(name);
4504
- channel.notify(ok ? t('theme-switched-saved', { name }) : t('theme-switch-failed', { name }), { color: ok ? 'success' : 'error' });
5334
+ else {
5335
+ closeRecap();
4505
5336
  }
4506
- } }) })), overlay.kind === 'history' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(HistorySearchDialog, { query: overlay.query, cursorOffset: overlay.cursor, matches: historyMatches, focusIndex: overlay.focus, onPick: (index) => {
4507
- // 点击行 = 填入该历史命令(与 Enter 同路径)
4508
- const entry = historyMatches[index];
4509
- if (entry) {
4510
- setHistoryFill(entry.text);
4511
- dispatchOverlay({ type: 'close' });
4512
- }
4513
- } }) })), overlay.kind === 'rewind' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(RewindPicker, { rows: rewindRows, focusIndex: overlay.index, confirmRow: overlay.confirm, modes: overlay.modes, modeIndex: overlay.modeIndex, busy: overlay.busy, onPickRow: (index) => {
4514
- // 列表页点击只选中:进入确认态保留键盘 Enter 显式触发
4515
- dispatchOverlay({ type: 'set-index', kind: 'rewind', index });
4516
- }, onConfirm: () => {
4517
- // 确认页即显式确认层,点击直接执行(与 Enter 同路径)
4518
- const row = overlay.confirm;
4519
- if (row === null)
4520
- return;
4521
- dispatchOverlay({ type: 'close' });
4522
- void performRewind(row);
4523
- }, onPickMode: (index) => {
4524
- // 模式列表点击直接执行该模式(与 Enter 同路径)
4525
- const row = overlay.confirm;
4526
- if (row === null)
5337
+ }, onCopy: () => {
5338
+ void setClipboard(recap.summary ?? '').then(raw => { if (raw)
5339
+ writeRaw?.(raw); });
5340
+ channel.notify(t('copied-chars', { n: (recap.summary ?? '').length }), { timeoutMs: 1500 });
5341
+ }, onApplyTitle: () => {
5342
+ if (recap.title === undefined || recap.titleApplied)
4527
5343
  return;
4528
- // 模式页仅当 modes 非空才渲染,这里空安全取值
4529
- const mode = index === 0 ? null : (overlay.modes?.[index - 1]?.id ?? null);
4530
- dispatchOverlay({ type: 'close' });
4531
- void performRewind(row, mode);
4532
- } }) })), overlay.kind === 'file-actions' && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(FileActionsPanel, { path: overlay.path, isDir: overlay.isDir, focusIndex: overlay.index, onPick: (index) => {
4533
- // 点击行直接执行该动作(与 Enter 同路径)
4534
- const path = overlay.path;
4535
- dispatchOverlay({ type: 'close' });
4536
- runFileAction(index, path);
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 }))] }));
5344
+ channel.renameSession(recap.title);
5345
+ setRecap(prev => (prev ? { ...prev, titleApplied: true } : prev));
5346
+ channel.notify(t('recap-title-applied-notify', { title: recap.title }), { color: 'success' });
5347
+ } }) })) : btw !== null ? (_jsx(Box, { flexDirection: "column", marginTop: 1, children: _jsx(BtwPanel, { question: btw.question, answer: btw.answer, error: btw.error, streaming: !btw.done, onClose: closeBtw, onCopy: () => {
5348
+ void setClipboard(btw.answer ?? '').then(raw => { if (raw)
5349
+ writeRaw?.(raw); });
5350
+ channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
5351
+ } }) })) : questionPanelNode !== null ? (questionPanelNode) : null, _jsx(PromptInput, { channel: channel, suspended: promptReplacementOpen,
5352
+ // 宠物面板是活动面板时,通知由它的头顶气泡「说出来」,输入框上方
5353
+ // 不再重复弹同一条(error 色除外——可能要行动的信号永远走 toast)。
5354
+ // 渲染期判定(split + activePanelId),与气泡同一次提交切换,不会
5355
+ // 先闪一帧 toast 再消失。
5356
+ toastSuppressed: petSaysNotices, draftCache: promptDraftRef.current, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => setHistoryFill(null), onRewindRequest: openRewind, onBackgroundRequest: backgroundToAgentView,
5357
+ // The 🏠 at the head of the input row opens the same session screen
5358
+ // `/resume` and `/agentview` open — one surface, three doors. It is
5359
+ // gated on this prop rather than a setting, so hosts that mount the
5360
+ // prompt without a session screen (and the layout regressions that
5361
+ // pin the row's column budget) keep the row they had.
5362
+ onOpenSessions: () => {
5363
+ agentViewOpenSessionRef.current = channel.agentId;
5364
+ setSupervisorOpen(true);
5365
+ }, backgroundAgentsNeedingInput:
5366
+ // Only the real channel supplies the seam; pre-agent-view test
5367
+ // stubs must not grow the footer row (layout-dependent
5368
+ // regressions pin the visible row count). The footer only
5369
+ // renders while some session actually waits (N > 0): a
5370
+ // permanent idle row would steal a transcript row on every
5371
+ // real channel — one row is enough to scroll the startup
5372
+ // header fully off a short terminal, pausing its viewport
5373
+ // clock and shifting every row-count layout invariant.
5374
+ channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
5375
+ ? backgroundAgentsNeedingInput
5376
+ : 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
5377
+ ? undefined
5378
+ : {
5379
+ band: wakeBand,
5380
+ hint: trajectorySeen ? undefined : primaryComboString('trajectory'),
5381
+ tick: Math.floor(wakeTime / 120),
5382
+ onOpen: openScene,
5383
+ hoverHint: primaryComboString('trajectory'),
5384
+ } }), dialogOverlayOpen && (_jsx(OverlayAbove, { maxHeight: Math.max(terminalRows - 8, 1), children: pickerPanels }))] })] }), _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 })), couponVisible && coupon !== null && (_jsx(WhaleCouponPrompt, { notice: coupon, onShown: markCouponShown, onClose: closeCoupon }))] }));
4538
5385
  }
4539
5386
  /**
4540
5387
  * The pinned prompt header shown above the ScrollBox while the user has