@deepseek-harness-tui/dsh-tui 0.12.0 → 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 (692) hide show
  1. package/README.md +41 -5
  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 +8 -3
  276. package/cordis.patch.yml +7 -1
  277. package/guide/dsh-tui-guide/configuration.en.md +6 -1
  278. package/guide/dsh-tui-guide/configuration.md +6 -1
  279. package/guide/dsh-tui-guide/getting-started.en.md +6 -3
  280. package/guide/dsh-tui-guide/getting-started.md +4 -2
  281. package/guide/dsh-tui-guide/interaction.en.md +68 -4
  282. package/guide/dsh-tui-guide/interaction.md +49 -3
  283. package/guide/dsh-tui-guide/plugins.en.md +16 -6
  284. package/guide/dsh-tui-guide/plugins.md +14 -6
  285. package/guide/dsh-tui-guide/themes.en.md +7 -2
  286. package/guide/dsh-tui-guide/themes.md +5 -2
  287. package/guide/dsh-tui-guide/user-guide.en.md +124 -21
  288. package/guide/dsh-tui-guide/user-guide.md +96 -16
  289. package/lib/settings.json +215 -1
  290. package/lib/types/adapter/channel/ui-policy.d.ts +6 -2
  291. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  292. package/lib/types/adapter/channel/ui-policy.js +14 -1
  293. package/lib/types/adapter/index.d.ts +1 -1
  294. package/lib/types/adapter/index.js +1 -1
  295. package/lib/types/adapter/kernel/host-facade.d.ts +4 -1
  296. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  297. package/lib/types/adapter/kernel/host-facade.js +8 -1
  298. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  299. package/lib/types/adapter/kernel/runtime.js +13 -0
  300. package/lib/types/adapter/kernel/slices/index.d.ts +1 -0
  301. package/lib/types/adapter/kernel/slices/index.d.ts.map +1 -1
  302. package/lib/types/adapter/kernel/slices/index.js +3 -0
  303. package/lib/types/adapter/kernel/slices/panels.d.ts +3 -0
  304. package/lib/types/adapter/kernel/slices/panels.d.ts.map +1 -0
  305. package/lib/types/adapter/kernel/slices/panels.js +14 -0
  306. package/lib/types/adapter/ports/channel-capabilities.d.ts +48 -0
  307. package/lib/types/adapter/ports/channel-capabilities.d.ts.map +1 -0
  308. package/lib/types/adapter/ports/channel-capabilities.js +2 -0
  309. package/lib/types/adapter/ports/channel-catalog.d.ts +7 -0
  310. package/lib/types/adapter/ports/channel-catalog.d.ts.map +1 -1
  311. package/lib/types/adapter/ports/channel-display.d.ts +10 -0
  312. package/lib/types/adapter/ports/channel-display.d.ts.map +1 -1
  313. package/lib/types/adapter/ports/channel-ui.d.ts +62 -2
  314. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  315. package/lib/types/adapter/ports/channel-view.d.ts +94 -0
  316. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  317. package/lib/types/adapter/ports/channel.d.ts +1 -1
  318. package/lib/types/adapter/ports/channel.js +1 -1
  319. package/lib/types/adapter/ports/descriptor.d.ts +1 -1
  320. package/lib/types/adapter/ports/descriptor.js +1 -1
  321. package/lib/types/adapter/ports/index.d.ts +1 -1
  322. package/lib/types/adapter/ports/index.js +1 -1
  323. package/lib/types/adapter/ports/panels.d.ts +28 -0
  324. package/lib/types/adapter/ports/panels.d.ts.map +1 -0
  325. package/lib/types/adapter/ports/panels.js +8 -0
  326. package/lib/types/adapter/ports/workspace.d.ts +1 -1
  327. package/lib/types/adapter/ports/workspace.js +1 -1
  328. package/lib/types/adapter/spec/index.d.ts +2 -2
  329. package/lib/types/adapter/spec/index.js +2 -2
  330. package/lib/types/adapter/spec/profile.d.ts +6 -6
  331. package/lib/types/adapter/spec/profile.d.ts.map +1 -1
  332. package/lib/types/adapter/spec/profile.js +6 -6
  333. package/lib/types/adapter/spec/protocol-constants.d.ts +3 -3
  334. package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -1
  335. package/lib/types/adapter/spec/protocol-constants.js +5 -5
  336. package/lib/types/adapter/spec/tui-contributions.d.ts +4 -4
  337. package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -1
  338. package/lib/types/adapter/spec/tui-contributions.js +5 -5
  339. package/lib/types/adapter/standard/descriptor.js +1 -1
  340. package/lib/types/adapter/standard/index.d.ts +3 -3
  341. package/lib/types/adapter/standard/index.js +3 -3
  342. package/lib/types/adapter/standard/registry.d.ts +3 -2
  343. package/lib/types/adapter/standard/registry.d.ts.map +1 -1
  344. package/lib/types/adapter/standard/registry.js +6 -5
  345. package/lib/types/adapter/standard/tui-extension.d.ts +1 -1
  346. package/lib/types/adapter/standard/tui-extension.js +1 -1
  347. package/lib/types/adapter/upstream/index.d.ts +1 -0
  348. package/lib/types/adapter/upstream/index.d.ts.map +1 -1
  349. package/lib/types/adapter/upstream/index.js +1 -0
  350. package/lib/types/adapter/upstream/panels-driver.d.ts +18 -0
  351. package/lib/types/adapter/upstream/panels-driver.d.ts.map +1 -0
  352. package/lib/types/adapter/upstream/panels-driver.js +159 -0
  353. package/lib/types/api.d.ts +2 -0
  354. package/lib/types/api.d.ts.map +1 -1
  355. package/lib/types/api.js +3 -1
  356. package/lib/types/commands.d.ts.map +1 -1
  357. package/lib/types/commands.js +12 -4
  358. package/lib/types/components/ActivityLine.d.ts +6 -9
  359. package/lib/types/components/ActivityLine.d.ts.map +1 -1
  360. package/lib/types/components/ActivityLine.js +7 -8
  361. package/lib/types/components/Chat/JobCard.d.ts +18 -1
  362. package/lib/types/components/Chat/JobCard.d.ts.map +1 -1
  363. package/lib/types/components/Chat/JobCard.js +90 -22
  364. package/lib/types/components/Chat/JobGroupHeader.d.ts +27 -0
  365. package/lib/types/components/Chat/JobGroupHeader.d.ts.map +1 -0
  366. package/lib/types/components/Chat/JobGroupHeader.js +62 -0
  367. package/lib/types/components/GoalTodoPanel.d.ts +13 -1
  368. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  369. package/lib/types/components/GoalTodoPanel.js +55 -14
  370. package/lib/types/components/JobsPanel.d.ts +42 -2
  371. package/lib/types/components/JobsPanel.d.ts.map +1 -1
  372. package/lib/types/components/JobsPanel.js +147 -14
  373. package/lib/types/components/LogoV2.d.ts +43 -3
  374. package/lib/types/components/LogoV2.d.ts.map +1 -1
  375. package/lib/types/components/LogoV2.js +42 -15
  376. package/lib/types/components/MessageList.d.ts +8 -2
  377. package/lib/types/components/MessageList.d.ts.map +1 -1
  378. package/lib/types/components/MessageList.js +212 -18
  379. package/lib/types/components/OverlayAbove.d.ts +11 -1
  380. package/lib/types/components/OverlayAbove.d.ts.map +1 -1
  381. package/lib/types/components/OverlayAbove.js +2 -12
  382. package/lib/types/components/PageMargin.d.ts.map +1 -1
  383. package/lib/types/components/PageMargin.js +3 -1
  384. package/lib/types/components/PluginSceneBoundary.d.ts +1 -1
  385. package/lib/types/components/PluginSceneBoundary.js +1 -1
  386. package/lib/types/components/PromptInput.d.ts +20 -22
  387. package/lib/types/components/PromptInput.d.ts.map +1 -1
  388. package/lib/types/components/PromptInput.js +413 -156
  389. package/lib/types/components/SearchBox.d.ts +22 -5
  390. package/lib/types/components/SearchBox.d.ts.map +1 -1
  391. package/lib/types/components/SearchBox.js +17 -13
  392. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  393. package/lib/types/components/SubagentCard.js +17 -2
  394. package/lib/types/components/SubagentDashboard.d.ts +18 -3
  395. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  396. package/lib/types/components/SubagentDashboard.js +53 -5
  397. package/lib/types/components/SubagentDetailScene.d.ts +7 -1
  398. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  399. package/lib/types/components/SubagentDetailScene.js +54 -5
  400. package/lib/types/components/SurfaceEdges.d.ts +21 -0
  401. package/lib/types/components/SurfaceEdges.d.ts.map +1 -0
  402. package/lib/types/components/SurfaceEdges.js +7 -0
  403. package/lib/types/components/ThemePicker.d.ts +9 -0
  404. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  405. package/lib/types/components/ThemePicker.js +74 -10
  406. package/lib/types/components/ThemePreviewPane.d.ts +26 -0
  407. package/lib/types/components/ThemePreviewPane.d.ts.map +1 -0
  408. package/lib/types/components/ThemePreviewPane.js +99 -0
  409. package/lib/types/components/design-system/Divider.d.ts.map +1 -1
  410. package/lib/types/components/design-system/Divider.js +14 -9
  411. package/lib/types/components/launchpadActions.d.ts +73 -0
  412. package/lib/types/components/launchpadActions.d.ts.map +1 -0
  413. package/lib/types/components/launchpadActions.js +111 -0
  414. package/lib/types/components/launchpadLayout.d.ts +62 -0
  415. package/lib/types/components/launchpadLayout.d.ts.map +1 -0
  416. package/lib/types/components/launchpadLayout.js +176 -0
  417. package/lib/types/components/onboardingModel.d.ts +83 -0
  418. package/lib/types/components/onboardingModel.d.ts.map +1 -0
  419. package/lib/types/components/onboardingModel.js +128 -0
  420. package/lib/types/components/sidePanel/InfoPanel.d.ts +17 -0
  421. package/lib/types/components/sidePanel/InfoPanel.d.ts.map +1 -0
  422. package/lib/types/components/sidePanel/InfoPanel.js +124 -0
  423. package/lib/types/components/sidePanel/PanelBar.d.ts +45 -0
  424. package/lib/types/components/sidePanel/PanelBar.d.ts.map +1 -0
  425. package/lib/types/components/sidePanel/PanelBar.js +104 -0
  426. package/lib/types/components/sidePanel/PanelErrorBoundary.d.ts +21 -0
  427. package/lib/types/components/sidePanel/PanelErrorBoundary.d.ts.map +1 -0
  428. package/lib/types/components/sidePanel/PanelErrorBoundary.js +30 -0
  429. package/lib/types/components/sidePanel/PanelHost.d.ts +36 -0
  430. package/lib/types/components/sidePanel/PanelHost.d.ts.map +1 -0
  431. package/lib/types/components/sidePanel/PanelHost.js +81 -0
  432. package/lib/types/components/sidePanel/PanelPicker.d.ts +24 -0
  433. package/lib/types/components/sidePanel/PanelPicker.d.ts.map +1 -0
  434. package/lib/types/components/sidePanel/PanelPicker.js +45 -0
  435. package/lib/types/components/sidePanel/PanelStore.d.ts +55 -0
  436. package/lib/types/components/sidePanel/PanelStore.d.ts.map +1 -0
  437. package/lib/types/components/sidePanel/PanelStore.js +109 -0
  438. package/lib/types/components/sidePanel/SidePanelColumn.d.ts +31 -0
  439. package/lib/types/components/sidePanel/SidePanelColumn.d.ts.map +1 -0
  440. package/lib/types/components/sidePanel/SidePanelColumn.js +70 -0
  441. package/lib/types/components/sidePanel/SidePanelLayout.d.ts +54 -0
  442. package/lib/types/components/sidePanel/SidePanelLayout.d.ts.map +1 -0
  443. package/lib/types/components/sidePanel/SidePanelLayout.js +81 -0
  444. package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts +43 -0
  445. package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts.map +1 -0
  446. package/lib/types/components/sidePanel/SidePanelRuntimeContext.js +26 -0
  447. package/lib/types/components/sidePanel/TrajectoryPanel.d.ts +7 -0
  448. package/lib/types/components/sidePanel/TrajectoryPanel.d.ts.map +1 -0
  449. package/lib/types/components/sidePanel/TrajectoryPanel.js +263 -0
  450. package/lib/types/components/sidePanel/WorkspacePanel.d.ts +4 -0
  451. package/lib/types/components/sidePanel/WorkspacePanel.d.ts.map +1 -0
  452. package/lib/types/components/sidePanel/WorkspacePanel.js +171 -0
  453. package/lib/types/components/sidePanel/builtinPanels.d.ts +3 -0
  454. package/lib/types/components/sidePanel/builtinPanels.d.ts.map +1 -0
  455. package/lib/types/components/sidePanel/builtinPanels.js +229 -0
  456. package/lib/types/components/sidePanel/companion/CompanionPanel.d.ts +53 -0
  457. package/lib/types/components/sidePanel/companion/CompanionPanel.d.ts.map +1 -0
  458. package/lib/types/components/sidePanel/companion/CompanionPanel.js +528 -0
  459. package/lib/types/components/sidePanel/companion/SplashMascot.d.ts +32 -0
  460. package/lib/types/components/sidePanel/companion/SplashMascot.d.ts.map +1 -0
  461. package/lib/types/components/sidePanel/companion/SplashMascot.js +103 -0
  462. package/lib/types/components/sidePanel/companion/deepy.d.ts +75 -0
  463. package/lib/types/components/sidePanel/companion/deepy.d.ts.map +1 -0
  464. package/lib/types/components/sidePanel/companion/deepy.js +222 -0
  465. package/lib/types/components/sidePanel/companion/mood.d.ts +176 -0
  466. package/lib/types/components/sidePanel/companion/mood.d.ts.map +1 -0
  467. package/lib/types/components/sidePanel/companion/mood.js +212 -0
  468. package/lib/types/components/sidePanel/companion/pose.d.ts +43 -0
  469. package/lib/types/components/sidePanel/companion/pose.d.ts.map +1 -0
  470. package/lib/types/components/sidePanel/companion/pose.js +34 -0
  471. package/lib/types/components/sidePanel/companion/skins.d.ts +102 -0
  472. package/lib/types/components/sidePanel/companion/skins.d.ts.map +1 -0
  473. package/lib/types/components/sidePanel/companion/skins.js +408 -0
  474. package/lib/types/components/sidePanel/dimensions.d.ts +60 -0
  475. package/lib/types/components/sidePanel/dimensions.d.ts.map +1 -0
  476. package/lib/types/components/sidePanel/dimensions.js +75 -0
  477. package/lib/types/components/sidePanel/jobsFocusStore.d.ts +21 -0
  478. package/lib/types/components/sidePanel/jobsFocusStore.d.ts.map +1 -0
  479. package/lib/types/components/sidePanel/jobsFocusStore.js +30 -0
  480. package/lib/types/components/sidePanel/panelBridge.d.ts +44 -0
  481. package/lib/types/components/sidePanel/panelBridge.d.ts.map +1 -0
  482. package/lib/types/components/sidePanel/panelBridge.js +55 -0
  483. package/lib/types/components/sidePanel/pluginPanelAdapter.d.ts +22 -0
  484. package/lib/types/components/sidePanel/pluginPanelAdapter.d.ts.map +1 -0
  485. package/lib/types/components/sidePanel/pluginPanelAdapter.js +148 -0
  486. package/lib/types/components/sidePanel/types.d.ts +110 -0
  487. package/lib/types/components/sidePanel/types.d.ts.map +1 -0
  488. package/lib/types/components/sidePanel/types.js +1 -0
  489. package/lib/types/components/sidePanel/usePanelInput.d.ts +5 -0
  490. package/lib/types/components/sidePanel/usePanelInput.d.ts.map +1 -0
  491. package/lib/types/components/sidePanel/usePanelInput.js +44 -0
  492. package/lib/types/components/sidePanel/useSidePanel.d.ts +47 -0
  493. package/lib/types/components/sidePanel/useSidePanel.d.ts.map +1 -0
  494. package/lib/types/components/sidePanel/useSidePanel.js +284 -0
  495. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +3 -1
  496. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -1
  497. package/lib/types/components/workspaces/HomeWorkspaceRow.js +3 -2
  498. package/lib/types/deepseekPricing.d.ts +10 -9
  499. package/lib/types/deepseekPricing.d.ts.map +1 -1
  500. package/lib/types/deepseekPricing.js +18 -11
  501. package/lib/types/dsh-adapter/channel/attached-context.d.ts +58 -0
  502. package/lib/types/dsh-adapter/channel/attached-context.d.ts.map +1 -0
  503. package/lib/types/dsh-adapter/channel/attached-context.js +109 -0
  504. package/lib/types/dsh-adapter/channel/binding-events.d.ts +6 -0
  505. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  506. package/lib/types/dsh-adapter/channel/binding-events.js +43 -0
  507. package/lib/types/dsh-adapter/channel/capabilities.d.ts +82 -0
  508. package/lib/types/dsh-adapter/channel/capabilities.d.ts.map +1 -0
  509. package/lib/types/dsh-adapter/channel/capabilities.js +165 -0
  510. package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -1
  511. package/lib/types/dsh-adapter/channel/command-completions.js +8 -0
  512. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +4 -12
  513. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  514. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +9 -4
  515. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +13 -2
  516. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  517. package/lib/types/dsh-adapter/channel/input-delivery.js +18 -1
  518. package/lib/types/dsh-adapter/channel/mode-actions.d.ts +0 -3
  519. package/lib/types/dsh-adapter/channel/mode-actions.d.ts.map +1 -1
  520. package/lib/types/dsh-adapter/channel/mode-actions.js +7 -2
  521. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +8 -1
  522. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
  523. package/lib/types/dsh-adapter/channel/mode-permission-actions.js +0 -1
  524. package/lib/types/dsh-adapter/channel/preferences.d.ts +1 -1
  525. package/lib/types/dsh-adapter/channel/preferences.d.ts.map +1 -1
  526. package/lib/types/dsh-adapter/channel/preferences.js +9 -1
  527. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  528. package/lib/types/dsh-adapter/channel/projection.js +42 -37
  529. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  530. package/lib/types/dsh-adapter/channel/reports.js +13 -4
  531. package/lib/types/dsh-adapter/channel/session-reset.d.ts +5 -1
  532. package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -1
  533. package/lib/types/dsh-adapter/channel/session-reset.js +1 -0
  534. package/lib/types/dsh-adapter/channel/state.d.ts +18 -2
  535. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  536. package/lib/types/dsh-adapter/channel/state.js +2 -1
  537. package/lib/types/dsh-adapter/channel/types.d.ts +1 -1
  538. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  539. package/lib/types/dsh-adapter/channel/usage.d.ts +18 -1
  540. package/lib/types/dsh-adapter/channel/usage.d.ts.map +1 -1
  541. package/lib/types/dsh-adapter/channel/usage.js +97 -2
  542. package/lib/types/dsh-adapter/channel.d.ts +1 -1
  543. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  544. package/lib/types/dsh-adapter/channel.js +56 -3
  545. package/lib/types/dsh-adapter/context-occupancy.d.ts +204 -0
  546. package/lib/types/dsh-adapter/context-occupancy.d.ts.map +1 -0
  547. package/lib/types/dsh-adapter/context-occupancy.js +299 -0
  548. package/lib/types/dsh-adapter/contract.d.ts +14 -0
  549. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  550. package/lib/types/dsh-adapter/contract.js +24 -0
  551. package/lib/types/dsh-adapter/index.d.ts +32 -0
  552. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  553. package/lib/types/dsh-adapter/index.js +20 -1
  554. package/lib/types/dsh-adapter/migrate/sessionize.d.ts.map +1 -1
  555. package/lib/types/dsh-adapter/migrate/sessionize.js +11 -12
  556. package/lib/types/dsh-adapter/panel-runtime-registry.d.ts +39 -0
  557. package/lib/types/dsh-adapter/panel-runtime-registry.d.ts.map +1 -0
  558. package/lib/types/dsh-adapter/panel-runtime-registry.js +84 -0
  559. package/lib/types/dsh-adapter/panels.d.ts +251 -0
  560. package/lib/types/dsh-adapter/panels.d.ts.map +1 -0
  561. package/lib/types/dsh-adapter/panels.js +407 -0
  562. package/lib/types/dsh-adapter/plugin-host.js +1 -1
  563. package/lib/types/dsh-adapter/plugin.d.ts +17 -1
  564. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  565. package/lib/types/dsh-adapter/plugin.js +169 -32
  566. package/lib/types/dsh-adapter/presets.d.ts +26 -6
  567. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  568. package/lib/types/dsh-adapter/presets.js +29 -7
  569. package/lib/types/dsh-adapter/startup-args.d.ts +8 -0
  570. package/lib/types/dsh-adapter/startup-args.d.ts.map +1 -0
  571. package/lib/types/dsh-adapter/startup-args.js +34 -0
  572. package/lib/types/history.d.ts +24 -7
  573. package/lib/types/history.d.ts.map +1 -1
  574. package/lib/types/history.js +77 -18
  575. package/lib/types/i18n.d.ts +734 -15
  576. package/lib/types/i18n.d.ts.map +1 -1
  577. package/lib/types/i18n.js +302 -6
  578. package/lib/types/ink/components/App.d.ts +14 -1
  579. package/lib/types/ink/components/App.d.ts.map +1 -1
  580. package/lib/types/ink/components/App.js +62 -61
  581. package/lib/types/ink/components/TerminalSizeContext.d.ts +7 -0
  582. package/lib/types/ink/components/TerminalSizeContext.d.ts.map +1 -1
  583. package/lib/types/ink/hooks/use-input.d.ts +11 -0
  584. package/lib/types/ink/hooks/use-input.d.ts.map +1 -1
  585. package/lib/types/ink/hooks/use-input.js +8 -2
  586. package/lib/types/ink/hooks/use-terminal-viewport.d.ts.map +1 -1
  587. package/lib/types/ink/hooks/use-terminal-viewport.js +3 -1
  588. package/lib/types/ink/ink.d.ts +18 -0
  589. package/lib/types/ink/ink.d.ts.map +1 -1
  590. package/lib/types/ink/ink.js +51 -8
  591. package/lib/types/ink/output.d.ts +10 -1
  592. package/lib/types/ink/output.d.ts.map +1 -1
  593. package/lib/types/ink/output.js +3 -3
  594. package/lib/types/ink/parse-keypress.d.ts +36 -3
  595. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  596. package/lib/types/ink/parse-keypress.js +262 -108
  597. package/lib/types/ink/render-node-to-output.d.ts +1 -1
  598. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  599. package/lib/types/ink/render-node-to-output.js +37 -5
  600. package/lib/types/ink/screen.d.ts +26 -1
  601. package/lib/types/ink/screen.d.ts.map +1 -1
  602. package/lib/types/ink/screen.js +96 -1
  603. package/lib/types/ink/selection.d.ts +23 -1
  604. package/lib/types/ink/selection.d.ts.map +1 -1
  605. package/lib/types/ink/selection.js +102 -26
  606. package/lib/types/ink/sixel-codec.d.ts +11 -0
  607. package/lib/types/ink/sixel-codec.d.ts.map +1 -1
  608. package/lib/types/ink/sixel-codec.js +42 -8
  609. package/lib/types/math/renderer.d.ts +2 -0
  610. package/lib/types/math/renderer.d.ts.map +1 -1
  611. package/lib/types/math/renderer.js +4 -0
  612. package/lib/types/onboardingPrefs.d.ts +52 -0
  613. package/lib/types/onboardingPrefs.d.ts.map +1 -0
  614. package/lib/types/onboardingPrefs.js +107 -0
  615. package/lib/types/panels.d.ts +4 -0
  616. package/lib/types/panels.d.ts.map +1 -0
  617. package/lib/types/panels.js +4 -0
  618. package/lib/types/screens/Chat.d.ts +22 -1
  619. package/lib/types/screens/Chat.d.ts.map +1 -1
  620. package/lib/types/screens/Chat.js +1126 -299
  621. package/lib/types/screens/Launchpad.d.ts +194 -0
  622. package/lib/types/screens/Launchpad.d.ts.map +1 -0
  623. package/lib/types/screens/Launchpad.js +607 -0
  624. package/lib/types/screens/Onboarding.d.ts +37 -0
  625. package/lib/types/screens/Onboarding.d.ts.map +1 -0
  626. package/lib/types/screens/Onboarding.js +492 -0
  627. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
  628. package/lib/types/screens/SessionSupervisor.js +18 -12
  629. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  630. package/lib/types/screens/StatusLine.js +32 -14
  631. package/lib/types/screens/StatusMetrics.d.ts +22 -0
  632. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  633. package/lib/types/screens/StatusMetrics.js +20 -0
  634. package/lib/types/screens/chatOverlay.d.ts +18 -1
  635. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  636. package/lib/types/screens/chatOverlay.js +3 -0
  637. package/lib/types/screens/sessionSupervisor/model.d.ts +1 -2
  638. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -1
  639. package/lib/types/screens/sessionSupervisor/model.js +4 -2
  640. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
  641. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +7 -3
  642. package/lib/types/settings/definitions.d.ts +93 -0
  643. package/lib/types/settings/definitions.d.ts.map +1 -1
  644. package/lib/types/settings/definitions.js +83 -0
  645. package/lib/types/tips.js +5 -5
  646. package/lib/types/tuiDisplayPrefs.d.ts +42 -2
  647. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  648. package/lib/types/tuiDisplayPrefs.js +74 -0
  649. package/lib/types/update.d.ts +53 -9
  650. package/lib/types/update.d.ts.map +1 -1
  651. package/lib/types/update.js +101 -44
  652. package/lib/types/utils/bmp.d.ts.map +1 -1
  653. package/lib/types/utils/bmp.js +8 -1
  654. package/lib/types/utils/clipboard.d.ts.map +1 -1
  655. package/lib/types/utils/clipboard.js +25 -6
  656. package/lib/types/utils/credentials.d.ts.map +1 -1
  657. package/lib/types/utils/credentials.js +5 -1
  658. package/lib/types/utils/draftWordBoundary.d.ts +23 -0
  659. package/lib/types/utils/draftWordBoundary.d.ts.map +1 -0
  660. package/lib/types/utils/draftWordBoundary.js +161 -0
  661. package/lib/types/utils/fileTarget.d.ts.map +1 -1
  662. package/lib/types/utils/fileTarget.js +7 -9
  663. package/lib/types/utils/inputPaste.d.ts +62 -0
  664. package/lib/types/utils/inputPaste.d.ts.map +1 -0
  665. package/lib/types/utils/inputPaste.js +111 -0
  666. package/lib/types/utils/intl.d.ts +8 -0
  667. package/lib/types/utils/intl.d.ts.map +1 -1
  668. package/lib/types/utils/intl.js +11 -0
  669. package/lib/types/utils/keymap.d.ts +13 -3
  670. package/lib/types/utils/keymap.d.ts.map +1 -1
  671. package/lib/types/utils/keymap.js +27 -5
  672. package/package.json +19 -8
  673. package/{dsh-ecosystem-spec → tui-profile}/registry/README.md +1 -1
  674. package/{dsh-ecosystem-spec → tui-profile}/registry/registry-0.15.json +1 -1
  675. /package/{dsh-ecosystem-spec → tui-profile}/protocols/profile-definitions.d.ts +0 -0
  676. /package/{dsh-ecosystem-spec → tui-profile}/protocols/profile-definitions.js +0 -0
  677. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel-http.d.ts +0 -0
  678. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel-http.js +0 -0
  679. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel.d.ts +0 -0
  680. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-channel.js +0 -0
  681. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-contributions.d.ts +0 -0
  682. /package/{dsh-ecosystem-spec → tui-profile}/protocols/tui-contributions.js +0 -0
  683. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/decision-events-v1alpha1.json +0 -0
  684. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/scene-v1alpha1.json +0 -0
  685. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/settings-section-v1alpha1.json +0 -0
  686. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/tui-channel-v1alpha1.json +0 -0
  687. /package/{dsh-ecosystem-spec → tui-profile}/registry/contracts/workspace-provider-v1alpha1.json +0 -0
  688. /package/{dsh-ecosystem-spec → tui-profile}/registry/host-descriptor.tui.example.json +0 -0
  689. /package/{dsh-ecosystem-spec → tui-profile}/registry/permissions-0.1.json +0 -0
  690. /package/{dsh-ecosystem-spec → tui-profile}/schemas/conformance-claim.schema.json +0 -0
  691. /package/{dsh-ecosystem-spec → tui-profile}/schemas/effect-ledger-record.schema.json +0 -0
  692. /package/{dsh-ecosystem-spec → tui-profile}/schemas/host-descriptor.schema.json +0 -0
