@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
@@ -18,9 +18,9 @@ import { homeDir } from '../utils/paths.js';
18
18
  import { cleanRenderText, cleanScalarText } from '../dsh-adapter/sanitize.js';
19
19
  import { deriveModelGroups, modelPickerLanding, recentCatalogModels, RECENTS_GROUP_PROVIDER, } from '../modelGroups.js';
20
20
  import { readModelRecents, recordModelUse } from '../modelRecents.js';
21
- import { sessionCwdMatches } from '../dsh-adapter/channel.js';
22
21
  import { TuiDialogStore } from '../dsh-adapter/dialogs.js';
23
22
  import { TuiStatusStore } from '../dsh-adapter/status.js';
23
+ import { ActivityStore, useActivity } from '../dsh-adapter/activity-store.js';
24
24
  import { runProviderWizard } from '../dsh-adapter/providerWizard.js';
25
25
  import { ApprovalStore } from '../dsh-adapter/approvals.js';
26
26
  import { AskUserQuestionPanel } from '../components/questions/AskUserQuestionPanel.js';
@@ -52,7 +52,7 @@ import { PluginSceneBoundary } from '../components/PluginSceneBoundary.js';
52
52
  import { PluginStatusViewBoundary } from '../components/PluginStatusViewBoundary.js';
53
53
  import { ImagePreviewOverlay } from '../components/ImagePreviewOverlay.js';
54
54
  import { SkillsPicker, SkillsPickerLoading } from '../components/SkillsPicker.js';
55
- import { SessionBrowser } from './SessionBrowser.js';
55
+ import { SessionSupervisor } from './SessionSupervisor.js';
56
56
  import { SessionTree } from './SessionTree.js';
57
57
  import { Settings } from './Settings.js';
58
58
  import { WorkspacePicker } from '../components/WorkspacePicker.js';
@@ -89,7 +89,8 @@ import instances from '../ink/instances.js';
89
89
  import { useAnimationFrame } from '../ink/hooks/use-animation-frame.js';
90
90
  import { useExternalVersion } from '../hooks/useExternalVersion.js';
91
91
  import { TrajectoryScene } from './TrajectoryScene.js';
92
- import { AgentView } from './AgentView.js';
92
+ import { resumeFailureText } from '../sessions/resumeFailure.js';
93
+ import { markHomeSeen } from '../homePrefs.js';
93
94
  import { extendTrajectory, projectWave } from '../dsh-adapter/trajectory/index.js';
94
95
  import { miniWakeWidth } from '../components/trajectory/MiniWake.js';
95
96
  import { readTrajectorySeen, writeTrajectorySeen } from '../trajectoryPrefs.js';
@@ -199,12 +200,14 @@ let fallbackApprovalStore;
199
200
  */
200
201
  let fallbackDialogStore;
201
202
  let fallbackStatusStore;
203
+ /** Standalone mounts (tests, bare embeds) without the composition root's store. */
204
+ let fallbackActivityStore;
202
205
  /** Identity of one caret-preview dismissal: the token (its title) on the
203
206
  * image, so the same image staged twice is dismissed per token. */
204
207
  function peekKey(image, title) {
205
208
  return `${title ?? ''} ${image.id}`;
206
209
  }
207
- export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, renderScene, }) {
210
+ export function Chat({ channel, questionStore, approvalStore, extensionDialogs, extensionStatus, activityStore, extensionShortcuts, themeHost, onExit, onUpdate, onRestart, fullscreen = false, trajectorySeen: trajectorySeenProp, injectControllerRef, promptControllerRef: promptControllerRefProp, renderScene, openHomeOnBoot, }) {
208
211
  const writeRaw = React.useContext(TerminalWriteContext);
209
212
  // Re-render whenever the channel mutates; rows/status are read fresh below.
210
213
  // DEFAULT lane on purpose (useExternalVersion): the channel version bumps
@@ -237,6 +240,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
237
240
  // Plugin status contributions: text keys join into one line; bounded rich
238
241
  // views keep their own rows immediately above the prompt.
239
242
  const statusContributions = extensionStatus ?? (fallbackStatusStore ??= new TuiStatusStore());
243
+ // The working line: the working-activity plugin's published value for THIS
244
+ // session, read from its session projection. No projection value (plugin
245
+ // absent, or nothing published yet) simply means the classic spinner below.
246
+ const activityValues = activityStore ?? (fallbackActivityStore ??= new ActivityStore());
247
+ const workingActivity = useActivity(activityValues, channel.sessionId);
240
248
  const subscribeStatus = React.useCallback((listener) => statusContributions.subscribe(listener), [statusContributions]);
241
249
  const statusEntries = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getSnapshot());
242
250
  const statusViews = React.useSyncExternalStore(subscribeStatus, () => statusContributions.getViewSnapshot());
@@ -358,18 +366,33 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
358
366
  };
