@deepseek-harness-tui/dsh-tui 0.10.2 → 0.11.1

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 (351) hide show
  1. package/README.md +175 -123
  2. package/bin/dsh-tui.js +748 -22
  3. package/cordis.patch.yml +42 -13
  4. package/cordis.yml +5 -2
  5. package/lib/types/activityPrefs.d.ts +24 -0
  6. package/lib/types/activityPrefs.d.ts.map +1 -1
  7. package/lib/types/activityPrefs.js +50 -1
  8. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  9. package/lib/types/adapter/channel/session-projection.js +2 -0
  10. package/lib/types/adapter/channel/state.d.ts.map +1 -1
  11. package/lib/types/adapter/channel/state.js +3 -1
  12. package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
  13. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  14. package/lib/types/adapter/channel/ui-policy.js +6 -2
  15. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  16. package/lib/types/adapter/kernel/host-facade.js +1 -0
  17. package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
  18. package/lib/types/adapter/kernel/replay.js +3 -0
  19. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  20. package/lib/types/adapter/kernel/runtime.js +2 -0
  21. package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
  22. package/lib/types/adapter/kernel/slices/workspace.js +1 -0
  23. package/lib/types/adapter/ports/channel-ui.d.ts +29 -5
  24. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  25. package/lib/types/adapter/ports/channel-view.d.ts +66 -12
  26. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  27. package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
  28. package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
  29. package/lib/types/adapter/ports/channel.d.ts +4 -1
  30. package/lib/types/adapter/ports/channel.d.ts.map +1 -1
  31. package/lib/types/adapter/ports/workspace.d.ts +2 -0
  32. package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
  33. package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
  34. package/lib/types/adapter/upstream/workspace-driver.js +10 -2
  35. package/lib/types/commands.d.ts.map +1 -1
  36. package/lib/types/commands.js +1 -0
  37. package/lib/types/components/ActivityLine.d.ts +7 -2
  38. package/lib/types/components/ActivityLine.d.ts.map +1 -1
  39. package/lib/types/components/ActivityLine.js +0 -8
  40. package/lib/types/components/ContextBarView.d.ts +15 -7
  41. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  42. package/lib/types/components/ContextBarView.js +26 -22
  43. package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
  44. package/lib/types/components/ImagePreviewOverlay.js +11 -5
  45. package/lib/types/components/Markdown.d.ts +11 -1
  46. package/lib/types/components/Markdown.d.ts.map +1 -1
  47. package/lib/types/components/Markdown.js +20 -2
  48. package/lib/types/components/MermaidDiagram.d.ts +13 -0
  49. package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
  50. package/lib/types/components/MermaidDiagram.js +61 -0
  51. package/lib/types/components/MessageList.d.ts +26 -1
  52. package/lib/types/components/MessageList.d.ts.map +1 -1
  53. package/lib/types/components/MessageList.js +74 -8
  54. package/lib/types/components/PageMargin.d.ts +1 -1
  55. package/lib/types/components/PromptInput.d.ts +37 -1
  56. package/lib/types/components/PromptInput.d.ts.map +1 -1
  57. package/lib/types/components/PromptInput.js +291 -30
  58. package/lib/types/components/ScrollbarGutter.d.ts +20 -5
  59. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
  60. package/lib/types/components/ScrollbarGutter.js +88 -10
  61. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  62. package/lib/types/components/SplitDiffView.js +2 -1
  63. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  64. package/lib/types/components/StreamingMarkdown.js +30 -28
  65. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  66. package/lib/types/components/SubagentCard.js +12 -6
  67. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  68. package/lib/types/components/SubagentDetailScene.js +8 -3
  69. package/lib/types/components/Tooltip.d.ts.map +1 -1
  70. package/lib/types/components/Tooltip.js +45 -7
  71. package/lib/types/components/design-system/HintLine.d.ts +1 -2
  72. package/lib/types/components/design-system/HintLine.d.ts.map +1 -1
  73. package/lib/types/components/design-system/HintLine.js +1 -2
  74. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
  75. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  76. package/lib/types/components/messages/AssistantThinkingMessage.js +2 -1
  77. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -2
  78. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  79. package/lib/types/components/messages/AssistantToolUseMessage.js +30 -26
  80. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  81. package/lib/types/components/messages/UserPromptMessage.js +5 -7
  82. package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
  83. package/lib/types/components/messages/useImageInspection.js +19 -9
  84. package/lib/types/components/promptDraftCache.d.ts +102 -0
  85. package/lib/types/components/promptDraftCache.d.ts.map +1 -0
  86. package/lib/types/components/promptDraftCache.js +76 -0
  87. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
  88. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  89. package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
  90. package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
  91. package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
  92. package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
  93. package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
  94. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  95. package/lib/types/components/sessions/SessionListRow.js +52 -5
  96. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
  97. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
  98. package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
  99. package/lib/types/dsh-adapter/activity-store.d.ts +221 -0
  100. package/lib/types/dsh-adapter/activity-store.d.ts.map +1 -0
  101. package/lib/types/dsh-adapter/activity-store.js +359 -0
  102. package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
  103. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
  104. package/lib/types/dsh-adapter/bundled-presets.js +80 -0
  105. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
  106. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  107. package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
  108. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
  109. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
  110. package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
  111. package/lib/types/dsh-adapter/channel/background-action.d.ts +1 -1
  112. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  113. package/lib/types/dsh-adapter/channel/background-action.js +53 -29
  114. package/lib/types/dsh-adapter/channel/binding-events.d.ts +8 -6
  115. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  116. package/lib/types/dsh-adapter/channel/binding-events.js +5 -4
  117. package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
  118. package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
  119. package/lib/types/dsh-adapter/channel/binding.js +78 -18
  120. package/lib/types/dsh-adapter/channel/compaction.d.ts +0 -1
  121. package/lib/types/dsh-adapter/channel/compaction.d.ts.map +1 -1
  122. package/lib/types/dsh-adapter/channel/compaction.js +0 -2
  123. package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
  124. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  125. package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
  126. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
  127. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  128. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
  129. package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
  130. package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
  131. package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
  132. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
  133. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  134. package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
  135. package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
  136. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  137. package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
  138. package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
  139. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  140. package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
  141. package/lib/types/dsh-adapter/channel/model-switch.d.ts +1 -2
  142. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  143. package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
  144. package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
  145. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  146. package/lib/types/dsh-adapter/channel/projection.js +29 -19
  147. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  148. package/lib/types/dsh-adapter/channel/reports.js +4 -12
  149. package/lib/types/dsh-adapter/channel/session-adoption.d.ts +1 -1
  150. package/lib/types/dsh-adapter/channel/session-adoption.d.ts.map +1 -1
  151. package/lib/types/dsh-adapter/channel/session-adoption.js +1 -0
  152. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  153. package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
  154. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +4 -1
  155. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
  156. package/lib/types/dsh-adapter/channel/session-live-adoption.js +2 -0
  157. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
  158. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  159. package/lib/types/dsh-adapter/channel/session-metadata.js +6 -4
  160. package/lib/types/dsh-adapter/channel/session-reset.d.ts +1 -1
  161. package/lib/types/dsh-adapter/channel/session-reset.d.ts.map +1 -1
  162. package/lib/types/dsh-adapter/channel/session-reset.js +0 -1
  163. package/lib/types/dsh-adapter/channel/session-resume.d.ts +12 -3
  164. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  165. package/lib/types/dsh-adapter/channel/session-resume.js +292 -94
  166. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  167. package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
  168. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  169. package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
  170. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  171. package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
  172. package/lib/types/dsh-adapter/channel/state.d.ts +6 -1
  173. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  174. package/lib/types/dsh-adapter/channel/state.js +3 -3
  175. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts +21 -1
  176. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  177. package/lib/types/dsh-adapter/channel/subagent-projection.js +173 -6
  178. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  179. package/lib/types/dsh-adapter/channel/transcript.js +6 -12
  180. package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
  181. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  182. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
  183. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  184. package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
  185. package/lib/types/dsh-adapter/channel.d.ts +1 -1
  186. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  187. package/lib/types/dsh-adapter/channel.js +82 -20
  188. package/lib/types/dsh-adapter/compat/index.d.ts +1 -1
  189. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  190. package/lib/types/dsh-adapter/compat/index.js +1 -1
  191. package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
  192. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
  193. package/lib/types/dsh-adapter/compat/messages.js +15 -0
  194. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +18 -0
  195. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  196. package/lib/types/dsh-adapter/compat/sessionLog.js +18 -3
  197. package/lib/types/dsh-adapter/compat/settings.d.ts +35 -0
  198. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
  199. package/lib/types/dsh-adapter/compat/settings.js +57 -0
  200. package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
  201. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
  202. package/lib/types/dsh-adapter/compat/shell.js +11 -0
  203. package/lib/types/dsh-adapter/contract.d.ts +5 -4
  204. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  205. package/lib/types/dsh-adapter/contract.js +17 -6
  206. package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
  207. package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
  208. package/lib/types/dsh-adapter/ide-channel.js +556 -0
  209. package/lib/types/dsh-adapter/index.d.ts +18 -3
  210. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  211. package/lib/types/dsh-adapter/index.js +32 -5
  212. package/lib/types/dsh-adapter/plugin.d.ts +4 -1
  213. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  214. package/lib/types/dsh-adapter/plugin.js +197 -49
  215. package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
  216. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  217. package/lib/types/dsh-adapter/preset-resolution.js +10 -0
  218. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
  219. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
  220. package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
  221. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  222. package/lib/types/dsh-adapter/sessionTree.js +3 -2
  223. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  224. package/lib/types/dsh-adapter/settings-sections.js +7 -4
  225. package/lib/types/dsh-adapter/subagents.d.ts +16 -3
  226. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  227. package/lib/types/dsh-adapter/subagents.js +92 -29
  228. package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
  229. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  230. package/lib/types/dsh-adapter/workspaces.js +116 -0
  231. package/lib/types/effortPrefs.d.ts +15 -3
  232. package/lib/types/effortPrefs.d.ts.map +1 -1
  233. package/lib/types/effortPrefs.js +37 -3
  234. package/lib/types/history.d.ts +13 -1
  235. package/lib/types/history.d.ts.map +1 -1
  236. package/lib/types/history.js +15 -2
  237. package/lib/types/homePrefs.d.ts +15 -0
  238. package/lib/types/homePrefs.d.ts.map +1 -0
  239. package/lib/types/homePrefs.js +53 -0
  240. package/lib/types/i18n.d.ts +347 -297
  241. package/lib/types/i18n.d.ts.map +1 -1
  242. package/lib/types/i18n.js +133 -85
  243. package/lib/types/ink/colorize.d.ts +10 -0
  244. package/lib/types/ink/colorize.d.ts.map +1 -1
  245. package/lib/types/ink/colorize.js +21 -0
  246. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  247. package/lib/types/ink/components/ScrollBox.js +12 -1
  248. package/lib/types/ink/dom.d.ts +1 -0
  249. package/lib/types/ink/dom.d.ts.map +1 -1
  250. package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
  251. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  252. package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
  253. package/lib/types/ink/ink.d.ts.map +1 -1
  254. package/lib/types/ink/ink.js +36 -1
  255. package/lib/types/ink/log-update.d.ts.map +1 -1
  256. package/lib/types/ink/log-update.js +19 -1
  257. package/lib/types/ink/output.d.ts +10 -1
  258. package/lib/types/ink/output.d.ts.map +1 -1
  259. package/lib/types/ink/output.js +109 -17
  260. package/lib/types/ink/render-border.d.ts +6 -1
  261. package/lib/types/ink/render-border.d.ts.map +1 -1
  262. package/lib/types/ink/render-border.js +25 -9
  263. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  264. package/lib/types/ink/render-node-to-output.js +48 -6
  265. package/lib/types/ink/renderer.d.ts +6 -0
  266. package/lib/types/ink/renderer.d.ts.map +1 -1
  267. package/lib/types/ink/renderer.js +2 -1
  268. package/lib/types/ink/selection.d.ts +88 -0
  269. package/lib/types/ink/selection.d.ts.map +1 -1
  270. package/lib/types/ink/selection.js +164 -0
  271. package/lib/types/ink/sixel-graphics.d.ts +19 -1
  272. package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
  273. package/lib/types/ink/sixel-graphics.js +124 -12
  274. package/lib/types/ink/terminal-image.d.ts +18 -1
  275. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  276. package/lib/types/ink/terminal.d.ts +17 -0
  277. package/lib/types/ink/terminal.d.ts.map +1 -1
  278. package/lib/types/ink/terminal.js +20 -1
  279. package/lib/types/screens/Chat.d.ts +20 -1
  280. package/lib/types/screens/Chat.d.ts.map +1 -1
  281. package/lib/types/screens/Chat.js +296 -76
  282. package/lib/types/screens/SessionSupervisor.d.ts +84 -0
  283. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
  284. package/lib/types/screens/SessionSupervisor.js +350 -0
  285. package/lib/types/screens/SessionTree.js +2 -2
  286. package/lib/types/screens/Settings.d.ts.map +1 -1
  287. package/lib/types/screens/Settings.js +32 -9
  288. package/lib/types/screens/StatusLine.d.ts +18 -1
  289. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  290. package/lib/types/screens/StatusLine.js +38 -19
  291. package/lib/types/screens/StatusMetrics.d.ts +79 -22
  292. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  293. package/lib/types/screens/StatusMetrics.js +153 -88
  294. package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
  295. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
  296. package/lib/types/screens/sessionSupervisor/model.js +69 -0
  297. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
  298. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
  299. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
  300. package/lib/types/sessionMounts.d.ts +272 -0
  301. package/lib/types/sessionMounts.d.ts.map +1 -0
  302. package/lib/types/sessionMounts.js +706 -0
  303. package/lib/types/sessions/format.d.ts.map +1 -1
  304. package/lib/types/sessions/format.js +4 -1
  305. package/lib/types/sessions/resumeFailure.d.ts +38 -0
  306. package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
  307. package/lib/types/sessions/resumeFailure.js +58 -0
  308. package/lib/types/terminal-utils/mermaid.d.ts +44 -0
  309. package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
  310. package/lib/types/terminal-utils/mermaid.js +83 -0
  311. package/lib/types/tips.d.ts.map +1 -1
  312. package/lib/types/tips.js +175 -19
  313. package/lib/types/tuiDisplayPrefs.d.ts +7 -9
  314. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  315. package/lib/types/tuiDisplayPrefs.js +31 -28
  316. package/lib/types/update.d.ts +61 -0
  317. package/lib/types/update.d.ts.map +1 -1
  318. package/lib/types/update.js +194 -0
  319. package/lib/types/utils/fold-long-lines.d.ts +1 -1
  320. package/lib/types/utils/fold-long-lines.js +1 -1
  321. package/lib/types/utils/imageResize.d.ts +90 -0
  322. package/lib/types/utils/imageResize.d.ts.map +1 -0
  323. package/lib/types/utils/imageResize.js +272 -0
  324. package/lib/types/utils/keymap.d.ts +5 -1
  325. package/lib/types/utils/keymap.d.ts.map +1 -1
  326. package/lib/types/utils/keymap.js +23 -0
  327. package/lib/types/working-activity.d.ts +10 -9
  328. package/lib/types/working-activity.d.ts.map +1 -1
  329. package/lib/types/working-activity.js +17 -10
  330. package/lib/types/workspaces.d.ts +2 -1
  331. package/lib/types/workspaces.d.ts.map +1 -1
  332. package/lib/types/workspaces.js +1 -1
  333. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
  334. package/package.json +126 -105
  335. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  336. package/presets/liangshen/agent.cordis.yml +15 -0
  337. package/lib/types/components/design-system/KeyboardShortcutHint.d.ts +0 -19
  338. package/lib/types/components/design-system/KeyboardShortcutHint.d.ts.map +0 -1
  339. package/lib/types/components/design-system/KeyboardShortcutHint.js +0 -14
  340. package/lib/types/dsh-adapter/channel/activity.d.ts +0 -20
  341. package/lib/types/dsh-adapter/channel/activity.d.ts.map +0 -1
  342. package/lib/types/dsh-adapter/channel/activity.js +0 -113
  343. package/lib/types/screens/AgentView.d.ts +0 -23
  344. package/lib/types/screens/AgentView.d.ts.map +0 -1
  345. package/lib/types/screens/AgentView.js +0 -546
  346. package/lib/types/screens/SessionBrowser.d.ts +0 -42
  347. package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
  348. package/lib/types/screens/SessionBrowser.js +0 -911
  349. package/lib/types/terminal-utils/terminal.d.ts +0 -18
  350. package/lib/types/terminal-utils/terminal.d.ts.map +0 -1
  351. package/lib/types/terminal-utils/terminal.js +0 -96