@@ -1,6 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React from 'react';
3
- import stripAnsi from 'strip-ansi';
3
+ // 粘贴/清洗语义已抽到 utils/inputPaste.ts(方案 B:PromptInput 与 Launchpad 共享;
4
+ // 行为逐字节不变,只是搬了家)。
5
+ import { sanitizeEditableText, sanitizePastedText } from '../utils/inputPaste.js';
4
6
  import { constants as fsConstants } from 'node:fs';
5
7
  import { open, unlink } from 'node:fs/promises';
6
8
  import { basename, resolve } from 'node:path';
@@ -19,12 +21,13 @@ import instances from '../ink/instances.js';
19
21
  import { stringWidth } from '../ink/stringWidth.js';
20
22
  import { truncateToWidth } from '../ink/truncateToWidth.js';
21
23
  import { getGraphemeSegmenter } from '../utils/intl.js';
24
+ import { draftWordRangeAt, isDraftWordBoundary } from '../utils/draftWordBoundary.js';
22
25
  import { formatClipboardInsert, readClipboard } from '../utils/clipboard.js';
23
26
  import { imagePathMediaType, parsePastedImagePath, stageClipboardFilePaths } from '../utils/pastedImagePath.js';
24
27
  import { editInExternalEditor } from '../utils/externalEditor.js';