359
367
  /** `/skills` 技能目录(issue #204):null = 注册表快照在途。 */
360
368
  const [skillsList, setSkillsList] = React.useState(null);
361
- /** `/resume` opens the session browser, a screen rather than a panel. It
362
- * owns its own selection, filters and keyboard — Chat only opens it. */
363
- const [browserOpen, setBrowserOpen] = React.useState(false);
369
+ /**
370
+ * The session supervisor — the ONE screen behind `/resume`, `/agentview`,
371
+ * `/home`, `/bg` and the composer's 🏠 button.
372
+ *
373
+ * Those were three screens over one domain (a workspace rail here, a
374
+ * search surface there, a live-status overview somewhere else), which is why
375
+ * each new session feature needed patching into all three and why the
376
+ * three disagreed about what switching a session even does. There is now a
377
+ * single surface and a single runtime behind every entry point: this
378
+ * terminal hosts several sessions, leaving one parks it rather than ending
379
+ * it, and a session another terminal holds is visible but not enterable.
380
+ *
381
+ * Seeded from the host's one-shot landing decision (`openHomeOnBoot`): the
382
+ * first ordinary launch of an installation lands here instead of on a blank
383
+ * conversation, because that is the launch where "which project am I working
384
+ * on" has not been answered yet. Every later launch starts on the chat
385
+ * screen, and the screen stays reachable.
386
+ */
387
+ const [supervisorOpen, setSupervisorOpen] = React.useState(openHomeOnBoot === true);
364
388
  /** `/tree` opens the session family tree (pi's Session Tree): every rewind
365
389
  * fork stitched back onto the message it diverged from, hover previews,
366
- * and per-node rewind/fork/adopt actions. Like the browser, a screen. */
390
+ * and per-node rewind/fork/adopt actions. Like the supervisor, a screen. */
367
391
  const [treeOpen, setTreeOpen] = React.useState(false);
368
- /** `/agentview` and `/bg` open the agent view — a screen like the browser:
369
- * it owns selection, the dispatch input and every key while up. */
370
- const [agentViewOpen, setAgentViewOpen] = React.useState(false);
371
- /** The session backgrounded when the view opened via ←/`/bg` (the "Esc
372
- * returns to that conversation" return target), cleared on close. */
392
+ /**
393
+ * The session backgrounded when the screen opened via ←/`/bg` (the "Esc
394
+ * returns to that conversation" return target), cleared on close.
395
+ */
373
396
  const [agentViewReturnId, setAgentViewReturnId] = React.useState(undefined);
374
397
  /** Live agent-view rows: the prompt footer's "← N agents" hint reads the
375
398
  * needs-input count from here (cached snapshot in the channel). The
@@ -378,16 +401,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
378
401
  const EMPTY_AGENT_VIEW_ROWS = [];
379
402
  const agentViewRows = React.useSyncExternalStore(listener => channel.subscribeAgentView?.(listener) ?? (() => { }), () => channel.agentViewRows?.() ?? EMPTY_AGENT_VIEW_ROWS);
380
403
  const backgroundAgentsNeedingInput = agentViewRows.filter(row => row.status === 'needs-input' && !row.current).length;
381
- /** Background the attached session and open the agent view
404
+ /** Background the attached session and open the supervisor
382
405
  * (`/bg`, `/background`, and ← on an empty prompt all land here). The
383
- * backgrounded session becomes the view's return target (final Esc
406
+ * backgrounded session becomes the screen's return target (final Esc
384
407
  * attaches back to it). */
385
408
  const backgroundToAgentView = React.useCallback(() => {
386
409
  void channel.backgroundCurrent().then((result) => {
387
410
  if (result.ok) {
388
411
  setAgentViewReturnId(result.backgroundedSessionId);
389
412
  agentViewOpenSessionRef.current = channel.agentId;
390
- setAgentViewOpen(true);
413
+ setSupervisorOpen(true);
391
414
  }
392
415
  });
393
416
  }, [channel]);
@@ -405,6 +428,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
405
428
  const [themeName, setTheme] = useTheme();
406
429
  const { rows: terminalRows } = useTerminalSize();
407
430
  const [showAllMessages, setShowAllMessages] = React.useState(false);
431
+ /** Scope the fold to this question: an aborted ask can promote its queued
432
+ * successor without ever publishing an idle (null) snapshot. */
433
+ const [minimizedQuestionKey, setMinimizedQuestionKey] = React.useState(null);
434
+ const questionMinimized = questionSnapshot !== null && minimizedQuestionKey === questionSnapshot.key;
408
435
  /** Fold state for the GoalTodoPanel todo section (ctrl/cmd+q or click). */