@@ -24,7 +24,7 @@ import { imagePathMediaType, parsePastedImagePath, stageClipboardFilePaths } fro
24
24
  import { editInExternalEditor } from '../utils/externalEditor.js';
25
25
  import { setPromptEditorNode, EditorButton } from './PromptEditor.js';
26
26
  import { isHiddenCommandName, parseCommandName } from '../commands.js';
27
- import { appendHistory } from '../history.js';
27
+ import { appendHistory, HISTORY_LIMIT, loadHistoryOldestFirst } from '../history.js';
28
28
  import { mentionAtCaret } from '../utils/mentions.js';
29
29
  import { preserveSelection } from '../utils/fileSuggestions.js';
30
30
  import { isMod } from '../utils/modifiers.js';
@@ -34,7 +34,25 @@ import { FileSuggestions } from './FileSuggestions.js';
34
34
  import { HelpMenu } from './HelpMenu.js';
35
35
  import { OverlayAbove } from './OverlayAbove.js';
36
36
  import { SuggestionCard, cardContentWidth } from './SuggestionCard.js';
37
- const HISTORY_LIMIT = 50;
37
+ import { filterLiveImageBindings, isUsableDraftSnapshot, resolveBindingGeneration, } from './promptDraftCache.js';
38
+ /**
39
+ * Visible text of the session-entry control at the head of the input row:
40
+ * U+2338 APL FUNCTIONAL SYMBOL QUAD COLON plus a separator space.
41
+ *
42
+ * The glyph is chosen for ONE property above all others: the layout model and
43
+ * the terminal cell table must agree on how many columns it occupies. `\u2338`
44
+ * is a plain single-cell symbol with no emoji presentation, so
45
+ * `stringWidth` and the terminal both say 1 + 1 = 2 and
46
+ * {@link HOME_BUTTON_COLS} can be derived rather than guessed.
47
+ *
48
+ * The emoji house (`U+1F3E0`) was tried and rejected on this exact ground: it
49
+ * costs the terminal ONE cell while `stringWidth` charges the row TWO, and
50
+ * appending VARIATION SELECTOR-15 (text presentation) does not change either
51
+ * number. That standing one-column disagreement put every caret-relative
52
+ * column in the composer off by one, so no offset compensation made it work.
53
+ */
54
+ const HOME_BUTTON_TEXT = '⌸ ';
55
+ const HOME_BUTTON_COLS = stringWidth(HOME_BUTTON_TEXT);
38
56
  /**
39
57
  * Paste fold with a visible preview and no black box:
40
58
  * a paste that leaves the input this big folds into a one-line chip
@@ -67,6 +85,9 @@ function sanitizeEditableText(text) {
67
85
  .replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/gu, '');
68
86
  }
69
87
  const COMPOSER_IMAGE_TOKEN = /\[Image #\d+\]/gu;
88
+ /** Format label for one image media type, matching the image preview card's
89
+ * title (`JPEG`, `PNG`, `WEBP`, `GIF`). */
90
+ const mediaTypeLabel = (mediaType) => mediaType.replace(/^image\//u, '').replace(/\+xml$/u, '').toUpperCase();
70
91
  /** Every `[Image #N]` in `text`, in order. */
71
92
  function imageTokenSpans(text) {
72
93
  const spans = [];
@@ -352,13 +373,70 @@ const DOUBLE_CLICK_MS = 500;
352
373
  * working) interrupts the turn and delivers them right away; Ctrl+Enter
353
374
  * aborts the turn and sends the current input immediately.
354
375
  */
355
- export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen = false, onDismissCaretPreview, }) {
376
+ export function PromptInput({ channel, suspended = false, draftCache, helpOpen, onToggleHelp, onRunCommand, selectionActive, fillText, onFillConsumed, onRewindRequest, onBackgroundRequest, onOpenSessions, backgroundAgentsNeedingInput, controllerRef, onCaretImage, caretPreviewOpen = false, onDismissCaretPreview, }) {
356
377
  const [themeName] = useTheme();
357
378
  // Raw stdout writer for OSC 52 clipboard writes (selection copy) — must
358
379
  // bypass the frame pipeline; null outside a mounted Ink App.
359
380
  const writeRaw = React.useContext(TerminalWriteContext);
381
+ // The composer owns its text in local state, so it starts EMPTY and adopts
382
+ // whatever draft the owner stored in a MOUNT-TIME effect below. Reading the
383
+ // store in this initializer instead looks equivalent and is not: a non-empty
384
+ // first frame leaves `repro-resume-position` red — resuming a session parks
385
+ // the transcript mid-history instead of pinning it to the newest message.
386
+ // Adopting after mount keeps the first frame identical to a fresh composer
387
+ // while still handing the draft back.
360
388
  const [value, setValue] = React.useState('');
361
389
  const [cursor, setCursor] = React.useState(0);
390
+ /**
391
+ * Adopt the owner's draft ONCE, after mount.
392
+ *
393
+ * This is where a screen swap gives the draft back: the slot outlives this
394
+ * component, so a remount picks up what the user had written — text, caret,
395
+ * the image bindings behind the visible `[Image #N]` tokens, and the edit
396
+ * state around them (fold chip, fullscreen editor, vim mode).
397
+ *
398
+ * It runs as an effect rather than as the `useState` initial value on
399
+ * purpose. A composer whose FIRST frame is already non-empty moves the
400
+ * transcript's restored scroll position (`repro-resume-position`); adopting
401
+ * after the first commit keeps that frame identical to a fresh composer. The
402
+ * slot is consumed here, and the composer never writes to it while mounted,
403
+ * so the empty first value cannot overwrite the draft before this reads it.
404
+ */
405
+ const adoptDraft = React.useRef(true);
406
+ React.useEffect(() => {
407
+ if (draftCache === undefined || !adoptDraft.current)
408
+ return;
409
+ adoptDraft.current = false;
410
+ const snapshot = draftCache.current;
411
+ draftCache.current = null;
412
+ if (!isUsableDraftSnapshot(snapshot, String(channel.agentId), resolveBindingGeneration(channel)))
413
+ return;
414
+ const text = snapshot.value;
415
+ // Carried edit state, restored ahead of the text: the chip, the
416
+ // fullscreen editor and the vim mode/submode come back with the draft —
417
+ // and they come back even with NO text, because they are modes rather
418
+ // than content. Fold ranges are safe by the CAPTURE invariant, not by
419
+ // restore order: a snapshot's block, when set, always sits inside the
420
+ // snapshot's own text (setInput keeps or drops it atomically), and at
421
+ // mount the caret is 0 so updateFoldBlock's caret-drag clamp cannot fire
422
+ // here. The transient state around them — editor scroll, vim undo stack,
423
+ // selection — does not come back.
424
+ updateFoldBlock(snapshot.foldBlock);
425
+ expandedRef.current = snapshot.expanded;
426
+ setExpanded(snapshot.expanded);
427
+ vimEnabledRef.current = snapshot.vimEnabled;
428
+ setVimEnabled(snapshot.vimEnabled);
429
+ vimInsertRef.current = snapshot.vimInsert;
430
+ setVimInsert(snapshot.vimInsert);
431
+ if (text === '')
432
+ return;
433
+ const restoredCursor = normalizeCursorOffset(text, snapshot.cursor);
434
+ replaceDraftImages(filterLiveImageBindings(snapshot.images, stageId => channel.hasStagedImage?.(stageId) === true).map(([token, stageId]) => ({ token, stageId })));
435
+ valueRef.current = text;
436
+ cursorRef.current = restoredCursor;
437
+ setValue(text);
438
+ setCursor(restoredCursor);
439
+ }, [draftCache]);
362
440
  /**
363
441
  * Mouse text selection: UTF-16 offsets [start, end) in `value`, snapped
364
442
  * to grapheme boundaries, start ≤ end. Null = no selection. Created by
@@ -403,7 +481,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
403
481
  const foldBlockRef = React.useRef(null);
404
482
  /**
405
483
  * Fullscreen draft editor (`expandEditor`, default Ctrl+Shift+E, or the
406
- * ⛶ affordance at the end of the input row). While expanded the SAME
484
+ * ✎ affordance at the end of the input row). While expanded the SAME
407
485
  * editing state renders into the PromptEditorLayer cover (published via
408
486
  * setPromptEditorNode each render): Enter inserts a newline, Ctrl+Enter
409
487
  * submits, Esc collapses. The fold chip is bypassed (full text shown).
@@ -424,14 +502,16 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
424
502
  const prevExpandedRef = React.useRef(false);
425
503
  /**
426
504
  * Feature gate (settings `dsh-tui.expandEditor`, on by default; a mock
427
- * channel without the field also reads as on). Off hides the ⛶
505
+ * channel without the field also reads as on). Off hides the ✎
428
506
  * affordance and refuses the shortcut — the editor cannot open.
429
507
  */
430
508
  const expandEnabled = channel.expandEditor !== false;
431
509
  /** Latest expanded viewport metrics for the useInput wheel branch. */
432
510
  const editorViewportRef = React.useRef(null);
433
- /** Hover state of the ⤢/⛶ expand affordance in the input row. */
511
+ /** Hover state of the ⤢/✎ expand affordance in the input row. */
434
512
  const [expandHovered, setExpandHovered] = React.useState(false);
513
+ /** Hover state of the ⌂ session-list affordance at the head of the row. */
514
+ const [homeHovered, setHomeHovered] = React.useState(false);
435
515
  /** Pointer over the input box (drives the hover peek card). */
436
516
  const [hovered, setHovered] = React.useState(false);
437
517
  /** 120ms grace so the pointer crossing the input border row from the
@@ -454,6 +534,8 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
454
534
  const history = React.useRef([]);
455
535
  const historyIndex = React.useRef(-1);
456
536
  const historyDraft = React.useRef({ text: '', images: [] });
537
+ /** The persisted history is read lazily, once per mount (see seedHistory). */
538
+ const historySeeded = React.useRef(false);
457
539
  /** Visible `[Image #N]` labels are presentation only; this sidecar carries
458
540
  * the non-reusable capability for the current draft. History/rewind text
459
541
  * restored without this map can never bind to a later image by accident. */
@@ -550,6 +632,10 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
550
632
  syncImageGeneration();
551
633
  valueRef.current = value;
552
634
  cursorRef.current = cursor;
635
+ // Nothing is written to the owner's draft slot here — see the unmount
636
+ // hand-off below. A commit-time write would run BEFORE the restore effect
637
+ // has consumed the slot, so the composer's own first (empty) value would
638
+ // erase the draft it just came back for.
553
639
  // Publish the live controller (fresh closure over `value` every render).
554
640
  // A prompt-slot panel withdraws the handle in the same commit: external
555
641
  // injection must not append/submit a hidden command draft while it waits
@@ -563,6 +649,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
563
649
  }
564
650
  controllerRef.current = {
565
651
  hasText: () => value.length > 0,
652
+ text: () => valueRef.current,
566
653
  previewImages: () => composerImageRefsForText(valueRef.current, draftImagesRef.current).flatMap(ref => {
567
654
  const image = channel.stagedImage(ref.stageId);
568
655
  return image === undefined ? [] : [{ image, title: ref.token.slice(1, -1) }];
@@ -660,9 +747,56 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
660
747
  clearTimeout(escTimerRef.current);
661
748
  if (hoverLeaveTimerRef.current)
662
749
  clearTimeout(hoverLeaveTimerRef.current);
663
- // An async image read/stage may outlive this component. Revoke its
664
- // draft lease so it cannot bind an invisible capability after unmount.
665
- discardDraftImages();
750
+ /**
751
+ * Hand the draft to the owner's slot as this component goes away.
752
+ *
753
+ * This is the ONLY write to that slot, and it belongs here: it happens at
754
+ * the instant a screen replaces the composer, so it can neither race the
755
+ * restore effect nor change the transcript's first frame.
756
+ *
757
+ * `advanceDraftRevision` invalidates any image read/stage still in
758
+ * flight, so a late continuation cannot bind a capability into a composer
759
+ * that no longer exists. `clearVimUndo` releases the capabilities that
760
+ * only the undo stack was holding: they are not part of the draft, and
761
+ * nothing will ever restore them once this composer is gone.
762
+ *
763
+ * The stageIds the DRAFT holds are deliberately NOT revoked — they are
764
+ * part of the snapshot the slot now keeps, and the restore filters out
765
+ * whatever the channel revoked in the meantime. Revoking them here is why
766
+ * an image draft used to come back as inert text.
767
+ */
768
+ advanceDraftRevision();
769
+ clearVimUndo();
770
+ const images = [...draftImagesRef.current.entries()]
771
+ .map(([token, stageId]) => [token, stageId]);
772
+ draftImagesRef.current.clear();
773
+ if (draftCache === undefined) {
774
+ discardUnretainedImages(images.map(image => image[1]));
775
+ return;
776
+ }
777
+ const text = valueRef.current;
778
+ // The edit state rides along even with nothing typed: vim mode and the
779
+ // fullscreen editor are MODES the user turned on, not content, and an
780
+ // empty composer must not drop them (`text === ''` alone used to skip
781
+ // the snapshot entirely, losing the vim badge on a round trip with an
782
+ // empty composer).
783
+ const editState = foldBlockRef.current !== null || expandedRef.current
784
+ || vimEnabledRef.current;
785
+ if (text === '' && images.length === 0 && !editState) {
786
+ draftCache.current = null;
787
+ return;
788
+ }
789
+ draftCache.current = {
790
+ ownerAgentId: String(channel.agentId),
791
+ bindingGeneration: resolveBindingGeneration(channel),
792
+ value: text,
793
+ cursor: cursorRef.current,
794
+ foldBlock: foldBlockRef.current,
795
+ expanded: expandedRef.current,
796
+ vimEnabled: vimEnabledRef.current,
797
+ vimInsert: vimInsertRef.current,
798
+ images,
799
+ };
666
800
  };
667
801
  }, []);
668
802
  const { columns, rows: terminalRows } = useTerminalSize();
@@ -768,14 +902,17 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
768
902
  onCaretImageRef.current = onCaretImage;
769
903
  const lastCaretImageRef = React.useRef({ image: undefined, title: undefined });
770
904
  /** Tell the caller which staged image the caret is on. `'caret'` reports
771
- * only changes; `'click'` always reports (see onCaretImage). */
905
+ * only changes; `'click'` always reports (see onCaretImage). "Same image"
906
+ * is the attachment id plus the token, not facade identity: this runs
907
+ * after every commit and the caller stores what it reports, so an
908
+ * identity-only difference would re-render the caller forever (#885). */
772
909
  const reportCaretImage = (reason) => {
773
910
  const report = onCaretImageRef.current;
774
911
  if (report === undefined)
775
912
  return;
776
913
  const found = suspended ? undefined : caretImageAt(valueRef.current, cursorRef.current);
777
914
  const last = lastCaretImageRef.current;
778
- if (reason === 'caret' && last.image === found?.image && last.title === found?.title)
915
+ if (reason === 'caret' && last.image?.id === found?.image.id && last.title === found?.title)
779
916
  return;
780
917
  lastCaretImageRef.current = { image: found?.image, title: found?.title };
781
918
  report(found?.image, found?.title, reason);
@@ -960,7 +1097,26 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
960
1097
  return;
961
1098
  }
962
1099
  };
1100
+ /**
1101
+ * Seed the walk with the persisted history (issue #986). `↑`/`↓` used to
1102
+ * see only what this process submitted, so a restart lost every earlier
1103
+ * entry. Seeding before the first push keeps ONE chronological list —
1104
+ * persisted entries first, this run's submits behind them — instead of two
1105
+ * lists to merge at recall time. Restored text carries no image capability
1106
+ * (the file stores text only), which is also what keeps a recalled entry
1107
+ * from binding to a later staged image by accident.
1108
+ */
1109
+ const seedHistory = () => {
1110
+ if (historySeeded.current)
1111
+ return;
1112
+ historySeeded.current = true;
1113
+ history.current = loadHistoryOldestFirst().map(entry => ({ text: entry.text, images: [] }));
1114
+ historyIndex.current = -1;
1115
+ };
963
1116
  const rememberHistory = (text, images) => {
1117
+ // Both entries into the walk (a submit and ↑) must see the persisted
1118
+ // prefix, so seed here rather than merging two lists later.
1119
+ seedHistory();
964
1120
  history.current.push({
965
1121
  text,
966
1122
  images: images.map(image => ({ ...image })),
@@ -1067,6 +1223,30 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1067
1223
  setFileSelected(0);
1068
1224
  channel.notify(t('input-retracted'), { timeoutMs: 2000 });
1069
1225
  };
1226
+ /**
1227
+ * Withdraw the queued copy of the text `↑` just recalled (issue #986): the
1228
+ * message is still parked in the inbox, so editing it and sending again
1229
+ * would run the same text twice. Alt+Up withdraws explicitly; walking the
1230
+ * history to the same text has to land in the same place. The newest match
1231
+ * wins — `↑` walks newest-first and the queue is FIFO. A message the
1232
+ * running turn already claimed cannot be withdrawn, and saying so beats
1233
+ * pretending it was.
1234
+ */
1235
+ const retractRecalledCopy = (text) => {
1236
+ let target;
1237
+ for (const item of channel.pending) {
1238
+ if (item.text === text)
1239
+ target = item;
1240
+ }
1241
+ if (target === undefined)
1242
+ return;
1243
+ if (channel.removePending(target.id)) {
1244
+ channel.notify(t('input-retracted'), { timeoutMs: 2000 });
1245
+ }
1246
+ else {
1247
+ channel.notify(t('input-cannot-retract'), { color: 'warning', timeoutMs: 2500 });
1248
+ }
1249
+ };
1070
1250
  /**
1071
1251
  * Ctrl+Enter: abort the running turn and send the input immediately — the
1072
1252
  * model stops what it is doing and starts on this message right away.
@@ -1231,7 +1411,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1231
1411
  if (!tryRunCommand(value))
1232
1412
  submitText(value);
1233
1413
  };
1234
- /** Expand/collapse the fullscreen editor (shortcut + ⛶ affordance).
1414
+ /** Expand/collapse the fullscreen editor (shortcut + ✎ affordance).
1235
1415
  * Expanding also DROPS any fold block: the fullscreen view shows and
1236
1416
  * edits the full text, and the block's atomic-clamp semantics (caret
1237
1417
  * pushed to its edges, selection clamped to one side) would contradict
@@ -1324,6 +1504,35 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1324
1504
  imageStageChainRef.current = queued.then(() => undefined, () => undefined);
1325
1505
  return queued;
1326
1506
  };
1507
+ /** Success notice for one staged image. An adapted paste says what the user
1508
+ * actually got (stored dimensions, stored format, filled alpha) — naming it
1509
+ * is the difference between a reported adaptation and a silently rewritten
1510
+ * image. */
1511
+ const stagedImageNotice = (token, handle) => {
1512
+ const adjustment = handle.adjustment;
1513
+ if (adjustment === undefined)
1514
+ return t('input-image-pasted', { token });
1515
+ const details = [];
1516
+ if (adjustment.resized) {
1517
+ details.push(t('input-image-detail-resized', {
1518
+ width: adjustment.width,
1519
+ height: adjustment.height,
1520
+ }));
1521
+ }
1522
+ if (adjustment.mediaType !== adjustment.sourceMediaType) {
1523
+ const from = mediaTypeLabel(adjustment.sourceMediaType);
1524
+ const to = mediaTypeLabel(adjustment.mediaType);
1525
+ details.push(adjustment.flattened
1526
+ ? t('input-image-detail-converted-flattened', { from, to })
1527
+ : t('input-image-detail-converted', { from, to }));
1528
+ }
1529
+ // An adjustment always describes a resize or a conversion; anything else
1530
+ // keeps the plain notice rather than an empty parenthetical. ' · ' joins
1531
+ // the clauses in both shipped languages.
1532
+ if (details.length === 0)
1533
+ return t('input-image-pasted', { token });
1534
+ return t('input-image-pasted-adjusted', { token, detail: details.join(' · ') });
1535
+ };
1327
1536
  const discardStagedHandles = (handles) => {
1328
1537
  for (const stageId of new Set(handles.map(handle => handle.stageId))) {
1329
1538
  channel.discardStagedImage(stageId);
@@ -1411,7 +1620,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1411
1620
  setFileSelected(0);
1412
1621
  return;
1413
1622
  }
1414
- // ── 全屏草稿编辑(expandEditor,默认 Ctrl+Shift+E / 输入行 ⛶)─────
1623
+ // ── 全屏草稿编辑(expandEditor,默认 Ctrl+Shift+E / 输入行 ✎)─────
1415
1624
  // 展开态拥有屏幕;Esc 收起(有选区时上面的 selection 分支已先行只清
1416
1625
  // 选区)。滚轮不经此——编辑区的 onWheel 位置路由直接驱动滚动窗口。
1417
1626
  if (key.escape && expandedRef.current) {
@@ -1497,7 +1706,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1497
1706
  // Bind and insert share this synchronous continuation: setInput's
1498
1707
  // sidecar pruning can never observe a bound-but-not-visible token.
1499
1708
  insertClipboardAtCaret(`${token} `);
1500
- channel.notify(t('input-image-pasted', { token }), { timeoutMs: 2500 });
1709
+ channel.notify(stagedImageNotice(token, handle), { timeoutMs: 2500 });
1501
1710
  })
1502
1711
  .catch(() => {
1503
1712
  if (!draftImageLeaseIsCurrent(lease))
@@ -1561,7 +1770,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1561
1770
  const handle = await stageImagePath(content.path, lease);
1562
1771
  const token = bindStagedImage(handle, lease);
1563
1772
  insertClipboardAtCaret(`${token} `);
1564
- channel.notify(t('input-image-pasted', { token }), { timeoutMs: 2500 });
1773
+ channel.notify(stagedImageNotice(token, handle), { timeoutMs: 2500 });
1565
1774
  });
1566
1775
  }
1567
1776
  catch (error) {
@@ -1605,9 +1814,14 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1605
1814
  // All bindings and their visible labels enter together;
1606
1815
  // typing while an earlier file saves cannot prune one.
1607
1816
  insertClipboardAtCaret(`${rendered.join(' ')} `);
1817
+ // A batch cannot itemise every image in one line, but it must
1818
+ // still say that some of them were not stored as pasted.
1819
+ const adapted = staged.filter(handle => handle.adjustment !== undefined).length;
1608
1820
  channel.notify(boundTokens.length === 1
1609
- ? t('input-image-pasted', { token: boundTokens[0] })
1610
- : t('input-images-staged', { count: boundTokens.length }), { timeoutMs: 2500 });
1821
+ ? stagedImageNotice(boundTokens[0], staged[0])
1822
+ : adapted > 0
1823
+ ? t('input-images-staged-adapted', { count: boundTokens.length, adapted })
1824
+ : t('input-images-staged', { count: boundTokens.length }), { timeoutMs: 2500 });
1611
1825
  return true;
1612
1826
  }
1613
1827
  catch (error) {
@@ -1916,6 +2130,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1916
2130
  setSelectedCommand(index => index <= 0 ? suggestions.length - 1 : index - 1);
1917
2131
  return;
1918
2132
  }
2133
+ seedHistory();
1919
2134
  if (history.current.length === 0)
1920
2135
  return;
1921
2136
  if (historyIndex.current < 0) {
@@ -1931,6 +2146,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
1931
2146
  const entry = history.current[historyIndex.current];
1932
2147
  if (entry === undefined)
1933
2148
  return;
2149
+ retractRecalledCopy(entry.text);
1934
2150
  updateFoldBlock(null);
1935
2151
  restoreDraftImages(entry);
1936
2152
  setInput(entry.text);
@@ -2463,6 +2679,11 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
2463
2679
  // the same row, so the wrap budget must shrink by its width or long lines
2464
2680
  // would be clipped at the value box's right edge.
2465
2681
  const vimBadgeCols = vimEnabled ? 7 : 0;
2682
+ // 行首会话入口占列:未传 onOpenSessions 时按钮整体不渲染,预算同步归还
2683
+ // (既有调用方的可用列数逐列不变)。用实测常量而不是 stringWidth:该字符
2684
+ // 是 U+1F3E0+VS15,stringWidth 按码点算 2(含 VS15 仍是 2),终端只给 1,
2685
+ // 见 HOME_BUTTON_COLS 的说明。
2686
+ const homeButtonCols = onOpenSessions === undefined ? 0 : HOME_BUTTON_COLS;
2466
2687
  // 补全卡片边框与输入框 idle 边框同色(plan 模式下整套面板一起变 sage
2467
2688
  // 绿)。`/color` 会话强调色优先于主题 promptBorder(plan 模式仍整体走
2468
2689
  // sage 绿)。`?? ''` 防御最小 mock channel(只声明用到的字段的回归脚
@@ -2470,14 +2691,15 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
2470
2691
  const sessionAccent = sessionColorHex(channel.sessionColor ?? '');
2471
2692
  const promptAccent = channel.mode.plan === true ? 'planMode' : (sessionAccent ?? 'promptBorder');
2472
2693
  // 展开态布局参数:编辑器独占整屏 —— 行号槽(宽度随逻辑行数伸缩)+
2473
- // 圆角边框 2 + 两侧 padding 各 1 占列,❯ 前缀 / vim 徽标 / ⛶ 按钮
2474
- // 全部让位;收起态额外扣掉行尾 ⛶ 按钮的 2 列。
2694
+ // 圆角边框 2 + 两侧 padding 各 1 占列,⌸ 入口 / ❯ 前缀 / vim 徽标 /
2695
+ // ⛶ 按钮全部让位;收起态额外扣掉行首 ⌸ 入口(未渲染时 0 列)与行尾
2696
+ // ⛶ 按钮的 2 列。
2475
2697
  const editorLogicalLines = expanded ? value.split('\n').length : 1;
2476
2698
  const editorNoWidth = Math.max(2, String(editorLogicalLines).length);
2477
2699
  const editorGutterCols = editorNoWidth + 3;
2478
2700
  const inputWidth = expanded
2479
2701
  ? Math.max(1, columns - 4 - editorGutterCols)
2480
- : Math.max(1, columns - 3 - vimBadgeCols - (expandEnabled ? 2 : 0));
2702
+ : Math.max(1, columns - 3 - vimBadgeCols - homeButtonCols - (expandEnabled ? 2 : 0));
2481
2703
  // 展开态无视折叠块:全屏编辑就是为了看全文(foldBlock 状态保留,
2482
2704
  // 收起后折叠显示恢复)。
2483
2705
  const block = expanded ? null : foldBlock;
@@ -2546,22 +2768,50 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
2546
2768
  visibleCount = MAX_VISIBLE_LINES;
2547
2769
  }
2548
2770
  const visibleLines = visualLines.slice(windowStart, windowStart + visibleCount);
2771
+ // Caret-window jump: when the caret leaves MAX_VISIBLE_LINES (typing past
2772
+ // it, arrow-key walks through a long prompt) the whole visible band is
2773
+ // replaced in place — same-height rows, completely different text. The
2774
+ // per-cell diff that repaints that band is exactly the path that goes
2775
+ // haywire when the inline viewport is even one row out of sync with the
2776
+ // terminal's scrollback ("重叠变花": old rows bleeding through the new
2777
+ // ones). Same one-shot reanchor family as the shrink/floaters/editor
2778
+ // patches above — the window jump is a viewport-level discontinuity, not
2779
+ // an ordinary content edit.
2780
+ const prevWindowStartRef = React.useRef(windowStart);
2781
+ React.useLayoutEffect(() => {
2782
+ if (windowStart !== prevWindowStartRef.current) {
2783
+ prevWindowStartRef.current = windowStart;
2784
+ const ink = instances.get(process.stdout) ?? instances.values().next().value;
2785
+ ink?.invalidatePrevFrame();
2786
+ ink?.reanchorViewport();
2787
+ }
2788
+ }, [windowStart]);
2549
2789
  // useInput 的滚轮分支需要这份几何(它在这些派生之前注册)。
2550
2790
  if (expanded) {
2551
2791
  editorViewportRef.current = { maxRows: editorMaxRows, total: visualLines.length };
2552
2792
  }
2553
2793
  // Folded chip content: block stats + first-line preview + hover hint,
2554
2794
  // all pre-truncated to the input width (the row is one line, always).
2795
+ // `·` separators are U+30FB KATAKANA MIDDLE DOT, NOT U+00B7: the latter
2796
+ // is East-Asian-ambiguous (model 1 cell, CJK terminal fonts paint 2) and
2797
+ // this row's stats→preview→hint truncation arithmetic all runs through
2798
+ // stringWidth — a painted-wide separator shifts every segment right by
2799
+ // one column per separator and the row reads as overlapping text.
2800
+ // U+30FB is officially Ambiguous too, but get-east-asian-width hardcodes
2801
+ // it Wide and mainstream terminals (Western included) paint it 2 cells —
2802
+ // the model and the painted width agree in practice. A wcwidth-strict
2803
+ // Western terminal painting it 1 cell would leave a spare column, which
2804
+ // the `- 6` slack below absorbs.
2555
2805
  const foldBadge = `▸ ${stats}`;
2556
2806
  const foldHint = t('input-fold-hover');
2557
- const foldPreviewWidth = inputWidth - stringWidth(foldBadge) - stringWidth(` · ${foldHint}`) - 6;
2807
+ const foldPreviewWidth = inputWidth - stringWidth(foldBadge) - stringWidth(`・${foldHint}`) - 6;
2558
2808
  const foldPreview = foldPreviewWidth >= 8
2559
2809
  ? truncateToWidth(foldText.split('\n')[0] ?? '', foldPreviewWidth)
2560
2810
  : '';
2561
2811
  // Expanded-state fold affordance: a `▾` prefix at the start of the FIRST
2562
2812
  // row (only while the window is at the top and no block exists); its
2563
2813
  // cells fold the whole input into a block again on click.
2564
- const prefixLabel = `▾ ${stats} · `;
2814
+ const prefixLabel = `▾ ${stats}・`;
2565
2815
  const prefixCols = !block && !expanded && big && windowStart === 0 ? stringWidth(prefixLabel) : 0;
2566
2816
  // Offset range [start, end) of every visual row — the SAME break rule as
2567
2817
  // wrapToWidth — so the selection highlight can intersect each row. With a
@@ -2639,7 +2889,7 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
2639
2889
  return (_jsxs(Box, { flexDirection: "row", onClick: (event) => {
2640
2890
  event.stopImmediatePropagation();
2641
2891
  updateFoldBlock(null);
2642
- }, onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: [_jsx(Text, { dimColor: true, children: foldBadge }), foldPreview !== '' && _jsx(Text, { dimColor: true, children: " \u00B7 " }), foldPreview !== '' && _jsx(Text, { wrap: "truncate-end", children: foldPreview }), _jsx(Text, { dimColor: true, children: ` · ${foldHint}` })] }, `fold-${absoluteLine}`));
2892
+ }, onMouseEnter: hoverEnter, onMouseLeave: hoverLeave, children: [_jsx(Text, { dimColor: true, children: foldBadge }), foldPreview !== '' && _jsx(Text, { dimColor: true, children: "\u30FB" }), foldPreview !== '' && _jsx(Text, { wrap: "truncate-end", children: foldPreview }), _jsx(Text, { dimColor: true, children: `・${foldHint}` })] }, `fold-${absoluteLine}`));
2643
2893
  }