25
28
  import { setPromptEditorNode, EditorButton } from './PromptEditor.js';
26
29
  import { isHiddenCommandName, parseCommandName } from '../commands.js';
27
- import { appendHistory, HISTORY_LIMIT, loadHistoryOldestFirst } from '../history.js';
30
+ import { appendHistory, HISTORY_LIMIT, historyProjectKey, loadHistoryOldestFirst } from '../history.js';
28
31
  import { mentionAtCaret } from '../utils/mentions.js';
29
32
  import { preserveSelection } from '../utils/fileSuggestions.js';
30
33
  import { isMod } from '../utils/modifiers.js';
@@ -35,6 +38,7 @@ import { HelpMenu } from './HelpMenu.js';
35
38
  import { OverlayAbove } from './OverlayAbove.js';
36
39
  import { SuggestionCard, cardContentWidth } from './SuggestionCard.js';
37
40
  import { filterLiveImageBindings, isUsableDraftSnapshot, resolveBindingGeneration, } from './promptDraftCache.js';
41
+ export { sanitizeEditableText, sanitizePastedText } from '../utils/inputPaste.js';
38
42
  /**
39
43
  * Visible text of the session-entry control at the head of the input row:
40
44
  * U+2338 APL FUNCTIONAL SYMBOL QUAD COLON plus a separator space.
@@ -53,6 +57,67 @@ import { filterLiveImageBindings, isUsableDraftSnapshot, resolveBindingGeneratio
53
57
  */