409
436
  const [todoCollapsed, setTodoCollapsed] = React.useState(false);
410
437
  const [thinkingVisible, setThinkingVisible] = React.useState(true);
@@ -615,6 +642,34 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
615
642
  });
616
643
  setSceneOpen(true);
617
644
  }, []);
645
+ /**
646
+ * Leave the session supervisor for the conversation.
647
+ *
648
+ * The one-shot landing preference is written here rather than at boot: a
649
+ * process that dies before the user ever sees the screen (a config error, a
650
+ * crash during the first render) must not burn the installation's only
651
+ * first-launch landing. Writing on the way OUT means "the user has seen it".
652
+ *
653
+ * Leaving also honours `/bg`'s return target. `/background` moved the
654
+ * session the user was in to the background and opened this screen; a plain
655
+ * Esc out of it re-attaches to that session instead of silently leaving them
656
+ * on the fresh one, which is what "go back to what I was doing" means. Any
657
+ * explicit mount inside the screen clears the target first, so this can
658
+ * never undo a choice the user just made.
659
+ */
660
+ const closeHome = React.useCallback(() => {
661
+ suppressLogoIntroRef.current = true;
662
+ markHomeSeen();
663
+ const returnTo = agentViewReturnId;
664
+ setAgentViewReturnId(undefined);
665
+ setSupervisorOpen(false);
666
+ if (returnTo !== undefined && returnTo !== channel.agentId) {
667
+ void channel.resumeTo(returnTo).then((result) => {
668
+ if (result.ok)
669
+ repaintTranscript();
670
+ }).catch(() => undefined);
671
+ }
672
+ }, [agentViewReturnId, channel, repaintTranscript]);
618
673
  /** The startup summary gives way to transcript rows after the first local command or message. */
619
674
  const loadedContextVisible = channel.rows.length === 0 && channel.loadedContext !== undefined;
620
675
  /** Startup context panel: collapsed by default, toggled with Ctrl+P. */
@@ -851,12 +906,121 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
851
906
  const exitTimerRef = React.useRef(null);
852
907
  // Live view into the prompt's text for the Ctrl+C rule (clears text when
853
908
  // non-empty; the double-press exit only arms on an empty input).