2644
2894
  const withPrefix = absoluteLine === 0 && prefixCols > 0;
2645
2895
  const text = withPrefix ? truncateToWidth(line, inputWidth - prefixCols) : line;
@@ -2723,12 +2973,16 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
2723
2973
  // In the expanded editor the declared box starts at its outer edge
2724
2974
  // (padding + gutter included), so those columns ride along and the
2725
2975
  // clamp grows with them.
2726
- column: Math.min(caretVisualCol +
2727
- (expanded
2728
- ? editorGutterCols + 1
2729
- : caretVisualLine === 0 && prefixCols > 0
2730
- ? prefixCols
2731
- : 0), expanded ? editorGutterCols + 1 + inputWidth : inputWidth),
2976
+ //
2977
+ // The declared column is relative to the VALUE BOX, and `caretVisualCol`
2978
+ // is already measured in that box's cell space: the text box IS the text
2979
+ // run, so nothing that renders before it — the session entry, the `❯ `
2980
+ // glyph, the fold prefix — shifts a cell inside it. Adding any of those
2981
+ // back double-counts them and parks the hardware cursor to the RIGHT of
2982
+ // the inverted caret cell (a 2-column miss shows up as a displaced IME
2983
+ // preedit target). Only the expanded editor declares against a wider box
2984
+ // (gutter + padding), so only that branch adds columns.
2985
+ column: Math.min(caretVisualCol + (expanded ? editorGutterCols + 1 : 0), expanded ? editorGutterCols + 1 + inputWidth : inputWidth),
2732
2986
  active: !suspended && !selectionActive,