54
58
  const HOME_BUTTON_TEXT = '⌸ ';
55
59
  const HOME_BUTTON_COLS = stringWidth(HOME_BUTTON_TEXT);
60
+ /** Undo depth for both stacks (draft `Ctrl+Z` and vim `u`). */
61
+ const UNDO_LIMIT = 100;
62
+ /** Total characters kept across all snapshots; the oldest are dropped first. */
63
+ const UNDO_MAX_CHARS = 1_000_000;
64
+ /** Layer-3 idle window: a pause longer than this starts a new step. */
65
+ const UNDO_IDLE_MS = 700;
66
+ /**
67
+ * Common prefix/suffix of `prev` and `next` — the single edited span. Undo
68
+ * grouping works on this span rather than on a stdin chunk: one chunk may
69
+ * carry several characters (or several keys), so the unit is one real text
70
+ * mutation however it arrived. The common prefix must not pass either caret:
71
+ * repeated characters otherwise make an edit at the head look like a tail edit.
72
+ */
73
+ function diffSpan(prev, next, cursorBefore, cursorAfter) {
74
+ let start = 0;
75
+ const common = Math.min(prev.length, next.length, cursorBefore, cursorAfter);
76
+ while (start < common && prev.charCodeAt(start) === next.charCodeAt(start))
77
+ start++;
78
+ let prevEnd = prev.length;
79
+ let nextEnd = next.length;
80
+ while (prevEnd > start &&
81
+ nextEnd > start &&
82
+ prev.charCodeAt(prevEnd - 1) === next.charCodeAt(nextEnd - 1)) {
83
+ prevEnd--;
84
+ nextEnd--;
85
+ }
86
+ return { start, inserted: next.slice(start, nextEnd) };
87
+ }
88
+ /**
89
+ * Open a deletion run at the first character it removed: remember the word that
90
+ * character belongs to, and how much has been consumed so far.
91
+ */
92
+ function startDeleteRun(text, start, removed) {
93
+ const word = draftWordRangeAt(text, start);
94
+ return { wordStart: word.start, wordEnd: word.end, lo: start, hi: start + removed };
95
+ }
96
+ /**
97
+ * Whether a same-direction deletion extends the run recorded on `last`. The
98
+ * run's word interval and consumed range live in `last.text` coordinates, so
99
+ * the comparison survives the draft shrinking under it; extending past the word
100
+ * (or off the text) starts a new step. Only called once the direction, caret
101
+ * continuity and idle window have already agreed, so the new text is always
102
+ * adjacent to the consumed range.
103
+ */
104
+ function extendDeleteRun(last, backward, removed) {
105
+ const run = last.deleteRun;
106
+ if (run === null || removed <= 0)
107
+ return false;
108
+ if (backward) {
109
+ const lo = run.lo - removed;
110
+ if (lo < 0 || lo < run.wordStart)
111
+ return false;
112
+ run.lo = lo;
113
+ return true;
114
+ }
115
+ const hi = run.hi + removed;
116
+ if (hi > last.text.length || hi > run.wordEnd)
117
+ return false;
118
+ run.hi = hi;
119
+ return true;
120
+ }
56
121
  /**
57
122
  * Paste fold with a visible preview and no black box:
58
123
  * a paste that leaves the input this big folds into a one-line chip
@@ -65,66 +130,6 @@ const HOME_BUTTON_COLS = stringWidth(HOME_BUTTON_TEXT);
65
130
  const FOLD_MIN_LINES = 6;
66
131
  const FOLD_MIN_CHARS = 600;
67
132
  const isBigInput = (text) => text.split('\n').length >= FOLD_MIN_LINES || text.length >= FOLD_MIN_CHARS;
68
- /**
69
- * Editable prompt text must have one stable source-to-screen geometry. The
70
- * renderer interprets ANSI as zero-width styling and expands tabs relative to
71
- * global tab stops; keeping either in `value` would let wrapping/click mapping
72
- * count different cells and could split an escape sequence during selection.
73
- * Strip terminal controls and expand tabs at ingress while preserving newlines.
74
- */
75
- const EDITABLE_CONTROL = /[\u0000-\u0009\u000b-\u001f\u007f]/u;
76
- /**
77
- * Raw win32-input-mode records (`CSI Vk;Sc;Uc;Kd;Cs;Rc _`) that reached the
78
- * editable buffer as text instead of being translated. `stripAnsi` consumes
79
- * the record head and leaves its terminating `_` in the draft — the stray
80
- * underscore users see after a multi-line paste (issue #1090). Only the full
81
- * record grammar (exactly five `;` separators) matches, so a real `_` and
82
- * ordinary bracket text survive untouched.
83
- */
84
- const WIN32_RECORD_RESIDUE = /\u001b\[\d*(?:;\d*){5}_/gu;
85
- /**
86
- * The same record with its ESC byte missing: what a record split across
87
- * reads leaves behind when the escape timer flushed the prefix before the
88
- * tail arrived. Printable, so it is stripped only from paste payloads
89
- * ({@link sanitizePastedText}) — and only when the same payload also carries
90
- * a full ESC-bearing record as in-payload evidence of that split; typed text
91
- * and literal clipboard/bracketed-paste bytes are left untouched.
92
- */
93
- const WIN32_RECORD_RESIDUE_TAIL = /\[\d*(?:;\d*){5}_/gu;
94
- /**
95
- * Normalize editable text so no terminal control characters remain in state.
96
- */
97
- export function sanitizeEditableText(text) {
98
- // Fast path for ordinary and multi-line drafts: newline is intentionally
99
- // absent from the probe, so large clean text returns without the
100
- // stripAnsi/control-normalization passes.
101
- if (!EDITABLE_CONTROL.test(text))
102
- return text;
103
- // Record residue goes first: `stripAnsi` would consume the CSI head and
104
- // leave only the terminating `_` behind.
105
- return stripAnsi(text.replace(WIN32_RECORD_RESIDUE, ''))
106
- .replace(/\r\n?/gu, '\n')
107
- .replace(/\t/gu, ' ')
108
- .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, '');
109
- }
110
- /**
111
- * Paste-payload ingress: strip the ESC-less tail of a split record before
112
- * normalizing. The tail is printable, so it survives `sanitizeEditableText`'s
113
- * control probe untouched; typed text keeps it because only a paste payload
114
- * can carry a partial record.
115
- *
116
- * The five separators only prove the *shape*, not that a record was split:
117
- * a user can legitimately paste the literal `[13;28;13;1;0;1_`. Strip the
118
- * ESC-less form only when the same payload also carries a full ESC-bearing
119
- * record — only then is there in-payload evidence of a split stream.
120
- * Otherwise the bytes are ordinary text and must survive verbatim.
121
- */
122
- export function sanitizePastedText(text) {
123
- // Probe with String#match: the /g detection regex carries lastIndex state
124
- // across `.test` calls, so a previous success could skip a later match.
125
- const hasRecordStream = text.match(WIN32_RECORD_RESIDUE) !== null;
126
- return sanitizeEditableText(hasRecordStream ? text.replace(WIN32_RECORD_RESIDUE_TAIL, '') : text);
127
- }
128
133
  const COMPOSER_IMAGE_TOKEN = /\[Image #\d+\]/gu;
129
134
  /** Format label for one image media type, matching the image preview card's
130
135
  * title (`JPEG`, `PNG`, `WEBP`, `GIF`). */
@@ -414,7 +419,7 @@ const DOUBLE_CLICK_MS = 500;
414
419
  * working) interrupts the turn and delivers them right away; Ctrl+Enter
415
420
  * aborts the turn and sends the current input immediately.
416
421
  */
417
- export function PromptInput({ channel, suspended = false, draftCache, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, onOpenSessions, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen = false, onDismissCaretPreview, }) {
422
+ export function PromptInput({ channel, toastSuppressed, suspended = false, draftCache, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, onOpenSessions, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen = false, onDismissCaretPreview, now = Date.now, }) {
418
423
  const [themeName] = useTheme();
419
424
  // Raw stdout writer for OSC 52 clipboard writes (selection copy) — must
420
425
  // bypass the frame pipeline; null outside a mounted Ink App.
@@ -507,7 +512,20 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
507
512
  const vimInsertRef = React.useRef(true);
508
513
  vimEnabledRef.current = vimEnabled;
509
514
  vimInsertRef.current = vimInsert;
510
- /** Undo owns the draft's image bindings as well as its text and caret. */
515
+ /**
516
+ * Draft-scoped undo stack (word-level `Ctrl+Z`). This is the DRAFT's own
517
+ * history: a submit, a recall or a session switch ends it. Entries hold image
518
+ * bindings exactly like the draft does, so dropping one must release the
519
+ * capabilities only it kept alive.
520
+ */
521
+ const draftUndoRef = React.useRef([]);
522
+ /**
523
+ * Vim NORMAL-mode undo stack (vim `u`), deliberately separate from
524
+ * `draftUndoRef`: unifying the two transaction models is a user-visible vim
525
+ * behavior change and is out of this change's scope. `/vim` toggling clears
526
+ * THIS stack — a later re-enable must never `u` back past edits made while
527
+ * vim was off.
528
+ */
511
529
  const vimUndoRef = React.useRef([]);
512
530
  /** Pending vim operator: `d` pressed, awaiting its second key. */
513
531
  const vimPendingRef = React.useRef('');
@@ -575,8 +593,11 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
575
593
  const history = React.useRef([]);
576
594
  const historyIndex = React.useRef(-1);
577
595
  const historyDraft = React.useRef({ text: '', images: [] });
578
- /** The persisted history is read lazily, once per mount (see seedHistory). */
579
- const historySeeded = React.useRef(false);
596
+ /** The persisted history is read lazily, once per mount and project: the
597
+ * normalized project key it was seeded for, so a workspace switch re-seeds
598
+ * (see seedHistory) while a respelled path to the same directory does not.
599
+ * `null` = never seeded; `''` = seeded without a workspace. */
600
+ const historySeededProject = React.useRef(null);
580
601
  /** Visible `[Image #N]` labels are presentation only; this sidecar carries
581
602
  * the non-reusable capability for the current draft. History/rewind text
582
603
  * restored without this map can never bind to a later image by accident. */
@@ -606,6 +627,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
606
627
  const detachDraftImages = () => {
607
628
  advanceDraftRevision();
608
629
  draftImagesRef.current.clear();
630
+ clearDraftUndo();
609
631
  clearVimUndo();
610
632
  };
611
633
  const stageIdIsRetained = (stageId) => {
@@ -613,7 +635,8 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
613
635
  if (current === stageId)
614
636
  return true;
615
637
  }
616
- return vimUndoRef.current.some(entry => entry.images.some(image => image.stageId === stageId))
638
+ return draftUndoRef.current.some(entry => entry.images.some(image => image.stageId === stageId))
639
+ || vimUndoRef.current.some(entry => entry.images.some(image => image.stageId === stageId))
617
640
  || history.current.some(entry => entry.images.some(image => image.stageId === stageId))
618
641
  || historyDraft.current.images.some(image => image.stageId === stageId)
619
642
  || channel.pending.some(item => item.images?.some(image => image.stageId === stageId) === true);
@@ -624,14 +647,109 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
624
647
  channel.discardStagedImage(stageId);
625
648
  }
626
649
  };
650
+ const clearDraftUndo = () => {
651
+ const previous = draftUndoRef.current;
652
+ draftUndoRef.current = [];
653
+ discardUnretainedImages(previous.flatMap(entry => entry.images.map(image => image.stageId)));
654
+ };
627
655
  const clearVimUndo = () => {
628
656
  const previous = vimUndoRef.current;
629
657
  vimUndoRef.current = [];
630
658
  discardUnretainedImages(previous.flatMap(entry => entry.images.map(image => image.stageId)));
631
659
  };
632
- const discardDraftImages = () => {
660
+ /** Push one step and enforce the depth/character guards. */
661
+ const pushDraftUndo = (snapshot, kind, caretAfter, deleteRun = null) => {
662
+ draftUndoRef.current.push({ ...snapshot, atMs: now(), caretAfter, kind, deleteRun });
663
+ const dropped = [];
664
+ while (draftUndoRef.current.length > UNDO_LIMIT)
665
+ dropped.push(draftUndoRef.current.shift());
666
+ let chars = draftUndoRef.current.reduce((total, entry) => total + entry.text.length, 0);
667
+ while (chars > UNDO_MAX_CHARS && draftUndoRef.current.length > 1) {
668
+ const oldest = draftUndoRef.current.shift();
669
+ chars -= oldest.text.length;
670
+ dropped.push(oldest);
671
+ }
672
+ if (dropped.length > 0) {
673
+ discardUnretainedImages(dropped.flatMap(entry => entry.images.map(image => image.stageId)));
674
+ }
675
+ };
676
+ /**
677
+ * Record one real text mutation. `group` folds it into the current step when
678
+ * the direction, caret continuity, idle window and word rule all agree;
679
+ * `step` always starts a sealed step. A chunk carrying several characters is
680
+ * judged per mutation, never per stdin read.
681
+ */
682
+ const recordDraftEdit = (prev, prevCursor, block, next, nextCursor, mode) => {
683
+ const span = diffSpan(prev, next, prevCursor, nextCursor);
684
+ // Backspace consumes the character BEFORE the caret; Delete consumes the
685
+ // one AT it. The two directions anchor differently, so their runs must
686
+ // never fold into one another.
687
+ const backward = span.start < prevCursor;
688
+ const kind = span.inserted.length > 0 ? 'insert' : backward ? 'deleteBackward' : 'deleteForward';
689
+ const removed = prev.length - next.length;
690
+ const caretAfter = nextCursor;
691
+ if (mode === 'group') {
692
+ const last = draftUndoRef.current[draftUndoRef.current.length - 1];
693
+ // Continuity: this edit must begin where the last one ended. An insertion
694
+ // begins at the caret; a Backspace consumes the character BEFORE it, so
695
+ // the pre-edit caret is its start — comparing `span.start` there would
696
+ // make every repeated Backspace a step of its own.
697
+ const editStart = kind === 'insert' ? span.start : prevCursor;
698
+ if (last !== undefined &&
699
+ last.caretAfter !== null &&
700
+ last.kind === kind &&
701
+ last.caretAfter === editStart &&
702
+ now() - last.atMs <= UNDO_IDLE_MS &&
703
+ // An insertion extends a word when its seam is not a boundary; a
704
+ // deletion continues while it stays inside the word its run began in.
705
+ // The boundary question is deliberately NOT asked of a deletion: at the
706
+ // seam, `isDraftWordBoundary(prev, span.start)` reads the removed
707
+ // character's LEFT neighbour, which stops the run one keystroke early
708
+ // at a word's left edge and at the text head (and mis-splits a
709
+ // re-segmented CJK suffix).
710
+ (kind === 'insert'
711
+ ? !isDraftWordBoundary(next, span.start)
712
+ : extendDeleteRun(last, backward, removed))) {
713
+ last.atMs = now();
714
+ last.caretAfter = caretAfter;
715
+ return;
716
+ }
717
+ }
718
+ pushDraftUndo({ text: prev, cursor: prevCursor, images: imageRefsFor(prev), block }, kind, mode === 'group' ? caretAfter : null,
719
+ // A sealed step never groups, so only a groupable deletion needs (and
720
+ // pays for) the word interval.
721
+ mode === 'group' && kind !== 'insert' ? startDeleteRun(prev, span.start, removed) : null);
722
+ };
723
+ /**
724
+ * Clear the draft while keeping ONE undo step, so `Ctrl+Z` can bring the
725
+ * whole draft back (text, caret, images and fold block). The snapshot is
726
+ * taken before the images are released: afterwards the capabilities would
727
+ * already be gone and the restored `[Image #N]` tokens would be inert.
728
+ */
729
+ const clearDraftUndoably = () => {
730
+ syncImageGeneration();
731
+ // Nothing to come back to on an already-empty draft: pushing a snapshot
732
+ // there would waste the next Ctrl+Z (and hide an older real step).
733
+ if (valueRef.current !== '') {
734
+ pushDraftUndo({
735
+ text: valueRef.current,
736
+ cursor: cursorRef.current,
737
+ images: imageRefsFor(valueRef.current),
738
+ block: foldBlockRef.current,
739
+ }, 'clear', null);
740
+ }
741
+ discardDraftImages({ keepUndo: true });
742
+ setInput('', 0, 'silent');
743
+ setSelectedCommand(0);
744
+ setFileSelected(0);
745
+ };
746
+ const discardDraftImages = (options) => {
633
747
  advanceDraftRevision();
634
748
  const stageIds = [...draftImagesRef.current.values()];
749
+ // The draft stack may be kept so Ctrl+Z can undo the clear; the vim stack
750
+ // is never restored by `u` across a whole-draft replacement.
751
+ if (options?.keepUndo !== true)
752
+ clearDraftUndo();
635
753
  clearVimUndo();
636
754
  draftImagesRef.current.clear();
637
755
  discardUnretainedImages(stageIds);
@@ -651,6 +769,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
651
769
  if (draftImagesGenerationRef.current !== generation) {
652
770
  // The channel has already cleared every old-generation capability.
653
771
  // Only detach the UI sidecar; calling discard would be redundant.
772
+ draftUndoRef.current = [];
654
773
  vimUndoRef.current = [];
655
774
  detachDraftImages();
656
775
  draftImagesGenerationRef.current = generation;
@@ -721,6 +840,12 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
721
840
  const next = sanitizeEditableText(previous + text);
722
841
  if (next !== previous)
723
842
  inputEditSequenceRef.current += 1;
843
+ // An external injection is a NEW draft baseline. It bypasses setInput,
844
+ // so the stack would still describe the pre-injection text: one Ctrl+Z
845
+ // would then erase the injected text together with the typing it
846
+ // joined (the injection is not an edit of the draft the user made).
847
+ if (next !== previous)
848
+ clearDraftUndo();
724
849
  valueRef.current = next;
725
850
  cursorRef.current = next.length;
726
851
  setValue(next);
@@ -739,14 +864,16 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
739
864
  // The selection stays: copy never clears it (Esc/typing/delete do).
740
865
  return true;
741
866
  },
867
+ consumeEscape: consumeEditingEscape,
742
868
  toggleVim: () => {
743
869
  const next = !vimEnabledRef.current;
744
870
  vimEnabledRef.current = next;
745
871
  setVimEnabled(next);
746
872
  // Every toggle lands in INSERT: a fresh vim user keeps typing
747
873
  // normally until they press Esc for the first time. Turning the
748
- // mode off also clears the undo stack — a later re-enable must
749
- // never `u` its way back past edits made while vim was off.
874
+ // mode off also clears the VIM undo stack — a later re-enable must
875
+ // never `u` its way back past edits made while vim was off. The
876
+ // draft stack (Ctrl+Z) is a different history and is left alone.
750
877
  vimInsertRef.current = true;
751
878
  setVimInsert(true);
752
879
  vimPendingRef.current = '';
@@ -769,7 +896,9 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
769
896
  syncImageGeneration();
770
897
  discardDraftImages();
771
898
  updateFoldBlock(null);
772
- setInput(fillText);
899
+ // A recall is not undoable (D4): the stack was just cleared above, so
900
+ // the fill must not push a snapshot of the draft it replaced.
901
+ setInput(fillText, fillText.length, 'silent');
773
902
  onFillConsumed?.();
774
903
  }
775
904
  }, [fillText, onFillConsumed]);
@@ -797,9 +926,10 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
797
926
  *
798
927
  * `advanceDraftRevision` invalidates any image read/stage still in
799
928
  * flight, so a late continuation cannot bind a capability into a composer
800
- * that no longer exists. `clearVimUndo` releases the capabilities that
801
- * only the undo stack was holding: they are not part of the draft, and
802
- * nothing will ever restore them once this composer is gone.
929
+ * that no longer exists. `clearDraftUndo`/`clearVimUndo` release the
930
+ * capabilities that only an undo stack was holding: they are not part of
931
+ * the draft, and nothing will ever restore them once this composer is
932
+ * gone.
803
933
  *
804
934
  * The stageIds the DRAFT holds are deliberately NOT revoked — they are
805
935
  * part of the snapshot the slot now keeps, and the restore filters out
@@ -807,6 +937,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
807
937
  * an image draft used to come back as inert text.
808
938
  */
809
939
  advanceDraftRevision();
940
+ clearDraftUndo();
810
941
  clearVimUndo();
811
942
  const images = [...draftImagesRef.current.entries()]
812
943
  .map(([token, stageId]) => [token, stageId]);
@@ -980,7 +1111,13 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
980
1111
  }