854
- const promptControllerRef = React.useRef(null);
909
+ const ownPromptControllerRef = React.useRef(null);
910
+ const promptControllerRef = promptControllerRefProp ?? ownPromptControllerRef;
911
+ /**
912
+ * Owner of the unsent draft. Every screen this component renders INSTEAD of
913
+ * the conversation (the session screen, the tree, settings, the jobs and
914
+ * subagent panels, the trajectory scene) unmounts the composer, and the
915
+ * composer keeps its text in local state — so without this the half-written
916
+ * prompt died on the way in. The slot lives here, outlives that unmount, and
917
+ * is dropped the moment the attached session changes so no draft can follow
918
+ * the user into a different conversation.
919
+ */
920
+ const promptDraftRef = React.useRef({ current: null });
921
+ /**
922
+ * Latest channel for the unmount release below: that effect must not re-run
923
+ * on a channel identity change, yet its cleanup must release against the
924
+ * channel of the last render.
925
+ */
926
+ const channelRef = React.useRef(channel);
927
+ channelRef.current = channel;
928
+ /**
929
+ * Release the staged images a WAITING snapshot alone owns.
930
+ *
931
+ * While a draft waits in the slot for the composer to remount, the snapshot
932
+ * is the only owner of the capabilities behind its `[Image #N]` tokens. If
933
+ * Chat itself goes away first (leaving an early-return screen by exiting the
934
+ * TUI), nothing would ever restore or discard them — the session's
935
+ * 128-entry FIFO would evict live entries instead. The `hasStagedImage`
936
+ * guard keeps a capability the channel already recycled a no-op; both calls
937
+ * are idempotent.
938
+ *
939
+ * Scope, deliberately narrow (review round 7): a capability a QUEUED message
940
+ * still references (`channel.pending`) is never revoked here. The real
941
+ * double-hold path is paste an image → queue the draft with Tab while the
942
+ * model works → recall that line from input history with ↑ (same stageId
943
+ * rebound to the draft) → park the composer. Delivery resolves its refs from
944
+ * the enqueue-time capture, so a late revoke would only bite a host that
945
+ * re-resolves them afterwards — this keeps the rule identical to
946
+ * `stageIdIsRetained` instead of relying on that.
947
+ *
948
+ * A composer that is still MOUNTED when Chat unmounts is NOT covered: React
949
+ * runs this parent cleanup BEFORE the child's, so the child then writes its
950
+ * draft into the now-dead ref and those ids ride the channel's lifetime out
951
+ * (the #942 review's remaining P2). Neither unmount path loses anything
952
+ * user-visible — the channel dies with them.
953
+ */
954
+ React.useEffect(() => {
955
+ return () => {
956
+ const snapshot = promptDraftRef.current.current;
957
+ promptDraftRef.current.current = null;
958
+ if (snapshot === null)
959
+ return;
960
+ const queued = new Set();
961
+ for (const item of channelRef.current.pending) {
962
+ // `?? []`: a foreign/embedded host may hand us a pending entry without
963
+ // images, and a throw inside an unmount cleanup escapes into the exit
964
+ // path — every other reader of this field guards it the same way.
965
+ for (const image of item.images ?? [])
966
+ queued.add(image.stageId);
967
+ }
968
+ for (const [, stageId] of snapshot.images) {
969
+ if (queued.has(stageId))
970
+ continue;
971
+ if (channelRef.current.hasStagedImage?.(stageId) === true) {
972
+ channelRef.current.discardStagedImage(stageId);
973
+ }
974
+ }
975
+ };
976
+ }, []);
977
+ const draftSessionId = channel.agentId;
978
+ /** Session the effect below last reconciled against; a change is a switch. */
979
+ const draftSessionRef = React.useRef(draftSessionId);
980
+ /**
981
+ * The session a fill Chat itself requested belongs to, if one is in flight.
982
+ *
983
+ * A rewind's restored message arrives in the same commit that replaces the
984
+ * session, and it belongs to the NEW binding — the user picked it. The
985
+ * composer cannot tell, so Chat says so here, at the two call sites that ask
986
+ * for a fill.
987
+ *
988
+ * Keyed by the session id rather than a bare flag, so it can only ever excuse
989
+ * the switch it was written for. Do NOT clear it when the composer consumes
990
+ * the fill: a child's layout effects run before the parent's, so the fill is
991
+ * consumed in the very commit this effect judges, and clearing it there would
992
+ * wipe the message the user just got back.
993
+ */
994
+ const pendingFillRef = React.useRef(null);
995
+ /**
996
+ * Drop the composer's text when the session underneath it is replaced.
997
+ *
998
+ * A LAYOUT effect, not a passive one: the clear has to land in the commit
999
+ * that swaps the session. A passive effect is flushed later, and anything
1000
+ * typed in between (the tree's hand-off, a fast user) would be wiped with the
1001
+ * old conversation's text. Which DRAFT the slot keeps is a separate question,
1002
+ * answered by the snapshot's owner fields.
1003
+ */
1004
+ React.useLayoutEffect(() => {
1005
+ if (draftSessionRef.current === draftSessionId)
1006
+ return;
1007
+ draftSessionRef.current = draftSessionId;
1008
+ // A stored draft can only belong to the conversation being replaced: the
1009
+ // composer is the one that writes it, and it writes it on the way out.
1010
+ promptDraftRef.current.current = null;
1011
+ if (pendingFillRef.current === draftSessionId) {
1012
+ pendingFillRef.current = null;
1013
+ return;
1014
+ }
1015
+ promptControllerRef.current?.clear();
1016
+ }, [draftSessionId]);
855
1017
  const previewGallery = activePreview === null ? [] : activePreview.peek
856
1018
  ? promptControllerRef.current?.previewImages?.() ?? [activePreview]
857
1019
  : overlay.kind === 'image-preview' ? overlay.gallery ?? [activePreview] : [];
1020
+ // Peek entries are rebuilt from the prompt every render: match by
1021
+ // attachment id + token title, not facade identity.
858
1022
  const previewIndex = activePreview?.peek
859
- ? previewGallery.findIndex(entry => entry.image === activePreview.image && entry.title === activePreview.title)
1023
+ ? previewGallery.findIndex(entry => entry.image.id === activePreview.image.id && entry.title === activePreview.title)
860
1024
  : overlay.kind === 'image-preview' ? overlay.index ?? 0 : -1;
861
1025
  const stepPreview = (delta) => {
862
1026
  if (!activePreview?.peek) {
@@ -943,7 +1107,11 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
943
1107
  // fullscreen (<AlternateScreen> supplies mouse tracking); a no-op
944
1108
  // subscription in inline mode, where selection belongs to the terminal.
945
1109
  // The copy clears the highlight and posts a transient notification.
946
- useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }));
1110
+ useCopyOnSelect(text => channel.notify(t('copied-chars', { n: text.length }), { timeoutMs: 1500 }),
1111
+ // Stale-selection refusal: the highlighted rows were replaced in place
1112
+ // (streaming overwrite), so nothing was copied — say why instead of
1113
+ // letting the highlight vanish silently.
1114
+ () => channel.notify(t('copy-refused-stale'), { timeoutMs: 2500 }));
947
1115
  const { clearSelection: clearMouseSelection, hasSelection: hasMouseSelection } = useSelection();
