@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
@@ -1,911 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import React from 'react';
3
- import { Box, Text, useInput, useTerminalSize } from '../ui.js';
4
- import { Divider } from '../components/design-system/Divider.js';
5
- import { HintLine } from '../components/design-system/HintLine.js';
6
- import { SearchBox } from '../components/SearchBox.js';
7
- import { PageInsetContext } from '../components/PageMargin.js';
8
- import { SessionListRow } from '../components/sessions/SessionListRow.js';
9
- import { SessionPreview } from '../components/sessions/SessionPreview.js';
10
- import { WorkspaceListRow } from '../components/sessions/WorkspaceListRow.js';
11
- import { useTerminalFocus } from '../ink/hooks/use-terminal-focus.js';
12
- import { isMod, isPlainReturn, modLabel } from '../utils/modifiers.js';
13
- import { formatProject, projectName, spreadRow, tailWidth, truncateWidth } from '../sessions/format.js';
14
- import { stringWidth } from '../ink/stringWidth.js';
15
- import { TICK, MULTIPLICATION_X } from '../terminal-utils/figures.js';
16
- import { anchorTop, buildView, buildWorkspaceGroups, DEFAULT_FILTERS, moveSelection, normalizeWorkspaceCwd, seekSelectable, sessionAt, windowEnd, } from '../sessions/view.js';
17
- import { t } from '../i18n.js';
18
- import { readSessionPins, setSessionPinned } from '../sessionPins.js';
19
- /**
20
- * Rows the layout cannot do without: the header, the four-row scope/search
21
- * card, the notice slot, and the hints. Everything else yields before these.
22
- */
23
- const REGULAR_MANDATORY_LINES = 7;
24
- const COMPACT_MANDATORY_LINES = 5;
25
- /**
26
- * The one remaining horizontal rule, above the hints.
27
- *
28
- * The search card's own border separates it from the header and the list, so
29
- * the two rules that used to flank it are gone; what remains lifts the hints
30
- * off the content — decoration, and the first thing a short terminal drops.
31
- */
32
- const RULE_PRIORITY = [2];
33
- /** Wide screens keep the working-directory rail visible beside sessions. */
34
- const WORKSPACE_RAIL_MIN_COLUMNS = 120;
35
- /** Content width below which preview replaces the session list. */
36
- const SPLIT_MIN_COLUMNS = 100;
37
- const WORKSPACE_ROW_HEIGHT = 2;
38
- /**
39
- * The right-click session menu, top to bottom. Index doubles as the
40
- * keyboard cursor: ↑/↓ move it, Enter activates the highlighted action.
41
- */
42
- const MENU_ACTIONS = ['open', 'pin', 'rename', 'delete'];
43
- /** Popup size, border included; clamped to the terminal so it never clips. */
44
- const MENU_WIDTH = 22;
45
- const MENU_HEIGHT = MENU_ACTIONS.length + 2;
46
- const MENU_LABEL_KEYS = {
47
- open: 'resume-menu-open',
48
- rename: 'resume-menu-rename',
49
- delete: 'resume-menu-delete',
50
- };
51
- /** The pin item's label depends on the target's current pin state. */
52
- const menuLabelKey = (action, pinned) => action === 'pin'
53
- ? (pinned ? 'resume-menu-unpin' : 'resume-menu-pin')
54
- : MENU_LABEL_KEYS[action];
55
- /** A thrown value's message, for a notification that has to say something. */
56
- function message(error) {
57
- return error instanceof Error ? error.message : String(error);
58
- }
59
- /** Rendered width of a hint: `HintLine` strips the `**` emphasis markers. */
60
- function hintWidth(text) {
61
- return stringWidth(text.replace(/\*\*/gu, ''));
62
- }
63
- /**
64
- * The widest hint that fits, cut only when even the shortest will not.
65
- *
66
- * A wrapped hint is worse than an abbreviated one in two ways: it eats rows
67
- * the list needs, and — being the last region on screen — the part that falls
68
- * off the bottom is its own tail, so the keys nobody can guess are exactly
69
- * the ones that disappear.
70
- *
71
- * @param candidates - Variants from widest to narrowest.
72
- * @param budget - Columns available to the row.
73
- */
74
- function fitHint(candidates, budget) {
75
- for (const candidate of candidates) {
76
- if (hintWidth(candidate) <= budget)
77
- return candidate;
78
- }
79
- return truncateWidth((candidates[candidates.length - 1] ?? '').replace(/\*\*/gu, ''), budget);
80
- }
81
- /**
82
- * The session browser — `/resume` as a screen of its own.
83
- *
84
- * The old picker was a panel of eight titles and a timestamp, and the reason
85
- * it could not be more than that was never layout: the data behind it was five
86
- * fields wide. With every session now arriving classified, sized, dated and
87
- * attributed, the surface that shows them can do the job a person actually
88
- * came for — find one conversation among many.
89
- *
90
- * What that means concretely:
91
- *
92
- * - Working-directory scope is visible and selectable. Wide terminals keep a
93
- * directory rail beside the sessions; narrow terminals drill into the same
94
- * directory list without sacrificing title width.
95
- * - Search is always live in the active list. There is no mode to enter;
96
- * typing filters, because the list is the search results.
97
- * - Delegated sub-agent runs are folded away by default and revealed under
98
- * their parents on demand. They are not noise to be deleted — they are the
99
- * model's own work, and it is worth being able to open one — but they are
100
- * not what "resume a conversation" means, and there are five of them for
101
- * every conversation.
102
- * - Sessions that hold no conversation are never listed, only counted, with
103
- * one action to clear them.
104
- * - The preview shows the end of a session, so "is this the one I was in the
105
- * middle of" is answerable without resuming it.
106
- *
107
- * Steady-state listings reuse a revision-keyed digest cache. The rare legacy
108
- * log whose opening prompt sits beyond the cheap window is scanned
109
- * progressively and cached, so it recovers a real title without making every
110
- * browser open pay for full history.
111
- */
112
- export function SessionBrowser({ channel, home, sameProject, onClose, }) {
113
- const { columns, rows } = useTerminalSize();
114
- const inset = React.useContext(PageInsetContext);
115
- const isTerminalFocused = useTerminalFocus();
116
- const [sessions, setSessions] = React.useState([]);
117
- const [loaded, setLoaded] = React.useState(false);
118
- const [filters, setFilters] = React.useState(DEFAULT_FILTERS);
119
- const [level, setLevel] = React.useState('sessions');
120
- const [selectedWorkspaceId, setSelectedWorkspaceId] = React.useState('current');
121
- const [workspaceFocusId, setWorkspaceFocusId] = React.useState('current');
122
- const [workspaceQuery, setWorkspaceQuery] = React.useState('');
123
- const [scopeHovered, setScopeHovered] = React.useState(false);
124
- // The cursor is a session ID, not a row index.
125
- //
126
- // Rows are reordered by almost everything the browser does: a rename touches
127
- // MRU and moves its row to the top, a filter rebuilds the list, a delete
128
- // removes one. An index survives none of those — it keeps pointing at a
129
- // POSITION, so the cursor silently lands on a different session and the next
130
- // Enter or ctrl+d acts on the wrong one. Tracking identity makes "the cursor
131
- // stays on the session you were looking at" true by construction instead of
132
- // something every mutation has to remember to restore.
133
- const [focusId, setFocusId] = React.useState(undefined);
134
- // The window start is a scroll anchor, not state: it is derived from the
135
- // focus every render and only ever read back to keep a stationary cursor
136
- // from re-shuffling the screen. Holding it in state would mean setting
137
- // state during render to resolve a layout that render already knows.
138
- const topRef = React.useRef(0);
139
- const workspaceTopRef = React.useRef(0);
140
- const [mode, setMode] = React.useState('list');
141
- const [renameText, setRenameText] = React.useState('');
142
- /** Hover state for the confirm rows (only one is ever mounted). */
143
- const [confirmHovered, setConfirmHovered] = React.useState(false);
144
- // The browser owns the whole screen, so it has to carry its own messages.
145
- // `channel.notify` renders into the conversation — which is exactly what is
146
- // NOT on screen right now — so a failed delete, a refused resume, or an
147
- // unreadable listing would otherwise report itself to a hidden surface and
148
- // look to the user like nothing happened at all.
149
- const [notice, setNotice] = React.useState();
150
- const [previewOpen, setPreviewOpen] = React.useState(false);
151
- const [preview, setPreview] = React.useState([]);
152
- const [previewLoading, setPreviewLoading] = React.useState(false);
153
- /**
154
- * Pinned session ids, from `~/.dsh-tui/session-pins.json`. Pins whose
155
- * sessions no longer exist are inert: the view filters them against the
156
- * live listing, and the file is left alone (lazy tolerance — a preference
157
- * file is not an index and rewrites cost more than stale entries).
158
- */
159
- const [pins, setPins] = React.useState(() => readSessionPins());
160
- // Keep pin toggles synchronous across several keys delivered in one stdin
161
- // chunk. The state render may lag, but the next command must see the set the
162
- // previous command successfully persisted.
163
- const pinsRef = React.useRef(pins);
164
- pinsRef.current = pins;
165
- /**
166
- * The right-click session menu: which session it belongs to, where the
167
- * pointer was (0-indexed screen coords, for anchoring), and which item
168
- * the keyboard cursor is on. Undefined = no menu.
169
- */
170
- const [menu, setMenu] = React.useState(undefined);
171
- // React batches every parsed key from one stdin chunk. Lock actions in a
172
- // ref so a repeated Enter cannot start the same async operation twice
173
- // before the mode change is rendered.
174
- const actionPendingRef = React.useRef(false);
175
- // One clock per render pass: every relative time on screen must agree, and
176
- // re-reading it per row would let two rows a millisecond apart round to
177
- // different minutes.
178
- const now = Date.now();
179
- /**
180
- * Say something, on screen and in the transcript.
181
- *
182
- * Both, deliberately: the line below the list is what the user can actually
183
- * read right now, and the transcript entry is what they still have after
184
- * they leave.
185
- */
186
- const report = React.useCallback((text, tone) => {
187
- setNotice({ text, tone });
188
- channel.notify(text, tone === 'error' ? { color: 'error' } : {});
189
- }, [channel]);
190
- // Every await below is wrapped. The adapter's own paths are total functions
191
- // that degrade rather than throw, but this is the UI: a rejection escaping
192
- // an event handler here becomes an unhandled rejection with the terminal in
193
- // raw mode and the alternate screen active — the worst possible place to
194
- // find out a promise was not caught. A failure leaves the browser standing
195
- // with whatever it already had.
196
- const reload = React.useCallback(async () => {
197
- try {
198
- setSessions(await channel.listSessions());
199
- }
200
- catch (error) {
201
- report(t('session-list-failed', { err: message(error) }), 'error');
202
- }
203
- finally {
204
- setLoaded(true);
205
- }
206
- }, [channel, report]);
207
- React.useEffect(() => {
208
- void reload();
209
- }, [reload]);
210
- const browserContext = React.useMemo(() => ({
211
- cwd: channel.cwd,
212
- branch: channel.gitBranch,
213
- currentId: channel.agentId,
214
- sameProject,
215
- }), [channel.cwd, channel.gitBranch, channel.agentId, sameProject]);
216
- const workspaceGroups = React.useMemo(() => buildWorkspaceGroups(sessions, browserContext), [sessions, browserContext]);
217
- const totalWorkspaceSessions = workspaceGroups.reduce((sum, group) => sum + group.count, 0);
218
- const workspaceChoices = React.useMemo(() => {
219
- return [
220
- { id: 'all', kind: 'all', count: totalWorkspaceSessions },
221
- ...workspaceGroups.map((workspace) => ({
222
- id: workspace.id,
223
- kind: 'workspace',
224
- workspace,
225
- })),
226
- ];
227
- }, [workspaceGroups, totalWorkspaceSessions]);
228
- const filteredWorkspaceChoices = React.useMemo(() => {
229
- const needle = workspaceQuery.trim().toLowerCase();
230
- if (needle === '')
231
- return workspaceChoices;
232
- return workspaceChoices.filter((choice) => {
233
- if (choice.kind === 'all') {
234
- return `${t('session-workspace-all')} ${t('session-scope-all')} ${t('session-workspace-all-detail', { n: choice.count })}`
235
- .toLowerCase()
236
- .includes(needle);
237
- }
238
- const state = choice.workspace.current
239
- ? t('session-workspace-current')
240
- : choice.workspace.count === 0
241
- ? t('session-workspace-empty')
242
- : '';
243
- return `${projectName(choice.workspace.cwd)} ${formatProject(choice.workspace.cwd, home)} ${state}`
244
- .toLowerCase()
245
- .includes(needle);
246
- });
247
- }, [workspaceChoices, workspaceQuery, home]);
248
- const selectedWorkspace = selectedWorkspaceId === 'all'
249
- ? undefined
250
- : workspaceGroups.find(group => group.id === selectedWorkspaceId) ?? workspaceGroups[0];
251
- React.useEffect(() => {
252
- if (selectedWorkspaceId === 'all' || workspaceGroups.some(group => group.id === selectedWorkspaceId))
253
- return;
254
- setSelectedWorkspaceId('current');
255
- setWorkspaceFocusId('current');
256
- setFilters(current => ({ ...current, allProjects: false }));
257
- }, [selectedWorkspaceId, workspaceGroups]);
258
- const scopeCwd = selectedWorkspace?.cwd ?? channel.cwd;
259
- const scopeSameProject = React.useMemo(() => selectedWorkspace?.current !== false
260
- ? sameProject
261
- : (left, right) => normalizeWorkspaceCwd(left) === normalizeWorkspaceCwd(right), [selectedWorkspace?.id, selectedWorkspace?.current, sameProject]);
262
- const view = React.useMemo(() => buildView(sessions, filters, {
263
- cwd: scopeCwd,
264
- branch: channel.gitBranch,
265
- currentId: channel.agentId,
266
- sameProject: scopeSameProject,
267
- }, pins), [sessions, filters, scopeCwd, channel.gitBranch, channel.agentId, scopeSameProject, pins]);
268
- // Resolve identity to a position once per render. A cursor whose session is
269
- // gone — deleted, filtered out, or never there — falls to the first
270
- // selectable row rather than to nothing, so the list is never unusable.
271
- const focus = React.useMemo(() => {
272
- const byId = view.rows.findIndex(row => row.kind === 'session' && row.session.id === focusId);
273
- return byId >= 0 ? byId : Math.max(0, seekSelectable(view.rows, 0, 1));
274
- }, [view.rows, focusId]);
275
- const focused = sessionAt(view.rows, focus);
276
- /** The session the open menu acts on; undefined once it leaves the view. */
277
- const menuTarget = React.useMemo(() => {
278
- if (menu === undefined)
279
- return undefined;
280
- for (const row of view.rows) {
281
- if (row.kind === 'session' && row.session.id === menu.sessionId)
282
- return row.session;
283
- }
284
- return undefined;
285
- }, [view.rows, menu]);
286
- // A menu whose session vanished (filtered out, deleted by another path)
287
- // must not linger invisible but still capture keys.
288
- React.useEffect(() => {
289
- if (menu !== undefined && menuTarget === undefined)
290
- closeMenu();
291
- }, [menu, menuTarget]);
292
- const workspaceFocus = React.useMemo(() => {
293
- const byId = filteredWorkspaceChoices.findIndex(choice => choice.id === workspaceFocusId);
294
- if (byId >= 0)
295
- return byId;
296
- const selected = filteredWorkspaceChoices.findIndex(choice => choice.id === selectedWorkspaceId);
297
- return Math.max(0, selected);
298
- }, [filteredWorkspaceChoices, workspaceFocusId, selectedWorkspaceId]);
299
- const focusedWorkspace = filteredWorkspaceChoices[workspaceFocus];
300
- /**
301
- * Resume a session (Enter and row-click share this path). The screen closes
302
- * only once the resume actually happened. Closing first and letting a
303
- * refusal fall through to a notification would send that explanation to the
304
- * conversation the user is not looking at, and leave them staring at an
305
- * unchanged transcript wondering what Enter did. `resumeTo` reports its own
306
- * reasons; this only has to stay put.
307
- */
308
- const resumeSession = (target) => {
309
- runAction(async () => {
310
- try {
311
- const result = await channel.resumeTo(target.id);
312
- if (result.ok) {
313
- channel.notify(t('resume-resumed'));
314
- onClose();
315
- }
316
- else {
317
- if (result.reason === 'cancelled')
318
- return;
319
- const text = result.reason === 'working'
320
- ? t('resume-while-working')
321
- : result.reason === 'unavailable'
322
- ? t('resume-unavailable')
323
- : t('session-resume-failed', { err: result.error });
324
- setNotice({ text, tone: 'error' });
325
- }
326
- }
327
- catch (error) {
328
- setNotice({ text: t('session-resume-failed', { err: message(error) }), tone: 'error' });
329
- }
330
- });
331
- };
332
- const chooseWorkspace = (choice) => {
333
- if (mode !== 'list' || actionPendingRef.current)
334
- return;
335
- closeMenu();
336
- setSelectedWorkspaceId(choice.id);
337
- setWorkspaceFocusId(choice.id);
338
- setFilters(current => ({ ...current, query: '', allProjects: choice.kind === 'all' }));
339
- setFocusId(undefined);
340
- setPreviewOpen(false);
341
- setPreview([]);
342
- topRef.current = 0;
343
- setLevel('sessions');
344
- };
345
- const openWorkspaceMenu = () => {
346
- if (mode !== 'list' || actionPendingRef.current)
347
- return;
348
- closeMenu();
349
- setScopeHovered(false);
350
- setWorkspaceFocusId(selectedWorkspaceId);
351
- setLevel('workspaces');
352
- };
353
- // Where the cursor is RIGHT NOW, including moves made earlier in this same
354
- // tick. A held arrow key (or a paste) delivers several key events from one
355
- // stdin chunk, and every one of them runs before React re-renders — so a
356
- // handler that read the position from the render closure would compute all
357
- // of them from the same starting row and keep only the last, silently
358
- // dropping every move but one.
359
- const focusRef = React.useRef(focus);
360
- focusRef.current = focus;
361
- /** Move the cursor by rows, then store the session it landed on. */
362
- const step = (by, times = 1) => {
363
- let next = focusRef.current;
364
- for (let taken = 0; taken < times; taken++)
365
- next = moveSelection(view.rows, next, by);
366
- focusRef.current = next;
367
- const landed = sessionAt(view.rows, next);
368
- if (landed !== undefined)
369
- setFocusId(landed.id);
370
- };
371
- const workspaceFocusRef = React.useRef(workspaceFocus);
372
- workspaceFocusRef.current = workspaceFocus;
373
- const stepWorkspace = (by, times = 1) => {
374
- if (filteredWorkspaceChoices.length === 0)
375
- return;
376
- let next = workspaceFocusRef.current;
377
- for (let taken = 0; taken < times; taken++) {
378
- next = (next + by + filteredWorkspaceChoices.length) % filteredWorkspaceChoices.length;
379
- }
380
- workspaceFocusRef.current = next;
381
- const landed = filteredWorkspaceChoices[next];
382
- if (landed !== undefined)
383
- setWorkspaceFocusId(landed.id);
384
- };
385
- // The menu cursor has the same problem the session cursor has: several
386
- // keys from one stdin chunk are all handled before React re-renders, so
387
- // a ↓ then Enter in the same chunk would read the pre-↓ item from the
388
- // closure. Move the item through a ref, like `step`/`stepWorkspace`.
389
- const menuRef = React.useRef(menu);
390
- menuRef.current = menu;
391
- const stepMenuItem = (by) => {
392
- const current = menuRef.current;
393
- if (current === undefined)
394
- return;
395
- const next = { ...current, item: (current.item + by + MENU_ACTIONS.length) % MENU_ACTIONS.length };
396
- menuRef.current = next;
397
- setMenu(next);
398
- };
399
- /**
400
- * Dismiss the context menu. State and ref are cleared TOGETHER: the
401
- * keyboard branch gates on menuRef, and a mouse dismissal followed by a
402
- * keystroke in the same stdin chunk must not re-activate the stale menu.
403
- */
404
- const closeMenu = () => {
405
- menuRef.current = undefined;
406
- setMenu(undefined);
407
- };
408
- const handleSessionWheel = (event) => {
409
- if (mode !== 'list' || actionPendingRef.current)
410
- return;
411
- // 菜单开着时滚轮只是关掉菜单,不滚动列表——行是惰性的,滚轮
412
- // 不该在用户还没看清菜单时把目标行换走。
413
- if (menu !== undefined) {
414
- closeMenu();
415
- return;
416
- }
417
- setLevel('sessions');
418
- step(event.deltaY >= 0 ? 1 : -1);
419
- };
420
- const handleWorkspaceWheel = (event) => {
421
- if (mode !== 'list' || actionPendingRef.current)
422
- return;
423
- if (menu !== undefined) {
424
- closeMenu();
425
- return;
426
- }
427
- setLevel('workspaces');
428
- stepWorkspace(event.deltaY >= 0 ? 1 : -1);
429
- };
430
- // Preview follows the cursor. Keyed on the id so arrowing past a row does
431
- // not refetch the one it came from, and guarded on unmount so a slow read
432
- // landing after the cursor moved cannot overwrite a newer preview.
433
- React.useEffect(() => {
434
- if (!previewOpen || focused === undefined)
435
- return;
436
- let live = true;
437
- setPreviewLoading(true);
438
- void channel
439
- .previewSession(focused.id)
440
- .then(entries => (live ? entries : undefined))
441
- .catch(() => (live ? [] : undefined))
442
- .then((entries) => {
443
- if (entries === undefined)
444
- return;
445
- setPreview(entries);
446
- setPreviewLoading(false);
447
- });
448
- return () => {
449
- live = false;
450
- };
451
- }, [channel, previewOpen, focused?.id]);
452
- // Wide terminals keep a directory rail visible. Narrow terminals drill into
453
- // the same list as a page, so classification stays explicit without leaving
454
- // too little width for two-line session rows.
455
- const compactChrome = rows < 10;
456
- const mandatoryLines = compactChrome ? COMPACT_MANDATORY_LINES : REGULAR_MANDATORY_LINES;
457
- const workspaceRail = columns >= WORKSPACE_RAIL_MIN_COLUMNS;
458
- const workspaceWidth = workspaceRail
459
- ? Math.min(34, Math.max(26, Math.floor(columns * 0.24)))
460
- : columns;
461
- const workspaceGap = workspaceRail ? 1 : 0;
462
- const sessionAreaWidth = Math.max(20, columns - (workspaceRail ? workspaceWidth + workspaceGap : 0));
463
- const workspaceAreaVisible = workspaceRail || level === 'workspaces';
464
- const sessionAreaVisible = workspaceRail || level === 'sessions';
465
- // Preview joins the session list only when the SESSION area itself is wide;
466
- // otherwise it replaces that list while the directory rail (if any) remains.
467
- const canPreview = focused !== undefined;
468
- const splitPreview = previewOpen && canPreview && sessionAreaVisible && sessionAreaWidth >= SPLIT_MIN_COLUMNS;
469
- const soloPreview = previewOpen && canPreview && sessionAreaVisible && !splitPreview;
470
- const previewWidth = splitPreview
471
- ? Math.min(56, Math.floor(sessionAreaWidth * 0.42))
472
- : sessionAreaWidth;
473
- const listWidth = Math.max(20, sessionAreaWidth - (splitPreview ? previewWidth : 0));
474
- // Height is distributed, never assumed. The permanent notice slot prevents
475
- // mutation/error feedback from shifting the list under the cursor.
476
- const extraLines = mode === 'list' ? 0 : 1;
477
- // Decoration yields before the first complete two-line selectable row.
478
- const ruleBudget = Math.max(0, Math.min(RULE_PRIORITY.length, rows - mandatoryLines - extraLines - WORKSPACE_ROW_HEIGHT));
479
- const rules = new Set(RULE_PRIORITY.slice(0, ruleBudget));
480
- const listHeight = Math.max(0, rows - mandatoryLines - extraLines - rules.size);
481
- const windowTop = anchorTop(view.rows, focus, listHeight, topRef.current);
482
- topRef.current = windowTop;
483
- const visible = view.rows.slice(windowTop, windowEnd(view.rows, windowTop, listHeight));
484
- const workspaceCapacity = Math.max(0, Math.floor(listHeight / WORKSPACE_ROW_HEIGHT));
485
- let workspaceTop = Math.min(Math.max(0, workspaceTopRef.current), Math.max(0, filteredWorkspaceChoices.length - workspaceCapacity));
486
- if (workspaceFocus < workspaceTop)
487
- workspaceTop = workspaceFocus;
488
- if (workspaceCapacity > 0 && workspaceFocus >= workspaceTop + workspaceCapacity) {
489
- workspaceTop = workspaceFocus - workspaceCapacity + 1;
490
- }
491
- workspaceTopRef.current = workspaceTop;
492
- const visibleWorkspaces = workspaceCapacity === 0
493
- ? []
494
- : filteredWorkspaceChoices.slice(workspaceTop, workspaceTop + workspaceCapacity);
495
- /**
496
- * Change the view.
497
- *
498
- * Takes a function of the CURRENT filters rather than a ready-made patch,
499
- * for the same reason the cursor keeps a ref: several key events can be
500
- * handled before React re-renders, and a patch built from the render
501
- * closure would compute each of them from the same starting filters — two
502
- * toggles in one chunk cancelling to one, or typed characters overwriting
503
- * each other instead of accumulating.
504
- */
505
- const applyFilters = (update) => {
506
- setFilters(current => ({ ...current, ...update(current) }));
507
- topRef.current = 0;
508
- };
509
- const runAction = (action) => {
510
- if (actionPendingRef.current)
511
- return;
512
- actionPendingRef.current = true;
513
- void action().finally(() => {
514
- actionPendingRef.current = false;
515
- });
516
- };
517
- /** Run one mutation, report it, and re-list — reporting a failure either way. */
518
- const mutate = (action, done, failed) => {
519
- runAction(async () => {
520
- let ok = false;
521
- let reason;
522
- try {
523
- ok = await action();
524
- }
525
- catch (error) {
526
- reason = message(error);
527
- }
528
- report(ok ? done : reason === undefined ? failed : `${failed} · ${reason}`, ok ? 'info' : 'error');
529
- await reload();
530
- });
531
- };
532
- /** Persist one pin mutation and synchronously adopt the returned full set. */
533
- const persistPin = (id, pinned) => {
534
- const result = setSessionPinned(id, pinned);
535
- if (!result.ok)
536
- return false;
537
- pinsRef.current = result.pins;
538
- setPins(result.pins);
539
- return true;
540
- };
541
- /**
542
- * Pin or unpin one session. A pure view preference: no reload needed, the
543
- * pinned group is derived from the listing already on screen. Persistence
544
- * failure is visible and never paints a star that will disappear on restart.
545
- */
546
- const togglePin = (target) => {
547
- if (mode !== 'list' || actionPendingRef.current)
548
- return;
549
- closeMenu();
550
- const pinning = !pinsRef.current.has(target.id);
551
- if (!persistPin(target.id, pinning)) {
552
- report(t('resume-pin-save-failed'), 'error');
553
- return;
554
- }
555
- report(t(pinning ? 'resume-pinned' : 'resume-unpinned', { name: target.title.text }), 'info');
556
- };
557
- const runDelete = (target) => mutate(async () => {
558
- const ok = await channel.deleteSession(target.id);
559
- // A deleted session's pin must not linger into the next launch's
560
- // pinned group (where its row would then be silently missing).
561
- if (ok && pinsRef.current.has(target.id)) {
562
- // Failure is harmless here: the deleted id is lazily invisible, and
563
- // a later pin mutation retries from the persisted set.
564
- persistPin(target.id, false);
565
- }
566
- return ok;
567
- }, t('resume-deleted', { name: target.title.text }), t('resume-delete-failed', { name: target.title.text }));
568
- const runRename = (target, title) => mutate(() => channel.renameSessionTo(target.id, title), t('rename-done', { title }), t('resume-rename-failed', { name: target.title.text }));
569
- const runClean = () => {
570
- // Snapshot the ids before any await: the view is rebuilt by the reload
571
- // below, and deleting from a list that moved under us would be a
572
- // destructive action aimed at whatever happens to be there now.
573
- const ids = [...view.emptyIds];
574
- runAction(async () => {
575
- let removed = 0;
576
- for (const id of ids) {
577
- try {
578
- if (await channel.deleteSession(id))
579
- removed += 1;
580
- }
581
- catch {
582
- // One unremovable log must not abandon the rest of the sweep.
583
- }
584
- }
585
- report(t('session-cleaned', { n: removed }), 'info');
586
- await reload();
587
- });
588
- };
589
- /**
590
- * Run the action the menu cursor points at, then close the menu. Shares
591
- * the keyboard paths: open = Enter, rename = Ctrl+R, delete = Ctrl+D.
592
- */
593
- const activateMenu = (target, item) => {
594
- closeMenu();
595
- const action = MENU_ACTIONS[item];
596
- if (action === 'open') {
597
- resumeSession(target);
598
- }
599
- else if (action === 'pin') {
600
- togglePin(target);
601
- }
602
- else if (action === 'rename') {
603
- setRenameText(target.title.text);
604
- setMode('rename');
605
- }
606
- else {
607
- setMode('confirm-delete');
608
- }
609
- };
610
- useInput((input, key) => {
611
- if (actionPendingRef.current)
612
- return;
613
- // A notice describes what the LAST action did; the next keystroke makes it
614
- // stale, so it goes as soon as the user acts again.
615
- if (notice !== undefined)
616
- setNotice(undefined);
617
- // The context menu is modal like the confirmations: ↑/↓/Enter drive it,
618
- // Esc dismisses it, and any other key dismisses it without acting
619
- // (DOM-menu semantics — a stray keystroke should not also type into the
620
- // search box behind a menu the user has not dismissed). The gate reads
621
- // the REF so a right-click and a keystroke in the same stdin chunk (the
622
- // click handler sets the ref before React renders) behave like the
623
- // separate chunks they visually are.
624
- if (menuRef.current !== undefined) {
625
- if (key.upArrow) {
626
- stepMenuItem(-1);
627
- }
628
- else if (key.downArrow) {
629
- stepMenuItem(1);
630
- }
631
- else if (isPlainReturn(key)) {
632
- const current = menuRef.current;
633
- if (current !== undefined && menuTarget !== undefined)
634
- activateMenu(menuTarget, current.item);
635
- }
636
- else {
637
- closeMenu();
638
- }
639
- return;
640
- }
641
- if (mode === 'confirm-delete') {
642
- if (isPlainReturn(key)) {
643
- setMode('list');
644
- if (focused !== undefined)
645
- runDelete(focused);
646
- }
647
- else if (key.escape) {
648
- setMode('list');
649
- }
650
- return;
651
- }
652
- if (mode === 'confirm-clean') {
653
- if (isPlainReturn(key)) {
654
- setMode('list');
655
- runClean();
656
- }
657
- else if (key.escape) {
658
- setMode('list');
659
- }
660
- return;
661
- }
662
- if (mode === 'rename') {
663
- if (isPlainReturn(key)) {
664
- setMode('list');
665
- const title = renameText.trim();
666
- if (focused !== undefined && title.length > 0)
667
- runRename(focused, title);
668
- }
669
- else if (key.escape) {
670
- setMode('list');
671
- }
672
- else if (key.backspace || key.delete) {
673
- setRenameText(text => text.slice(0, -1));
674
- }
675
- else if (!isMod(key) && !key.meta && input) {
676
- setRenameText(text => text + input.replace(/[\r\n]+/g, ' '));
677
- }
678
- return;
679
- }
680
- if (level === 'workspaces') {
681
- if (key.upArrow || key.wheelUp) {
682
- stepWorkspace(-1);
683
- }
684
- else if (key.downArrow || key.wheelDown) {
685
- stepWorkspace(1);
686
- }
687
- else if (key.pageUp || key.pageDown) {
688
- stepWorkspace(key.pageDown ? 1 : -1, Math.max(1, workspaceCapacity));
689
- }
690
- else if (isPlainReturn(key) || key.rightArrow) {
691
- if (focusedWorkspace !== undefined)
692
- chooseWorkspace(focusedWorkspace);
693
- }
694
- else if (key.escape || key.leftArrow) {
695
- if (workspaceQuery.length > 0) {
696
- setWorkspaceQuery('');
697
- workspaceTopRef.current = 0;
698
- }
699
- else {
700
- setLevel('sessions');
701
- }
702
- }
703
- else if (isMod(key) && input === 'a') {
704
- const all = workspaceChoices[0];
705
- if (all !== undefined)
706
- chooseWorkspace(all);
707
- }
708
- else if (key.backspace || key.delete) {
709
- setWorkspaceQuery(query => query.slice(0, -1));
710
- workspaceTopRef.current = 0;
711
- }
712
- else if (!isMod(key) && !key.meta && !key.super && input && !key.return) {
713
- const typed = input.replace(/\p{Cc}/gu, '');
714
- if (typed.length > 0) {
715
- setWorkspaceQuery(query => query + typed);
716
- workspaceTopRef.current = 0;
717
- }
718
- }
719
- return;
720
- }
721
- if (key.leftArrow) {
722
- openWorkspaceMenu();
723
- }
724
- else if (key.upArrow) {
725
- step(-1);
726
- }
727
- else if (key.downArrow) {
728
- step(1);
729
- }
730
- else if (key.wheelUp) {
731
- // 滚轮在列表上:与 ↑ 同路径——焦点跟随窗口下移焦点即滚动
732
- step(-1);
733
- }
734
- else if (key.wheelDown) {
735
- step(1);
736
- }
737
- else if (key.pageUp || key.pageDown) {
738
- // A page is "as many rows as the window holds", taken as repeated single
739
- // steps so it lands on a selectable row like every other move.
740
- step(key.pageDown ? 1 : -1, Math.max(1, Math.floor(listHeight / 2)));
741
- }
742
- else if (isPlainReturn(key)) {
743
- if (focused === undefined)
744
- return;
745
- resumeSession(focused);
746
- }
747
- else if (key.escape) {
748
- // Esc backs out one layer at a time: a live query first, the screen
749
- // second. Closing on the first Esc would discard a search the user is
750
- // still refining.
751
- if (filters.query.length > 0)
752
- applyFilters(() => ({ query: '' }));
753
- else if (previewOpen)
754
- setPreviewOpen(false);
755
- else
756
- onClose();
757
- }
758
- else if (key.tab) {
759
- if (focused !== undefined)
760
- setPreviewOpen(open => !open);
761
- }
762
- else if (isMod(key) && input === 'a') {
763
- const target = filters.allProjects
764
- ? workspaceChoices.find(choice => choice.id === 'current')
765
- : workspaceChoices[0];
766
- if (target !== undefined)
767
- chooseWorkspace(target);
768
- }
769
- else if (isMod(key) && input === 'b') {
770
- applyFilters(current => ({ branchOnly: !current.branchOnly }));
771
- }
772
- else if (isMod(key) && input === 's') {
773
- applyFilters(current => ({ showSubagents: !current.showSubagents }));
774
- }
775
- else if (isMod(key) && input === 'r' && focused !== undefined) {
776
- setRenameText(focused.title.text);
777
- setMode('rename');
778
- }
779
- else if (isMod(key) && input === 'p') {
780
- // Resolve through focusRef, not the render closure: Down + Ctrl+P may
781
- // arrive in one stdin chunk, and the pin must follow the moved cursor.
782
- // Plain `p` remains search text like every unbound character.
783
- const target = sessionAt(view.rows, focusRef.current);
784
- if (target !== undefined)
785
- togglePin(target);
786
- }
787
- else if (isMod(key) && input === 'd' && focused !== undefined) {
788
- setMode('confirm-delete');
789
- }
790
- else if (isMod(key) && input === 'x' && view.emptyCount > 0) {
791
- setMode('confirm-clean');
792
- }
793
- else if (key.backspace || key.delete) {
794
- applyFilters(current => ({ query: current.query.slice(0, -1) }));
795
- }
796
- else if (!isMod(key) && !key.meta && !key.super && input && !key.return) {
797
- // Only real characters reach the query. Anything else the terminal
798
- // delivers — an unbound control byte, a chord this screen does not
799
- // claim, the newlines inside a paste — would otherwise be typed into
800
- // the search box invisibly, leaving a filter that matches nothing for
801
- // no reason the user can see.
802
- const typed = input.replace(/\p{Cc}/gu, '');
803
- if (typed.length > 0)
804
- applyFilters(current => ({ query: current.query + typed }));
805
- }
806
- });
807
- const counts = [];
808
- if (level === 'workspaces') {
809
- counts.push(t('session-workspace-project-count', { n: workspaceGroups.length }));
810
- counts.push(t('session-count-shown', { n: totalWorkspaceSessions }));
811
- }
812
- else {
813
- counts.push(t('session-count-shown', { n: view.shown }));
814
- if (view.hiddenSubagents > 0)
815
- counts.push(t('session-count-subagents', { n: view.hiddenSubagents }));
816
- if (view.emptyCount > 0)
817
- counts.push(t('session-count-empty', { n: view.emptyCount }));
818
- }
819
- // Every chrome row is pre-measured in terminal cells; flex truncation at an
820
- // exact fit can otherwise wrap one localized character and shift the footer.
821
- const heading = ` ↻ ${t('resume-title')}`;
822
- const header = spreadRow(heading, counts.join(' · '), Math.max(0, columns - 1));
823
- const scope = filters.allProjects
824
- ? t('session-scope-all')
825
- : formatProject(selectedWorkspace?.cwd ?? channel.cwd, home);
826
- // The scope and search share one four-row card (border + two content rows),
827
- // making directory classification visible without stacking two big panels.
828
- const inputBudget = Math.max(0, columns - (compactChrome ? 1 : 4));
829
- const scopeLeft = level === 'workspaces'
830
- ? `▣ ${t('session-workspace-select-title')}`
831
- : `▣ ${t('session-workspace-scope')} ${scope}`;
832
- const scopeRight = level === 'workspaces'
833
- ? t('session-workspace-project-count', { n: workspaceGroups.length })
834
- : t('session-workspace-switch');
835
- const scopeRow = spreadRow(scopeLeft, scopeRight, inputBudget);
836
- const activeQuery = level === 'workspaces' ? workspaceQuery : filters.query;
837
- const searchPlaceholder = level === 'workspaces'
838
- ? t('session-workspace-search-placeholder')
839
- : t('session-search-placeholder', { scope });
840
- const hint = mode === 'confirm-delete' || mode === 'confirm-clean'
841
- ? fitHint([t('resume-hint-delete')], inputBudget)
842
- : mode === 'rename'
843
- ? fitHint([t('resume-hint-rename')], inputBudget)
844
- : level === 'workspaces'
845
- ? fitHint([
846
- t('session-hint-workspaces', { mod: modLabel }),
847
- t('session-hint-workspaces-short'),
848
- ], inputBudget)
849
- : fitHint([
850
- t('session-hint-list', {
851
- mod: modLabel,
852
- projects: filters.allProjects ? t('session-toggle-on') : t('session-toggle-off'),
853
- runs: filters.showSubagents ? t('session-toggle-on') : t('session-toggle-off'),
854
- }),
855
- t('session-hint-list-mid', { mod: modLabel }),
856
- t('session-hint-list-short', { mod: modLabel }),
857
- ], inputBudget);
858
- return (_jsxs(Box, { flexDirection: "column", width: columns, height: rows, onClick: menu !== undefined ? closeMenu : undefined, children: [_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: header.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(header.gap)}${header.right}` })] }), _jsxs(Box, { flexDirection: "column", flexShrink: 0, borderStyle: compactChrome ? undefined : 'round', borderColor: !compactChrome && mode === 'list' ? 'permission' : undefined, borderDimColor: !compactChrome && mode !== 'list', paddingX: compactChrome ? 0 : 1, children: [_jsxs(Box, { height: 1, flexShrink: 0, overflow: "hidden", onClick: level === 'sessions' && mode === 'list' ? openWorkspaceMenu : undefined, onMouseEnter: level === 'sessions' && mode === 'list' ? () => setScopeHovered(true) : undefined, onMouseLeave: level === 'sessions' && mode === 'list' ? () => setScopeHovered(false) : undefined, backgroundColor: scopeHovered ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { color: "remember", bold: true, children: scopeRow.left }), _jsx(Text, { dimColor: true, children: `${' '.repeat(scopeRow.gap)}${scopeRow.right}` })] }), _jsx(SearchBox, { query: activeQuery, isFocused: mode === 'list', isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(searchPlaceholder, inputBudget), borderless: true, width: "100%" })] }), _jsxs("ink-box", { style: { flexDirection: 'row', gap: workspaceGap, flexGrow: 1, flexShrink: 1, overflow: 'hidden' }, children: [workspaceAreaVisible && (_jsxs("ink-box", { style: {
859
- flexDirection: 'column',
860
- width: workspaceRail ? workspaceWidth : columns,
861
- height: listHeight,
862
- flexShrink: 0,
863
- overflow: 'hidden',
864
- }, onWheel: handleWorkspaceWheel, children: [!loaded && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-loading'), workspaceWidth - 2)}` })), loaded && filteredWorkspaceChoices.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-workspace-no-match'), workspaceWidth - 2)}` })), visibleWorkspaces.map((choice, index) => (_jsx(WorkspaceListRow, { workspace: choice.kind === 'workspace' ? choice.workspace : undefined, all: choice.kind === 'all', totalProjects: workspaceGroups.length, totalSessions: totalWorkspaceSessions, width: workspaceRail ? workspaceWidth : columns, focused: workspaceTop + index === workspaceFocus && level === 'workspaces', selected: choice.id === selectedWorkspaceId, home: home, now: now, onClick: mode === 'list' ? () => chooseWorkspace(choice) : undefined }, choice.id)))] })), sessionAreaVisible && (_jsxs("ink-box", { style: {
865
- flexDirection: 'row',
866
- width: sessionAreaWidth,
867
- height: listHeight,
868
- flexShrink: 0,
869
- overflow: 'hidden',
870
- }, onWheel: handleSessionWheel, children: [!soloPreview && (_jsxs(Box, { flexDirection: "column", width: listWidth, height: listHeight, flexShrink: 0, children: [!loaded && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('session-loading'), listWidth - 2)}` })), loaded && view.rows.length === 0 && (_jsx(Text, { dimColor: true, italic: true, children: ` ${truncateWidth(t('resume-none-in-cwd'), listWidth - 2)}` })), visible.map((row, index) => row.kind === 'project' ? (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "planMode", bold: true, children: truncateWidth(` ▣ ${formatProject(row.project, home)}`, listWidth - 8) }), _jsx(Text, { dimColor: true, children: ` ${row.count}` })] }, `project:${row.project}:${index}`)) : row.kind === 'pin' ? (_jsxs(Box, { flexShrink: 0, children: [_jsx(Text, { color: "remember", bold: true, children: truncateWidth(` ★ ${t('session-pinned-group')}`, listWidth - 8) }), _jsx(Text, { dimColor: true, children: ` ${row.count}` })] }, "pin-header")) : (_jsx(SessionListRow, { session: row.session, width: listWidth, depth: row.depth, focused: windowTop + index === focus && level === 'sessions', pinned: pins.has(row.session.id), now: now, onClick: mode === 'list' && menu === undefined ? () => {
871
- if (actionPendingRef.current)
872
- return;
873
- setLevel('sessions');
874
- setFocusId(row.session.id);
875
- resumeSession(row.session);
876
- } : undefined, onTogglePin: mode === 'list' && menu === undefined && !actionPendingRef.current
877
- ? () => {
878
- setFocusId(row.session.id);
879
- togglePin(row.session);
880
- }
881
- : undefined, onContextMenu: mode === 'list' ? (event) => {
882
- if (actionPendingRef.current)
883
- return;
884
- setLevel('sessions');
885
- setFocusId(row.session.id);
886
- // Write the ref alongside the state: the keyboard
887
- // gate reads menuRef, so a right-click followed by a
888
- // keystroke in the same stdin chunk must see the
889
- // menu as open.
890
- const next = { sessionId: row.session.id, col: event.col, row: event.row, item: 0 };
891
- menuRef.current = next;
892
- setMenu(next);
893
- } : undefined }, row.session.id)))] })), (splitPreview || soloPreview) && focused !== undefined && (_jsx(SessionPreview, { session: focused, entries: preview, loading: previewLoading, width: previewWidth, height: listHeight, home: home, now: now }))] }))] }), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { color: notice?.tone === 'error' ? 'error' : 'success', children: notice === undefined
894
- ? ' '
895
- : ` ${notice.tone === 'error' ? MULTIPLICATION_X : TICK} ${truncateWidth(notice.text, Math.max(0, columns - 4))}` }) }), mode === 'confirm-delete' && focused !== undefined && (_jsx(Box, { flexShrink: 0, onClick: () => {
896
- // 点击确认行 = 确认删除(与 Enter 同路径);确认屏本身就是
897
- // 显式确认层,取消保留键盘 Esc,防误点
898
- setMode('list');
899
- runDelete(focused);
900
- }, onMouseEnter: () => setConfirmHovered(true), onMouseLeave: () => setConfirmHovered(false), backgroundColor: confirmHovered ? 'userMessageBackgroundHover' : undefined, children: _jsx(Text, { color: "error", children: ` ${truncateWidth(t('resume-delete-confirm', { name: focused.title.text }), inputBudget)}` }) })), mode === 'confirm-clean' && (_jsx(Box, { flexShrink: 0, onClick: () => {
901
- setMode('list');
902
- runClean();
903
- }, onMouseEnter: () => setConfirmHovered(true), onMouseLeave: () => setConfirmHovered(false), backgroundColor: confirmHovered ? 'userMessageBackgroundHover' : undefined, children: _jsx(Text, { color: "warning", children: ` ${truncateWidth(t('session-clean-confirm', { n: view.emptyCount }), inputBudget)}` }) })), mode === 'rename' && (_jsx(Box, { flexShrink: 0, children: _jsx(SearchBox, { query: tailWidth(renameText, inputBudget), isFocused: true, isTerminalFocused: isTerminalFocused, placeholder: truncateWidth(t('resume-rename-placeholder'), inputBudget), prefix: "\u270E", borderless: true,
904
- // 同搜索卡片:自适应宽度会让窗口化预算跟随内容收缩,
905
- // 追加字符后预填标题的头部被丢弃。
906
- width: "100%" }) })), rules.has(2) && (_jsx(Box, { flexShrink: 0, children: _jsx(Divider, { bleed: true }) })), _jsx(Box, { flexShrink: 0, children: _jsx(Text, { dimColor: true, italic: true, children: _jsx(HintLine, { text: hint }) }) }), menu !== undefined && menuTarget !== undefined && (_jsx(Box, { position: "absolute", left: Math.max(inset.x, Math.min(menu.col + 1, inset.x + Math.max(0, columns - MENU_WIDTH))), top: Math.max(inset.y, Math.min(menu.row + 1, inset.y + Math.max(0, rows - MENU_HEIGHT))), width: MENU_WIDTH, height: MENU_HEIGHT, flexDirection: "column", flexShrink: 0, borderStyle: "round", borderColor: "permission", backgroundColor: "toolCardBackground", children: MENU_ACTIONS.map((action, index) => (_jsx(Box, { height: 1, flexShrink: 0, backgroundColor: index === menu.item ? 'userMessageBackgroundHover' : undefined, onMouseEnter: () => setMenu(m => (m === undefined ? m : { ...m, item: index })), onClick: () => {
907
- if (actionPendingRef.current)
908
- return;
909
- activateMenu(menuTarget, index);
910
- }, children: _jsx(Text, { color: action === 'delete' ? 'error' : undefined, children: ` ${index === menu.item ? '❯' : ' '} ${t(menuLabelKey(action, pins.has(menuTarget.id)))}` }) }, action))) }))] }));
911
- }