981
1112
  }
982
1113
  };
983
- const setInput = (next, cursorOffset = next.length) => {
1114
+ /**
1115
+ * Apply `next` at `cursorOffset` and record the edit on the undo stack.
1116
+ * `snapshotBlock` overrides the block the step snapshots: a caller that must
1117
+ * clear the fold block BEFORE writing (the external-editor refill) still
1118
+ * wants the block that was live at edit time.
1119
+ */
1120
+ const setInput = (next, cursorOffset = next.length, undo = 'group', snapshotBlock) => {
984
1121
  // Apply the same ingress normalization to fills/history/editor results as
985
1122
  // to paste. Map the requested caret through the sanitized prefix so an
986
1123
  // ANSI sequence removed before it cannot leave the caret past the text.
@@ -1022,6 +1159,16 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1022
1159
  // one continues in its direction of travel (←/word-left/↑ → the token's
1023
1160
  // start, →/word-right/↓ → its end); with no travel, the nearer edge.
1024
1161
  offset = snapOffImageToken(boundImageSpans(next), offset, offset < prevCursor ? 'start' : offset > prevCursor ? 'end' : 'nearest');
1162
+ // Undo bookkeeping runs before the mirrors move: it snapshots the state
1163
+ // this edit replaces, and `diffSpan` needs both texts. `reset` ends the
1164
+ // draft's history, `silent` (undo itself, a recall) leaves it untouched.
1165
+ if (next !== prev) {
1166
+ if (undo === 'reset')
1167
+ clearDraftUndo();
1168
+ else if (undo !== 'silent') {
1169
+ recordDraftEdit(prev, prevCursor, snapshotBlock === undefined ? block : snapshotBlock, next, offset, undo);
1170
+ }
1171
+ }
1025
1172
  // The synchronous mirrors are what batch-dispatched events (one stdin
1026
1173
  // read → several keys, no render in between) read on their next turn.
1027
1174
  if (next !== prev)
@@ -1044,12 +1191,12 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1044
1191
  setValue(next);
1045
1192
  setCursor(offset);
1046
1193
  };
1047
- const deleteInputRange = (start, end) => {
1194
+ const deleteInputRange = (start, end, undo = 'group') => {
1048
1195
  if (start >= end)
1049
1196
  return;
1050
1197
  const text = valueRef.current;
1051
1198
  const range = expandImageTokenRange(boundImageSpans(text), start, end);
1052
- setInput(text.slice(0, range.start) + text.slice(range.end), range.start);
1199
+ setInput(text.slice(0, range.start) + text.slice(range.end), range.start, undo);
1053
1200
  };
1054
1201
  /**
1055
1202
  * Write the selection [start, end) (snapped to grapheme boundaries,
@@ -1112,7 +1259,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1112
1259
  const suffix = mention.pathEnd === undefined ? '' : value.slice(mention.pathEnd, mention.end);
1113
1260
  const insert = candidate.kind === 'directory' ? `${body}${suffix}` : `${body}${suffix} `;
1114
1261
  const next = value.slice(0, mention.start) + insert + value.slice(mention.end);
1115
- setInput(next, mention.start + insert.length);
1262
+ setInput(next, mention.start + insert.length, 'step');
1116
1263
  setFileSelected(0);
1117
1264
  };
1118
1265
  const imageRefsFor = (text) => {
@@ -1145,14 +1292,35 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1145
1292
  * persisted entries first, this run's submits behind them — instead of two
1146
1293
  * lists to merge at recall time. Restored text carries no image capability
1147
1294
  * (the file stores text only), which is also what keeps a recalled entry
1148
- * from binding to a later staged image by accident.
1295
+ * from binding to a later staged image by accident. The walk is scoped to
1296
+ * the channel's workspace, keyed the same way the store keys it
1297
+ * (historyProjectKey), so a workspace switch re-seeds it with that
1298
+ * project's history instead of carrying the previous one over — and a
1299
+ * respelled path to the same directory is not a switch.
1300
+ * @returns Whether the re-seed cut short a walk in progress: the composer
1301
+ * then shows a recalled entry of the previous project while the draft that
1302
+ * walk started from is still held in `historyDraft`.
1149
1303
  */
1150
1304
  const seedHistory = () => {
1151
- if (historySeeded.current)
1152
- return;
1153
- historySeeded.current = true;
1154
- history.current = loadHistoryOldestFirst().map(entry => ({ text: entry.text, images: [] }));
1305
+ // The store's own key, not the raw cwd: `/repo` and `/repo/` are one
1306
+ // project, so respelling the path must not look like a workspace switch.
1307
+ const project = historyProjectKey(channel.cwd) ?? '';
1308
+ if (historySeededProject.current === project)
1309
+ return false;
1310
+ const interrupted = historyIndex.current >= 0;
1311
+ historySeededProject.current = project;
1312
+ history.current = loadHistoryOldestFirst(channel.cwd).map(entry => ({ text: entry.text, images: [] }));
1155
1313
  historyIndex.current = -1;
1314
+ return interrupted;
1315
+ };
1316
+ /** End the walk and put back the draft it started from. */
1317
+ const restoreHistoryDraft = () => {
1318
+ historyIndex.current = -1;
1319
+ updateFoldBlock(null);
1320
+ restoreDraftImages(historyDraft.current);
1321
+ // A recall is not undoable (D4): `restoreDraftImages` just ended the
1322
+ // history, so the restore must not push a step of its own.
1323
+ setInput(historyDraft.current.text, historyDraft.current.text.length, 'silent');
1156
1324
  };
1157
1325
  const rememberHistory = (text, images) => {
1158
1326
  // Both entries into the walk (a submit and ↑) must see the persisted
@@ -1165,14 +1333,15 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1165
1333
  if (history.current.length > HISTORY_LIMIT)
1166
1334
  history.current.shift();
1167
1335
  historyIndex.current = -1;
1168
- void appendHistory(text);
1336
+ void appendHistory(text, channel.cwd);
1169
1337
  };
1170
1338
  const clearDeliveredDraft = () => {
1171
1339
  syncImageGeneration();
1172
1340
  // Delivery captured the opaque refs and history retains them; only the
1173
- // editable-draft binding is ending here.
1341
+ // editable-draft binding is ending here. `reset` ends the draft's undo
1342
+ // history: a submitted message must not come back with Ctrl+Z.
1174
1343
  detachDraftImages();
1175
- setInput('', 0);
1344
+ setInput('', 0, 'reset');
1176
1345
  setSelectedCommand(0);
1177
1346
  setFileSelected(0);
1178
1347
  };
@@ -1189,8 +1358,40 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1189
1358
  syncImageGeneration();
1190
1359
  advanceDraftRevision();
1191
1360
  replaceDraftImages(entry.images);
1361
+ // A recall is not undoable (D4): both stacks end with the draft it replaced.
1362
+ clearDraftUndo();
1192
1363
  clearVimUndo();
1193
1364
  };
1365
+ /**
1366
+ * `Ctrl+Z`: pop back to the state before the current undo step — text,
1367
+ * caret, images and fold block together. An empty stack is a no-op (never
1368
+ * the rewind picker, never a clear). The restore is `silent`, so repeated
1369
+ * Ctrl+Z walks further back instead of pushing what it just left. Vim `u`
1370
+ * uses its own stack (see {@link vimUndoRef}).
1371
+ */
1372
+ const undoDraft = () => {
1373
+ // A session replacement invalidates the whole stack. Check that BEFORE
1374
+ // popping: restoring first and discarding afterwards would put the old
1375
+ // session's text (and its image tokens) back into the new composer.
1376
+ syncImageGeneration();
1377
+ const entry = draftUndoRef.current.pop();
1378
+ if (entry === undefined)
1379
+ return;
1380
+ advanceDraftRevision();
1381
+ replaceDraftImages(entry.images);
1382
+ // Drop the live block first: setInput's atomicity rules describe the block
1383
+ // that was live at edit time, and re-shifting it here would corrupt the
1384
+ // snapshot's. The snapshot's own block goes back afterwards, and only if
1385
+ // its range still fits the text it was captured from.
1386
+ updateFoldBlock(null);
1387
+ setInput(entry.text, entry.cursor, 'silent');
1388
+ const block = entry.block !== null && entry.block.start < entry.block.end && entry.block.end <= entry.text.length
1389
+ ? entry.block
1390
+ : null;
1391
+ updateFoldBlock(block);
1392
+ setSelectedCommand(0);
1393
+ setFileSelected(0);
1394
+ };
1194
1395
  const submitText = (text, notice) => {
1195
1396
  const trimmed = text.trim();
1196
1397
  if (!trimmed)
@@ -1258,7 +1459,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1258
1459
  text: item.text,
1259
1460
  images: item.images ?? [],
1260
1461
  });