948
1116
  React.useEffect(() => {
949
1117
  if (!channel.working || !terminalFocused)
@@ -1611,27 +1779,31 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
1611
1779
  }
1612
1780
  return true;
1613
1781
  }
1614
- case 'resume': {
1615
- setHelpOpen(false);
1616
- // The browser opens immediately and loads its own list. Waiting for
1617
- // the listing here would make `/resume` feel slower the more history
1618
- // a project has, which is exactly backwards.
1619
- setBrowserOpen(true);
1620
- return true;
1621
- }
1782
+ case 'resume':
1783
+ /**
1784
+ * `/resume`, `/home` and `/agentview` are one screen.
1785
+ *
1786
+ * They were three implementations of one domain and drifted apart: the
1787
+ * same session could be listed by all three, each with its own selection
1788
+ * model and its own idea of what opening one does. Keeping the three
1789
+ * commands is about muscle memory, not about three surfaces — every one
1790
+ * of them lands here, on the same runtime.
1791
+ */
1792
+ case 'home':
1622
1793
  case 'agentview': {
1623
- // The agent view shows one screen for every session. It opens
1624
- // immediately; the view reads its own rows (live + persisted).
1625
1794
  setHelpOpen(false);
1795
+ // The screen opens immediately and loads its own list. Waiting for the
1796
+ // listing here would make it feel slower the more history a project
1797
+ // has, which is exactly backwards.
1626
1798
  agentViewOpenSessionRef.current = channel.agentId;
1627
- setAgentViewOpen(true);
1799
+ setSupervisorOpen(true);
1628
1800
  return true;
1629
1801
  }
1630
1802
  case 'bg':
1631
1803
  case 'background': {
1632
1804
  // `/background`: the attached session moves to the background
1633
1805
  // (it keeps running in this process), the terminal lands on a fresh
1634
- // session, and the agent view opens on top.
1806
+ // session, and the supervisor opens on top.
1635
1807
  setHelpOpen(false);
1636
1808
  backgroundToAgentView();
1637
1809
  return true;
@@ -2256,6 +2428,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2256
2428
  const performRewind = async (row, mode = null) => {
2257
2429
  const text = await channel.rewindTo(row, mode);
2258
2430
  if (text !== null) {
2431
+ // The restored message belongs to the binding `rewindTo` just created,
2432
+ // not to the one it replaced: it is the user's choice, and the switch
2433
+ // effect must not treat it as the old conversation's leftovers.
2434
+ pendingFillRef.current = String(channel.agentId);
2259
2435
  // Put the restored message back in the prompt for re-editing.
2260
2436
  setHistoryFill(text);
2261
2437
  channel.notify(t('rewind-done'));
@@ -2460,18 +2636,15 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2460
2636
  || overlay.kind === 'tips'
2461
2637
  || (recap !== null && (!recap.auto || recap.expanded)))
2462
2638
  return;
2463
- // Same for the session browser: it renders instead of the conversation,
2464
- // so every key belongs to it — including the plain letters that drive its
2465
- // search box, which Chat would otherwise route into the prompt.
2466
- if (browserOpen)
2467
- return;
2468
- // Same for the session tree: plain letters drive its search, clicks and
2469
- // Enter drive its action menu.
2639
+ // The session tree owns the whole terminal while it is up: plain letters
2640
+ // drive its search, clicks and Enter drive its action menu.
2470
2641
  if (treeOpen)
2471
2642
  return;
2472
- // The agent view is another whole-screen surface:
2473
- // its dispatch input owns every printable key.
2474
- if (agentViewOpen)
2643
+ // The session supervisor owns the whole terminal while it is up: its rail
2644
+ // and session list bind ↑/↓/Enter/Tab/Esc, its filter box takes the plain
2645
+ // letters that would otherwise reach the prompt, and the directory picker
2646
+ // and menus it opens are its own modal layers.
2647
+ if (supervisorOpen)
2475
2648
  return;
2476
2649
  // Same for the settings screen: plain letters (s save / d discard) and
2477
2650
  // the field draft editor belong to it alone.
@@ -2575,8 +2748,17 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
2575
2748
  // keyboard while one is pending (the panel's own useInput handles
2576
2749
  // ↑/↓/Space/Tab/Enter/Esc; the prompt input is suspended, so nothing
2577
2750
  // else should see these keys).
2578
- if (questionSnapshot !== null || approvalSnapshot !== null || dialogSnapshot !== null)
2751
+ if (approvalSnapshot !== null || dialogSnapshot !== null)
2752
+ return;
2753
+ if (questionSnapshot !== null) {
2754
+ // Only transcript navigation belongs here. The mounted questionnaire
2755
+ // owns fold/expand keys, including when it interrupts another screen.
2756
+ if (questionMinimized && !isSticky && (isPlainReturnInput(input, key) || key.end)) {
2757
+ handle?.scrollToBottom();
2758
+ event.stopImmediatePropagation();
2759
+ }
2579
2760
  return;
2761
+ }
2580
2762
  const returnCandidate = isPlainReturnInput(input, key);
2581
2763
  const returnNow = Date.now();
2582
2764
  const plainReturn = returnCandidate && returnNow - lastModalEnterAtRef.current >= 80;
@@ -3365,9 +3547,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3365
3547
  const approvalPanelNode = approvalSnapshot !== null ? (_jsx(ApprovalPanel, { approval: approvalSnapshot, background: approvalSnapshot.agentId !== channel.agentId, onDecide: outcome => approvals.decide(outcome) }, approvalSnapshot.key)) : null;
3366
3548
  const questionPanelNode = questionSnapshot !== null ? (_jsx(AskUserQuestionPanel, { question: questionSnapshot.question, position: questionSnapshot.position, total: questionSnapshot.total, answered: questionSnapshot.answered, initialDraft: questionSnapshot.draft, onAnswer: selection => questionStore.answerCurrent(selection), onCancel: () => questionStore.cancelCurrent(), onBack: questionSnapshot.canGoBack
3367
3549
  ? draft => questionStore.backCurrent(draft)
3368
- : undefined }, questionSnapshot.key)) : null;
3550
+ : undefined, collapsed: questionMinimized, onExpand: () => setMinimizedQuestionKey(null), onToggleFold: () => setMinimizedQuestionKey(previous => previous === questionSnapshot.key ? null : questionSnapshot.key), fullscreen: fullscreen }, questionSnapshot.key)) : null;
3369
3551
  const interruptPanel = approvalPanelNode ?? questionPanelNode;