2733
2987
  });
2734
2988
  /**
@@ -3063,7 +3317,14 @@ export function PromptInput({ channel, suspended = false, helpOpen, onToggleHelp
3063
3317
  : undefined, rows: peekVisualLines.map((row, index) => (_jsx(Text, { wrap: "truncate-end", children: row }, index))), onRowPick: () => {
3064
3318
  updateFoldBlock(null);
3065
3319
  setHovered(false);
3066
- } }) }))] })), lastNotification && (_jsx(Box, { position: "absolute", marginTop: -1, height: 1, width: "100%", paddingLeft: 2, paddingRight: 1, flexDirection: "column", justifyContent: "flex-end", overflow: "hidden", children: _jsx(Box, { justifyContent: "flex-end", children: _jsx(Text, { color: lastNotification.color, dimColor: !lastNotification.color, wrap: "truncate", children: lastNotification.text }) }) })), _jsx(EffortInputBorder, { effort: channel.reasoningEffort, levels: channel.effortLevels, columns: columns, onLight: isLightThemeActive(themeName), idleColor: promptAccent, topRightLabel: topRightLabel, children: _jsxs(Box, { flexDirection: "row", alignItems: "flex-start", width: "100%", children: [_jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), vimEnabled && (_jsxs(Text, { bold: true, color: vimInsert ? 'success' : 'warning', children: [vimInsert ? 'INSERT' : 'NORMAL', " "] })), _jsx(Box, { ref: expanded ? undefined : valueBoxRef, flexGrow: 1, flexShrink: 1, onClick: handleValueClick, onDragStart: handleDragStart, onDragMove: handleDragMove, onDragEnd: handleDragEnd, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) }), expandEnabled && (_jsx(Box, { flexShrink: 0, onClick: (event) => {
3320
+ } }) }))] })), 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) => {
3321
+ event.stopImmediatePropagation();
3322
+ onOpenSessions?.();
3323
+ }, onMouseEnter: () => {
3324
+ setHomeHovered(true);
3325
+ }, onMouseLeave: () => {
3326
+ setHomeHovered(false);
3327
+ }, children: _jsx(Text, { dimColor: !homeHovered, bold: homeHovered, color: homeHovered ? promptAccent : undefined, children: HOME_BUTTON_TEXT }) })), _jsx(EffortChargeGlyph, { effort: channel.reasoningEffort, levels: channel.effortLevels, working: channel.working }), vimEnabled && (_jsxs(Text, { bold: true, color: vimInsert ? 'success' : 'warning', children: [vimInsert ? 'INSERT' : 'NORMAL', " "] })), _jsx(Box, { ref: expanded ? undefined : valueBoxRef, flexGrow: 1, flexShrink: 1, onClick: handleValueClick, onDragStart: handleDragStart, onDragMove: handleDragMove, onDragEnd: handleDragEnd, children: value.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { inverse: true, children: " " }), _jsx(EffortTierBadge, { effort: channel.reasoningEffort, levels: channel.effortLevels, onLight: isLightThemeActive(themeName), columns: columns, leadingColumns: 3 + homeButtonCols })] })) : (_jsx(Box, { flexDirection: "column", children: rendered })) }), expandEnabled && (_jsx(Box, { flexShrink: 0, onClick: (event) => {
3067
3328
  event.stopImmediatePropagation();
3068
3329
  toggleExpand();
3069
3330
  }, onMouseEnter: () => {
@@ -10,16 +10,31 @@ import { type ScrollBoxHandle } from '../ui.js';
10
10
  * - clicking the track scrolls the clicked position to the viewport top
11
11
  * (classic scrollbar semantics — the thumb centers under the click
12
12
  * through the follow-up renders);
13
+ * - dragging the track scrubs the transcript: the drag protocol's
14
+ * per-target localRow maps through the SAME trackScrollTop as a click
15
+ * (absolute mapping — drag to point, no grabbed thumb offset), so a
16
+ * drag is a continuous run of click-to-position jumps. The mapping only
17
+ * reads the render-time maxScroll/trackH and never re-reads scrollTop
18
+ * mid-gesture, so a throttled/clamped scrollTo cannot feed back into the
19
+ * pointer mapping (no jitter). Dragging the thumb therefore lands the
20
+ * pointer's row at the viewport top, exactly like clicking that row;
21
+ * - a landing (drag or the back-to-bottom affordance) breaks and re-pins
22
+ * sticky, which unmounts the chrome that had shrunk the transcript row;
23
+ * the re-pinning Ink pass follows the commit and fires no subscriber
24
+ * notify, so the gutter re-reads the handle once that frame lands (see
25
+ * the subscribe effect). Without it the thumb stays painted from the
26
+ * shrunken viewport and floats a few rows above the bottom until an
27
+ * unrelated render (hover) re-reads the handle;
13
28
  * - the gutter is permanent while scrollable (Qwen's rule: an
14
29
  * auto-hiding gutter that changes content width rewraps everything);
15
30
  * hidden below 60 terminal columns or when the content fits (inline
16
31
  * mode keeps the terminal's native scrollback);
17
32
  * - NoSelect fences the glyphs out of click-drag text selection; the
18
- * wheel over the gutter scrolls the transcript.
19
- *
20
- * Thumb dragging is intentionally NOT implemented (Grok's MVP rule): a
21
- * proportional thumb encodes position, and the tick rail remains the
22
- * semantic navigator. Click-to-position covers the same need.
33
+ * wheel over the gutter scrolls the transcript. Because the track is a
34
+ * drag target, an unmodified left drag scrubs instead of selecting, and
35
+ * multi-clicks on the track no longer feed the select-line chain;
36
+ * Shift/Alt/Ctrl drags never open a drag session and keep the
37
+ * selection path.
23
38
  */
24
39
  export declare function ScrollbarGutter({ handle, terminalWidth, }: {
25
40
  handle: ScrollBoxHandle | null;
@@ -1 +1 @@
1
- {"version":3,"file":"ScrollbarGutter.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollbarGutter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAUpE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,aAAa,GACd,EAAE;IACD,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,KAAK,CAAC,SAAS,CAsIlB"}
1
+ {"version":3,"file":"ScrollbarGutter.d.ts","sourceRoot":"","sources":["../../../src/components/ScrollbarGutter.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AACzB,OAAO,EAAuB,KAAK,eAAe,EAAE,MAAM,UAAU,CAAA;AAqBpE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,eAAe,CAAC,EAC9B,MAAM,EACN,aAAa,GACd,EAAE;IACD,MAAM,EAAE,eAAe,GAAG,IAAI,CAAA;IAC9B,aAAa,EAAE,MAAM,CAAA;CACtB,GAAG,KAAK,CAAC,SAAS,CA6LlB"}