1261
- setInput(item.text);
1462
+ setInput(item.text, item.text.length, 'silent');
1262
1463
  updateFoldBlock(null);
1263
1464
  setSelectedCommand(0);
1264
1465
  setFileSelected(0);
@@ -1480,6 +1681,19 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1480
1681
  lastClickColRef.current = -1;
1481
1682
  lastClickRowRef.current = -1;
1482
1683
  };
1684
+ /** Local Esc layers, shared by the prompt listener and Chat's delegation.
1685
+ * Refs preserve this order even when expansion and Esc share a stdin batch. */
1686
+ const consumeEditingEscape = () => {
1687
+ if (selectionRef.current && !helpOpen && !overlayOpen && !fileOverlayOpen) {
1688
+ clearSelection();
1689
+ return true;
1690
+ }
1691
+ if (expandedRef.current) {
1692
+ collapseEditor();
1693
+ return true;
1694
+ }
1695
+ return false;
1696
+ };
1483
1697
  /** The editor's explicit send (Ctrl+Enter / Send button): the Enter main
1484
1698
  * path, then collapse — an empty draft just collapses. */
1485
1699
  const submitFromEditor = () => {
@@ -1498,7 +1712,8 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1498
1712
  const next = sel
1499
1713
  ? current.slice(0, sel.start) + text + current.slice(sel.end)
1500
1714
  : current.slice(0, position) + text + current.slice(position);
1501
- setInput(next, position + text.length);
1715
+ // A paste is ONE undo step however many characters it carries.
1716
+ setInput(next, position + text.length, 'step');
1502
1717
  setSelectedCommand(0);
1503
1718
  setFileSelected(0);
1504
1719
  return { next, at: position };
@@ -1650,27 +1865,18 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1650
1865
  // ONLY drops the highlight (text untouched), and Backspace/Delete
1651
1866
  // delete the selected span. Arrows/typing handle the selection at their
1652
1867
  // own arms below.
1653
- if (key.escape && selection && !helpOpen && !overlayOpen && !fileOverlayOpen) {
1868
+ if (key.escape && consumeEditingEscape()) {
1654
1869
  event.stopImmediatePropagation();
1655
- clearSelection();
1656
1870
  return;
1657
1871
  }
1658
1872
  if ((key.backspace || key.delete) && selection) {
1659
- deleteInputRange(selection.start, selection.end);
1873
+ // Removing a selection is atomic: it must undo in one step, not fold
1874
+ // into whatever the caret-continuity rule would otherwise see.
1875
+ deleteInputRange(selection.start, selection.end, 'step');
1660
1876
  setSelectedCommand(0);
1661
1877
  setFileSelected(0);
1662
1878
  return;
1663
1879
  }
1664
- // ── 全屏草稿编辑(expandEditor,默认 Ctrl+Shift+E / 输入行 ✎)─────
1665
- // 展开态拥有屏幕;Esc 收起(有选区时上面的 selection 分支已先行只清
1666
- // 选区)。滚轮不经此——编辑区的 onWheel 位置路由直接驱动滚动窗口。
1667
- if (key.escape && expandedRef.current) {
1668
- // Collapse runs AHEAD of the fold-block/vim Esc meanings: the
1669
- // fullscreen cover is the outermost modal layer.
1670
- event?.stopImmediatePropagation();
1671
- collapseEditor();
1672
- return;
1673
- }
1674
1880
  // Fold block: Esc expands it — it must NEVER clear text
1675
1881
  // that LOOKS like one line; Backspace at the block's tail / Delete at
1676
1882
  // its head deletes the WHOLE block in one key; ←/→ jump over the
@@ -1690,7 +1896,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1690
1896
  if ((key.backspace && cursor === block.end) || (key.delete && cursor === block.start)) {
1691
1897
  const next = value.slice(0, block.start) + value.slice(block.end);
1692
1898
  updateFoldBlock(null);
1693
- setInput(next, block.start);
1899
+ setInput(next, block.start, 'step', block);
1694
1900
  setSelectedCommand(0);
1695
1901
  setFileSelected(0);
1696
1902
  return;
@@ -1711,7 +1917,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1711
1917
  * active selection is REPLACED by the insert (standard editor
1712
1918
  * semantics). Returns the insertion offset so callers can derive the
1713
1919
  * inserted span (paste fold). */
1714
- const insertAtCaret = (text) => {
1920
+ const insertAtCaret = (text, undo = 'group') => {
1715
1921
  if (helpOpen)
1716
1922
  onToggleHelp();
1717
1923
  const sel = selectionRef.current;
@@ -1719,7 +1925,9 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1719
1925
  const next = sel
1720
1926
  ? value.slice(0, sel.start) + text + value.slice(sel.end)
1721
1927
  : value.slice(0, cursor) + text + value.slice(cursor);
1722
- setInput(next, at + text.length);
1928
+ // Replacing a selection is atomic (like deleting one): the replaced span
1929
+ // must not fold into the surrounding typed run.
1930
+ setInput(next, at + text.length, sel ? 'step' : undo);
1723
1931
  setSelectedCommand(0);
1724
1932
  setFileSelected(0);
1725
1933
  return at;
@@ -1758,7 +1966,8 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1758
1966
  });
1759
1967
  return;
1760
1968
  }
1761
- const at = insertAtCaret(text);
1969
+ // One paste is ONE undo step however many characters it carries.
1970
+ const at = insertAtCaret(text, 'step');
1762
1971
  // A big paste becomes a fold block right away (hover peeks
1763
1972
  // at it); an existing block is replaced by the new paste's span.
1764
1973
  // The EXPANDED editor never folds — pasting there is plain text
@@ -1950,8 +2159,12 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
1950
2159
  if (!draftImageLeaseIsCurrent(editorLease))
1951
2160
  return;
1952
2161
  if (outcome.kind === 'edited') {
2162
+ // Snapshot the block BEFORE clearing it: the refill is one undo
2163
+ // step (D4), and Ctrl+Z must bring back the fold chip the draft
2164
+ // had, not the already-cleared null.
2165
+ const beforeBlock = foldBlockRef.current;
1953
2166
  updateFoldBlock(null);
1954
- setInput(outcome.text);
2167
+ setInput(outcome.text, outcome.text.length, 'step', beforeBlock);
1955
2168
  setSelectedCommand(0);
1956
2169
  setFileSelected(0);
1957
2170
  }
@@ -2094,7 +2307,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2094
2307
  const command = suggestions[selectedCommand];
2095
2308
  if (command) {
2096
2309
  updateFoldBlock(null);
2097
- setInput(command.replacement);
2310
+ setInput(command.replacement, command.replacement.length, 'step');
2098
2311
  }
2099
2312
  return;
2100
2313
  }
@@ -2182,10 +2395,18 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2182
2395
  setSelectedCommand(index => index <= 0 ? suggestions.length - 1 : index - 1);
2183
2396
  return;
2184
2397
  }