3370
- const screenOpen = channel.pluginScene !== undefined || browserOpen || settingsOpen
3552
+ const screenOpen = channel.pluginScene !== undefined || supervisorOpen || settingsOpen
3371
3553
  || subagentDetailId !== null || subagentDashboardOpen || sceneOpen;
3372
3554
  if (interruptPanel !== null && screenOpen) {
3373
3555
  const node = (_jsx(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: interruptPanel }));
@@ -3394,37 +3576,62 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3394
3576
  }, children: renderScene ? renderScene(pluginScene.id, channel) : _jsxs(Text, { children: ["Scene unavailable: ", pluginScene.id] }) }));
3395
3577
  return fullscreen ? node : _jsx(AlternateScreen, { children: node });
3396
3578
  }
3397
- // The agent view is a screen like the browser: it REPLACES the
3398
- // conversation. Every session keeps running behind it — including the
3399
- // attached one mid-turn.
3400
- if (agentViewOpen) {
3401
- const view = (_jsx(AgentView, { channel: channel, home: homeDir(), approval: approvalSnapshot, onApprove: outcome => approvals.decide(outcome), returnSessionId: agentViewReturnId, onClose: () => {
3402
- // The transcript tree remounts on close: never replay the whale
3403
- // intro there, and when the session changed INSIDE the view
3404
- // (attach / backgrounded dispatch), repaint the fresh transcript
3405
- // from the top like `/new` does.
3579
+ /**
3580
+ * The session supervisor: a screen in the same sense as the tree — an early
3581
+ * return after every hook above has run, so there is no transcript
3582
+ * underneath to repaint or bled through.
3583
+ *
3584
+ * It sits ABOVE the session tree because it is the surface a launch can
3585
+ * start on (`openHomeOnBoot`): a first launch has no conversation to come
3586
+ * back to, and every action it offers either mounts a session (which closes
3587
+ * it) or starts a new one.
3588
+ *
3589
+ * Behind it, every session this terminal hosts keeps running — that is the
3590
+ * runtime the screen describes, not an implementation detail of it. A turn
3591
+ * that was in flight when the user opened this screen is still in flight
3592
+ * while they read the list, which is why closing the screen only repaints
3593
+ * the transcript when the attached session actually changed.
3594
+ */
3595
+ if (supervisorOpen) {
3596
+ /**
3597
+ * Live state per session, from the channel's own agent-view projection.
3598
+ * Reading the projection rather than a parallel source is what keeps this
3599
+ * screen and the attention hints in the composer footer from disagreeing
3600
+ * about which session is waiting for input.
3601
+ */
3602
+ const agentRowOf = (sessionId) => agentViewRows.find(row => row.id === sessionId);
3603
+ const supervisorNode = (_jsx(SessionSupervisor, { channel: channel, home: homeDir(), onClose: closeHome, approval: approvalSnapshot, onApprove: outcome => approvals.decide(outcome), onOpenSession: async (sessionId) => {
3604
+ const result = await channel.resumeTo(sessionId);
3605
+ if (!result.ok) {
3606
+ const text = resumeFailureText(result);
3607
+ if (text !== undefined)
3608
+ channel.notify(text, { color: 'error', timeoutMs: 8000 });
3609
+ return false;
3610
+ }
3611
+ channel.notify(t('resume-resumed'));
3406
3612
  suppressLogoIntroRef.current = true;
3407
- const switched = agentViewOpenSessionRef.current !== undefined &&
3408
- agentViewOpenSessionRef.current !== channel.agentId;
3409
- if (switched)
3410
- repaintTranscript();
3411
3613
  setAgentViewReturnId(undefined);
3412
- setAgentViewOpen(false);
3413
- } }));
3414
- return fullscreen ? view : _jsx(AlternateScreen, { children: view });
3415
- }
3416
- // The browser is a screen, not an overlay: it REPLACES the conversation
3417
- // rather than floating above it. Rendering it as an early return (after
3418
- // every hook above has run) is what makes that literal — there is no
3419
- // transcript underneath to be repainted, scrolled, or bled through.
3420
- if (browserOpen) {
3421
- const browser = (_jsx(SessionBrowser, { channel: channel, home: homeDir(), sameProject: sessionCwdMatches, onClose: () => {
3422
- suppressLogoIntroRef.current = true;
3423
- setBrowserOpen(false);
3614
+ setSupervisorOpen(false);
3615
+ repaintTranscript();
3616
+ return true;
3617
+ }, onNewSession: async (target) => {
3618
+ const ok = await channel.switchWorkspace(target);
3619
+ if (ok) {
3620
+ suppressLogoIntroRef.current = true;
3621
+ setAgentViewReturnId(undefined);
3622
+ setSupervisorOpen(false);
3623
+ repaintTranscript();
3624
+ }
3625
+ return ok;
3626
+ }, onStopSession: async (sessionId) => channel.stopBackgroundAgent?.(sessionId) ?? false, liveStateOf: (sessionId) => {
3627
+ const row = agentRowOf(sessionId);
3628
+ return row === undefined
3629
+ ? undefined
3630
+ : { status: row.status, live: row.live, current: row.current, summary: row.summary };
3424
3631
  } }));
3425
3632
  // Inline hosts enter the alternate screen for the duration; full-screen
3426
3633
  // hosts are already in it and must not nest a second one.
3427
- return fullscreen ? browser : _jsx(AlternateScreen, { children: browser });
3634
+ return fullscreen ? supervisorNode : _jsx(AlternateScreen, { children: supervisorNode });
3428
3635
  }
3429
3636
  // The session tree follows the browser's rule exactly: it REPLACES the
3430
3637
  // conversation (an early return after every hook above has run), so there
@@ -3432,6 +3639,10 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3432
3639
  // turn's prompt returns through the same fill path a rewind picker uses.
3433
3640
  if (treeOpen) {
3434
3641
  const tree = (_jsx(SessionTree, { channel: channel, currentSessionId: channel.agentId, onClose: () => setTreeOpen(false), onRestoreText: (text) => {
3642
+ // The tree rewound to a node and is handing that turn's prompt back,
3643
+ // exactly like the picker does. It belongs to the binding the tree
3644
+ // action just created.
3645
+ pendingFillRef.current = String(channel.agentId);
3435
3646
  setHistoryFill(text);
3436
3647
  } }));
3437
3648
  return fullscreen ? tree : _jsx(AlternateScreen, { children: tree });
@@ -3568,7 +3779,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3568
3779
  // sessions keep the full intro; restored ones settle instantly.
3569
3780
  // A remount after a whole screen closed also settles instantly
3570
3781
  // (see suppressLogoIntroRef).
3571
- skipIntro: suppressLogoIntroRef.current || channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
3782
+ skipIntro: suppressLogoIntroRef.current || channel.rows.length > 30 }, logoNonce), loadedContextVisible && (_jsx(LoadedContextPanel, { context: channel.loadedContext, open: loadedContextOpen, onToggle: toggleLoadedContext })), _jsx(MessageList, { rows: channel.rows, failureHintRowId: failureHintRowId, failureHint: t('traj-hint-failure', { key: `${modLabel}t` }), expanded: expanded, expandedRows: expandedRows, selectedId: selectionActive ? selectedId : null, onToggleRow: toggleRowExpanded, streamViewToggledRows: streamViewToggledRows, onToggleStreamView: toggleStreamView, model: channel.model, diffLayout: channel.diffLayout, thinkingFold: channel.thinkingFold, toolBackground: channel.toolBackground, foldTerminalCommand: channel.foldTerminalCommand, smoothStreaming: channel.smoothStreaming, activityFrames: channel.activityFrames, showAll: showAllMessages, thinkingVisible: thinkingVisible, historyPaintEnabled: !fullscreen, onToggleAll: () => { setShowAllMessages(previous => !previous); }, onLoadOlder: () => channel.loadOlder(), registerRowRef: registerRowRef, scrollHandle: handle, forceMountRowId: forceMountRowId, newSinceRowId: isSticky ? null : lastSeenRowIdRef.current, onUnseenCount: setUnseenCount, onTimeline: setTimeline, onOpenSubagent: setSubagentDetailId, onOpenJobs: openJobsPanel, onOpenFile: openFileActions, sessionCwd: channel.cwd, onPreviewImage: openImagePreview, suppressImageGraphics: activePreview !== null })] }), (() => {
3572
3783
  // Gutter mode (settings `dsh-tui.scrollGutter`): the timeline
3573
3784
  // rail (default), the proportional scrollbar, or nothing. The
3574
3785
  // slot keeps its 2 columns in both rendered modes (Qwen's
@@ -3584,9 +3795,9 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3584
3795
  })(), !promptEditorOpen && imagePreviewNode] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, children: [showPill && (_jsx(NewMessagesPill, { count: unseenCount, onClick: () => handle?.scrollToBottom() })), channel.working &&
3585
3796
  (channel.activityEnabled &&
3586
3797
  !channel.minimal &&
3587
- channel.workingActivity !== undefined &&
3588
- channel.workingActivity.line !== '' &&
3589
- channel.workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: channel.workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
3798
+ workingActivity !== undefined &&
3799
+ workingActivity.line !== '' &&
3800
+ workingActivity.phase !== 'idle' ? (_jsx(Box, { marginTop: 1, children: _jsx(ActivityLine, { activity: workingActivity, activityFrames: channel.activityFrames, warnPct: activityWarnPct, warnDanger: activityWarnPct !== undefined && activityWarnPct >= 95,
3590
3801
  // Upload = real tokens of the last request; download =
3591
3802
  // the animated chars/4 estimate, matching the classic
3592
3803
  // spinner's counter (the suffix used raw chars before,
@@ -3617,7 +3828,16 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3617
3828
  void setClipboard(btw.answer ?? '').then(raw => { if (raw)
3618
3829
  writeRaw?.(raw); });
3619
3830
  channel.notify(t('copied-chars', { n: (btw.answer ?? '').length }), { timeoutMs: 1500 });
3620
- } }) })) : questionPanelNode !== null ? (questionPanelNode) : null, _jsx(PromptInput, { channel: channel, suspended: promptReplacementOpen, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => { setHistoryFill(null); }, onRewindRequest: openRewind, onBackgroundRequest: backgroundToAgentView, backgroundAgentsNeedingInput:
3831
+ } }) })) : questionPanelNode !== null ? (questionPanelNode) : null, _jsx(PromptInput, { channel: channel, suspended: promptReplacementOpen, draftCache: promptDraftRef.current, helpOpen: helpOpen, onToggleHelp: () => { setHelpOpen(previous => !previous); }, onRunCommand: runCommand, selectionActive: promptSelectionActive, fillText: historyFill, onFillConsumed: () => setHistoryFill(null), onRewindRequest: openRewind, onBackgroundRequest: backgroundToAgentView,
3832
+ // The 🏠 at the head of the input row opens the same session screen
3833
+ // `/resume` and `/agentview` open — one surface, three doors. It is
3834
+ // gated on this prop rather than a setting, so hosts that mount the
3835
+ // prompt without a session screen (and the layout regressions that
3836
+ // pin the row's column budget) keep the row they had.
3837
+ onOpenSessions: () => {
3838
+ agentViewOpenSessionRef.current = channel.agentId;
3839
+ setSupervisorOpen(true);
3840
+ }, backgroundAgentsNeedingInput:
3621
3841
  // Only the real channel supplies the seam; pre-agent-view test
3622
3842
  // stubs must not grow the footer row (layout-dependent
3623
3843
  // regressions pin the visible row count). The footer only
@@ -3628,7 +3848,7 @@ export function Chat({ channel, questionStore, approvalStore, extensionDialogs,
3628
3848
  // clock and shifting every row-count layout invariant.
3629
3849
  channel.agentViewRows !== undefined && backgroundAgentsNeedingInput > 0
3630
3850
  ? backgroundAgentsNeedingInput
3631
- : undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
3851
+ : undefined, controllerRef: promptControllerRef, onCaretImage: handleCaretImage, caretPreviewOpen: peekPreview !== null, onDismissCaretPreview: dismissPeek }, "prompt-input"), _jsx(StatusLine, { channel: channel, activity: workingActivity, selectionActive: selectionActive, helpOpen: helpOpen, wake: wakeBand === undefined
3632
3852
  ? undefined
3633
3853
  : {
3634
3854
  band: wakeBand,