2185
- seedHistory();
2186
- if (history.current.length === 0)
2398
+ // A workspace switch mid-walk keeps the draft that walk started from:
2399
+ // the composer shows the previous project's entry, not a new draft.
2400
+ const interrupted = seedHistory();
2401
+ if (history.current.length === 0) {
2402
+ if (interrupted)
2403
+ restoreHistoryDraft();
2187
2404
  return;
2188
- if (historyIndex.current < 0) {
2405
+ }
2406
+ if (interrupted) {
2407
+ historyIndex.current = history.current.length - 1;
2408
+ }
2409
+ else if (historyIndex.current < 0) {
2189
2410
  historyDraft.current = {
2190
2411
  text: value,
2191
2412
  images: imageRefsFor(value),
@@ -2201,7 +2422,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2201
2422
  retractRecalledCopy(entry.text);
2202
2423
  updateFoldBlock(null);
2203
2424
  restoreDraftImages(entry);
2204
- setInput(entry.text);
2425
+ setInput(entry.text, entry.text.length, 'silent');
2205
2426
  return;
2206
2427
  }
2207
2428
  if (key.downArrow) {
@@ -2253,20 +2474,23 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2253
2474
  setSelectedCommand(index => index >= suggestions.length - 1 ? 0 : index + 1);
2254
2475
  return;
2255
2476
  }
2477
+ // A walk cut short by a workspace switch has no position in the new
2478
+ // project's list; ↓ just ends it and returns the draft.
2479
+ if (seedHistory()) {
2480
+ restoreHistoryDraft();
2481
+ return;
2482
+ }
2256
2483
  if (historyIndex.current < 0)
2257
2484
  return;
2258
2485
  if (historyIndex.current >= history.current.length - 1) {
2259
- historyIndex.current = -1;
2260
- updateFoldBlock(null);
2261
- restoreDraftImages(historyDraft.current);
2262
- setInput(historyDraft.current.text);
2486
+ restoreHistoryDraft();
2263
2487
  }
2264
2488
  else {
2265
2489
  historyIndex.current += 1;
2266
2490
  const entry = history.current[historyIndex.current];
2267
2491
  if (entry !== undefined) {
2268
2492
  restoreDraftImages(entry);
2269
- setInput(entry.text);
2493
+ setInput(entry.text, entry.text.length, 'silent');
2270
2494
  }
2271
2495
  }
2272
2496
  return;
@@ -2355,10 +2579,19 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2355
2579
  deleteInputRange(cursor, end);
2356
2580
  return;
2357
2581
  }
2582
+ if (actionMatches('undo', input, key)) {
2583
+ // Word-level draft undo (remappable via /settings). Only the prompt
2584
+ // draft has an undo space: with the keyboard owned by a panel or a
2585
+ // dialog this handler is inactive (useInput isActive), and an empty
2586
+ // stack is a no-op rather than the rewind picker.
2587
+ undoDraft();
2588
+ return;
2589
+ }
2358
2590
  if (isMod(key) && input === 'w') {
2359
- // Delete the word before the cursor: skip
2360
- // trailing whitespace, then the whitespace-delimited word. The
2361
- // deletion start never crosses into the block.
2591
+ // Delete the word before the cursor: readline's whitespace-delimited
2592
+ // rule (skip trailing whitespace, then the word). The ICU/CJK
2593
+ // alternative is a separate editor-UX change, not part of draft undo.
2594
+ // The deletion start never crosses into the block.
2362
2595
  const before = value.slice(0, cursor);
2363
2596
  let end = before.length;
2364
2597
  while (end > 0 && /\s/.test(before[end - 1]))
@@ -2375,25 +2608,30 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2375
2608
  // NORMAL submode: bare characters and Esc are vim keys; help/command/
2376
2609
  // file overlays, modified combos, Tab, Enter, arrows and other
2377
2610
  // structured keys keep their existing handlers.
2378
- const vimNormalEdit = (next, cursorOffset) => {
2379
- updateFoldBlock(null);
2380
- setInput(next, cursorOffset);
2381
- setSelectedCommand(0);
2382
- setFileSelected(0);
2383
- };
2611
+ /**
2612
+ * Push one vim NORMAL-mode step. Vim edits never touch the draft stack:
2613
+ * `u` and `Ctrl+Z` are two independent histories (see {@link vimUndoRef}).
2614
+ */
2384
2615
  const vimPushUndo = () => {
2385
2616
  const images = imageRefsFor(valueRef.current);
2386
- const evicted = vimUndoRef.current.length >= 100 ? vimUndoRef.current.shift() : undefined;
2617
+ const evicted = vimUndoRef.current.length >= UNDO_LIMIT ? vimUndoRef.current.shift() : undefined;
2387
2618
  vimUndoRef.current.push({ text: valueRef.current, cursor: cursorRef.current, images });
2388
- if (evicted !== undefined)
2619
+ if (evicted !== undefined) {
2389
2620
  discardUnretainedImages(evicted.images.map(image => image.stageId));
2621
+ }
2622
+ };
2623
+ const vimNormalEdit = (next, cursorOffset) => {
2624
+ updateFoldBlock(null);
2625
+ setInput(next, cursorOffset, 'silent');
2626
+ setSelectedCommand(0);
2627
+ setFileSelected(0);
2390
2628
  };
2391
2629
  const vimDeleteRange = (start, end) => {
2392
2630
  if (start >= end)
2393
2631
  return;
2394
2632
  vimPushUndo();
2395
2633
  updateFoldBlock(null);
2396
- deleteInputRange(start, end);
2634
+ deleteInputRange(start, end, 'silent');
2397
2635
  setSelectedCommand(0);
2398
2636
  setFileSelected(0);
2399
2637
  };
@@ -2510,7 +2748,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2510
2748
  case 'd':
2511
2749
  vimPendingRef.current = 'd';
2512
2750
  return;
2513
- case 'u': { // undo the last vim edit
2751
+ case 'u': { // undo the last vim edit (its own stack, not Ctrl+Z's)
2514
2752
  syncImageGeneration();
2515
2753
  const prev = vimUndoRef.current.pop();
2516
2754
  if (prev === undefined)
@@ -2518,7 +2756,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2518
2756
  advanceDraftRevision();
2519
2757
  replaceDraftImages(prev.images);
2520
2758
  updateFoldBlock(null);
2521
- setInput(prev.text, prev.cursor);
2759
+ setInput(prev.text, prev.cursor, 'silent');
2522
2760
  setSelectedCommand(0);
2523
2761
  setFileSelected(0);
2524
2762
  return;
@@ -2591,12 +2829,17 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2591
2829
  return;
2592
2830
  // `/` opens the slash-command menu even in NORMAL: insert it and
2593
2831
  // switch to INSERT so the rest of the command types normally
2594
- // (the menu then owns the keys while it is open).
2832
+ // (the menu then owns the keys while it is open). The insertion is
2833
+ // a NORMAL command, so it lands on the vim undo stack (`u` reverts
2834
+ // it); anything typed afterwards is a draft edit whose first
2835
+ // snapshot already contains the `/`, so `Ctrl+Z` never peels the
2836
+ // `/` off on its own.
2595
2837
  if (input === '/') {
2596
2838
  const text = valueRef.current;
2597
2839
  const pos = cursorRef.current;
2598
2840
  const next = text.slice(0, pos) + '/' + text.slice(pos);
2599
- setInput(next, pos + 1);
2841
+ vimPushUndo(); // NORMAL command: one vim-undo step, not a draft edit
2842
+ setInput(next, pos + 1, 'silent');
2600
2843
  setSelectedCommand(0);
2601
2844
  setFileSelected(0);
2602
2845
  vimInsertRef.current = true;
@@ -2634,11 +2877,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2634
2877
  // A single Esc closes the open command menu first;
2635
2878
  // the double-tap-clear semantics only apply to ordinary input.
2636
2879
  if (overlayOpen) {
2637
- syncImageGeneration();
2638
- discardDraftImages();
2639
- setInput('', 0);
2640
- setSelectedCommand(0);
2641
- setFileSelected(0);
2880
+ clearDraftUndoably();
2642
2881
  return;
2643
2882
  }
2644
2883
  // File overlay: Esc dismisses the menu for THIS token only — clearing
@@ -2660,6 +2899,18 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2660
2899
  });
2661
2900
  return;
2662
2901
  }
2902
+ // "Send to Chat" chips peel before the draft: the first Esc drops the
2903
+ // contexts panels staged and leaves the typed text untouched, so a second
2904
+ // Esc then clears the input as usual. The press is CONSUMED — this rung
2905
+ // has exactly one meaning, and a running turn must not be interrupted by
2906
+ // the same key that only dropped a chip.
2907
+ const stagedContexts = channel.attachedContexts ?? [];
2908
+ if (stagedContexts.length > 0) {
2909
+ event?.stopImmediatePropagation();
2910
+ for (const staged of [...stagedContexts])
2911
+ channel.detachContext(staged.id);
2912
+ return;
2913
+ }
2663
2914
  // A single Esc clears the current input (if any); the double-tap
2664
2915
  // path below handles rewind/clear on an already-empty input. A BIG
2665
2916
  // input folds into a block instead (Esc = the fold toggle; the
@@ -2672,28 +2923,19 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2672
2923
  setFileSelected(0);
2673
2924
  return;
2674
2925
  }
2675
- syncImageGeneration();
2676
- discardDraftImages();
2677
- setInput('', 0);
2678
- setSelectedCommand(0);
2679
- setFileSelected(0);
2926
+ // Esc clearing the draft is undoable (D4): Ctrl+Z brings the whole
2927
+ // draft back, images and fold block included.
2928
+ clearDraftUndoably();
2680
2929
  return;
2681
2930
  }
2682
- // Double-tap Esc: clear the input when it has content; when empty,
2683
- // open the rewind picker (double-tap Esc rewinds the selected message
2684
- // and/or conversation to a previous point in time).
2931
+ // Double-tap Esc opens the rewind picker. Reaching here means the draft
2932
+ // is empty: every non-empty branch above returns after clearing, so the
2933
+ // second tap can only be the rewind gesture.
2685
2934
  if (escPendingRef.current) {
2686
2935
  escPendingRef.current = false;
2687
2936
  if (escTimerRef.current)
2688
2937
  clearTimeout(escTimerRef.current);
2689
- if (value.length === 0) {
2690
- onRewindRequest?.();
2691
- }
2692
- else {
2693
- syncImageGeneration();
2694
- discardDraftImages();
2695
- setInput('', 0);
2696
- }
2938
+ onRewindRequest?.();
2697
2939
  return;
2698
2940
  }
2699
2941
  escPendingRef.current = true;
@@ -2717,7 +2959,9 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
2717
2959
  const next = sel
2718
2960
  ? value.slice(0, sel.start) + input + value.slice(sel.end)
2719
2961
  : value.slice(0, cursor) + input + value.slice(cursor);
2720
- setInput(next, at + input.length);
2962
+ // Replacing a selection is atomic, exactly like `insertAtCaret`: the
2963
+ // replaced span must not fold into the surrounding typed run.
2964
+ setInput(next, at + input.length, sel ? 'step' : 'group');
2721
2965
  setSelectedCommand(0);
2722
2966
  setFileSelected(0);
2723
2967
  }
@@ -3010,6 +3254,11 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
3010
3254
  prevContentRowsRef.current = contentRows;
3011
3255
  }, [contentRows]);
3012
3256
  const lastNotification = channel.notifications[channel.notifications.length - 1];
3257
+ // The pet panel can "say" a notice with its speech bubble instead: while
3258
+ // that panel is the ACTIVE one the host suppresses the duplicate toast
3259
+ // here (errors always toast — they may need action). Same render, same
3260
+ // commit, so the swap cannot flash one frame of toast first.
3261
+ const toastVisible = lastNotification !== undefined && (toastSuppressed?.(lastNotification) ?? false) !== true;
3013
3262
  // Park the native terminal cursor at the input caret (via the renderer's
3014
3263
  // cursor-declaration mechanism). Terminal emulators render IME preedit
3015
3264
  // text and screen-reader focus at the physical cursor, so parking it at
@@ -3246,6 +3495,13 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
3246
3495
  // 顶边框右侧的会话名标签 chip:色随强调色;超宽截断,宽度
3247
3496
  // 随终端列数伸缩但不超过 28 显示单元。默认关闭——`/settings` 的
3248
3497
  // 「会话名标签」开关(dsh-tui.promptSessionLabel)开启后显示。
3498
+ // "Send to Chat" chips (side-panel §6.7): the contexts panels staged for the
3499
+ // next submission, rendered as one strip directly above the prompt border.
3500
+ // Read defensively — hosts and older fixtures mount this composer with a
3501
+ // partial channel stub (Chat guards `agentViewRows` for the same reason),
3502
+ // and a missing projection means "nothing attached". A render-time TypeError
3503
+ // here would be swallowed by ink and silently freeze the screen.
3504
+ const attachedContexts = channel.attachedContexts ?? [];
3249
3505
  const sessionTitle = channel.sessionTitle ?? '';
3250
3506
  const topRightLabel = channel.promptSessionLabel === true && sessionTitle !== ''
3251
3507
  ? {
@@ -3338,7 +3594,8 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
3338
3594
  return (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [floatersOpen && (_jsxs(OverlayAbove, { maxHeight: Math.max(terminalRows - 6, 1), children: [helpOpen && (_jsx(Box, { marginBottom: 1, children: _jsx(HelpMenu, { commands: channel.commandList, viewportHeight: helpViewportHeight, viewportWidth: columns, scrollRef: helpScrollRef, onCommandPick: (name) => {
3339
3595
  // 点击命令行 = 填入 /name 并关闭帮助(Tab 补全的鼠标等价)
3340
3596
  updateFoldBlock(null);
3341
- setInput(`/${name} `);
3597
+ // `/${name} ` — the whole fill is one undo step.
3598
+ setInput(`/${name} `, name.length + 2, 'step');
3342
3599
  onToggleHelp();
3343
3600
  } }) })), !helpOpen && channel.pending.length > 0 && (_jsxs(Box, { flexDirection: "column", paddingLeft: 2, paddingBottom: 1, children: [channel.pending.some(item => item.placement === 'steer') && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: ["\u26A1 ", t('input-pending-steer-label')] }), channel.pending
3344
3601
  .filter(item => item.placement === 'steer')
@@ -3369,7 +3626,7 @@ export function PromptInput({ channel, suspended = false, draftCache, helpOpen,
3369
3626
  : undefined, rows: peekVisualLines.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, index))), onRowPick: () => {
3370
3627
  updateFoldBlock(null);
3371
3628
  setHovered(false);
3372
- } }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [onOpenSessions !== undefined && (_jsx(Box, { flexShrink: 0, onClick: (event) => {
3629
+ } }) }))] })), attachedContexts.length > 0 && (_jsx(Box, { flexDirection: "row", width: "100%", height: 1, overflow: "hidden", paddingLeft: 2, columnGap: 2, children: attachedContexts.map(item => (_jsx(Box, { overflow: "hidden", children: _jsx(Text, { color: promptAccent, wrap: "truncate-end", children: t('prompt-attached-context-chip', { title: item.title }) }) }, item.id))) })), lastNotification && toastVisible && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [onOpenSessions !== undefined && (_jsx(Box, { flexShrink: 0, onClick: (event) => {
3373
3630
  event.stopImmediatePropagation();
3374
3631
  onOpenSessions?.();
3375
3632
  }, onMouseEnter: () => {