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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (277) hide show
  1. package/README.md +171 -124
  2. package/bin/dsh-tui.js +748 -22
  3. package/cordis.patch.yml +38 -10
  4. package/cordis.yml +2 -0
  5. package/lib/types/adapter/channel/session-projection.d.ts.map +1 -1
  6. package/lib/types/adapter/channel/session-projection.js +2 -0
  7. package/lib/types/adapter/channel/ui-policy.d.ts +4 -1
  8. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  9. package/lib/types/adapter/channel/ui-policy.js +5 -1
  10. package/lib/types/adapter/kernel/host-facade.d.ts.map +1 -1
  11. package/lib/types/adapter/kernel/host-facade.js +1 -0
  12. package/lib/types/adapter/kernel/replay.d.ts.map +1 -1
  13. package/lib/types/adapter/kernel/replay.js +3 -0
  14. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  15. package/lib/types/adapter/kernel/runtime.js +2 -0
  16. package/lib/types/adapter/kernel/slices/workspace.d.ts.map +1 -1
  17. package/lib/types/adapter/kernel/slices/workspace.js +1 -0
  18. package/lib/types/adapter/ports/channel-ui.d.ts +22 -2
  19. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  20. package/lib/types/adapter/ports/channel-view.d.ts +66 -0
  21. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  22. package/lib/types/adapter/ports/channel-workspace.d.ts +23 -0
  23. package/lib/types/adapter/ports/channel-workspace.d.ts.map +1 -1
  24. package/lib/types/adapter/ports/workspace.d.ts +2 -0
  25. package/lib/types/adapter/ports/workspace.d.ts.map +1 -1
  26. package/lib/types/adapter/upstream/workspace-driver.d.ts.map +1 -1
  27. package/lib/types/adapter/upstream/workspace-driver.js +10 -2
  28. package/lib/types/commands.d.ts.map +1 -1
  29. package/lib/types/commands.js +1 -0
  30. package/lib/types/components/ContextBarView.d.ts +15 -7
  31. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  32. package/lib/types/components/ContextBarView.js +26 -22
  33. package/lib/types/components/ImagePreviewOverlay.d.ts.map +1 -1
  34. package/lib/types/components/ImagePreviewOverlay.js +11 -5
  35. package/lib/types/components/Markdown.d.ts +11 -1
  36. package/lib/types/components/Markdown.d.ts.map +1 -1
  37. package/lib/types/components/Markdown.js +20 -2
  38. package/lib/types/components/MermaidDiagram.d.ts +13 -0
  39. package/lib/types/components/MermaidDiagram.d.ts.map +1 -0
  40. package/lib/types/components/MermaidDiagram.js +61 -0
  41. package/lib/types/components/MessageList.d.ts +26 -1
  42. package/lib/types/components/MessageList.d.ts.map +1 -1
  43. package/lib/types/components/MessageList.js +72 -7
  44. package/lib/types/components/PageMargin.d.ts +1 -1
  45. package/lib/types/components/PromptInput.d.ts +37 -1
  46. package/lib/types/components/PromptInput.d.ts.map +1 -1
  47. package/lib/types/components/PromptInput.js +243 -28
  48. package/lib/types/components/ScrollbarGutter.d.ts +20 -5
  49. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -1
  50. package/lib/types/components/ScrollbarGutter.js +88 -10
  51. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  52. package/lib/types/components/StreamingMarkdown.js +30 -28
  53. package/lib/types/components/Tooltip.d.ts.map +1 -1
  54. package/lib/types/components/Tooltip.js +45 -7
  55. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  56. package/lib/types/components/messages/UserPromptMessage.js +5 -7
  57. package/lib/types/components/messages/useImageInspection.d.ts.map +1 -1
  58. package/lib/types/components/messages/useImageInspection.js +19 -9
  59. package/lib/types/components/promptDraftCache.d.ts +102 -0
  60. package/lib/types/components/promptDraftCache.d.ts.map +1 -0
  61. package/lib/types/components/promptDraftCache.js +76 -0
  62. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +17 -1
  63. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  64. package/lib/types/components/questions/AskUserQuestionPanel.js +40 -7
  65. package/lib/types/components/questions/QuestionMinimizedBar.d.ts +12 -0
  66. package/lib/types/components/questions/QuestionMinimizedBar.d.ts.map +1 -0
  67. package/lib/types/components/questions/QuestionMinimizedBar.js +43 -0
  68. package/lib/types/components/sessions/SessionListRow.d.ts +23 -1
  69. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  70. package/lib/types/components/sessions/SessionListRow.js +52 -5
  71. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +49 -0
  72. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -0
  73. package/lib/types/components/workspaces/HomeWorkspaceRow.js +50 -0
  74. package/lib/types/dsh-adapter/bundled-presets.d.ts +10 -0
  75. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -0
  76. package/lib/types/dsh-adapter/bundled-presets.js +80 -0
  77. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +1 -1
  78. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  79. package/lib/types/dsh-adapter/channel/action-readiness.js +3 -0
  80. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts +1 -1
  81. package/lib/types/dsh-adapter/channel/agent-view-projection.d.ts.map +1 -1
  82. package/lib/types/dsh-adapter/channel/agent-view-projection.js +19 -2
  83. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  84. package/lib/types/dsh-adapter/channel/background-action.js +52 -29
  85. package/lib/types/dsh-adapter/channel/binding.d.ts +19 -2
  86. package/lib/types/dsh-adapter/channel/binding.d.ts.map +1 -1
  87. package/lib/types/dsh-adapter/channel/binding.js +78 -18
  88. package/lib/types/dsh-adapter/channel/composer-images.d.ts +5 -0
  89. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  90. package/lib/types/dsh-adapter/channel/composer-images.js +105 -6
  91. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts +8 -0
  92. package/lib/types/dsh-adapter/channel/context-bookkeeping.d.ts.map +1 -1
  93. package/lib/types/dsh-adapter/channel/context-bookkeeping.js +3 -2
  94. package/lib/types/dsh-adapter/channel/ide-selection.d.ts +58 -0
  95. package/lib/types/dsh-adapter/channel/ide-selection.d.ts.map +1 -0
  96. package/lib/types/dsh-adapter/channel/ide-selection.js +226 -0
  97. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +6 -1
  98. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  99. package/lib/types/dsh-adapter/channel/input-delivery.js +9 -1
  100. package/lib/types/dsh-adapter/channel/local-actions.d.ts +3 -19
  101. package/lib/types/dsh-adapter/channel/local-actions.d.ts.map +1 -1
  102. package/lib/types/dsh-adapter/channel/local-actions.js +2 -1
  103. package/lib/types/dsh-adapter/channel/model-actions.d.ts +4 -1
  104. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  105. package/lib/types/dsh-adapter/channel/model-actions.js +82 -6
  106. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  107. package/lib/types/dsh-adapter/channel/model-switch.js +49 -31
  108. package/lib/types/dsh-adapter/channel/projection.d.ts +4 -0
  109. package/lib/types/dsh-adapter/channel/projection.d.ts.map +1 -1
  110. package/lib/types/dsh-adapter/channel/projection.js +29 -19
  111. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  112. package/lib/types/dsh-adapter/channel/reports.js +4 -12
  113. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  114. package/lib/types/dsh-adapter/channel/session-fork.js +26 -2
  115. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts +3 -0
  116. package/lib/types/dsh-adapter/channel/session-live-adoption.d.ts.map +1 -1
  117. package/lib/types/dsh-adapter/channel/session-live-adoption.js +1 -0
  118. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +10 -0
  119. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  120. package/lib/types/dsh-adapter/channel/session-metadata.js +2 -2
  121. package/lib/types/dsh-adapter/channel/session-resume.d.ts +11 -2
  122. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  123. package/lib/types/dsh-adapter/channel/session-resume.js +291 -94
  124. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  125. package/lib/types/dsh-adapter/channel/session-rewind.js +34 -16
  126. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  127. package/lib/types/dsh-adapter/channel/session-tree-actions.js +19 -3
  128. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  129. package/lib/types/dsh-adapter/channel/settings-host.js +4 -3
  130. package/lib/types/dsh-adapter/channel/state.d.ts +1 -1
  131. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  132. package/lib/types/dsh-adapter/channel/state.js +1 -1
  133. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  134. package/lib/types/dsh-adapter/channel/transcript.js +6 -12
  135. package/lib/types/dsh-adapter/channel/types.d.ts +6 -1
  136. package/lib/types/dsh-adapter/channel/types.d.ts.map +1 -1
  137. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts +4 -1
  138. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  139. package/lib/types/dsh-adapter/channel/workspace-actions.js +41 -1
  140. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  141. package/lib/types/dsh-adapter/channel.js +67 -6
  142. package/lib/types/dsh-adapter/compat/messages.d.ts +15 -0
  143. package/lib/types/dsh-adapter/compat/messages.d.ts.map +1 -0
  144. package/lib/types/dsh-adapter/compat/messages.js +15 -0
  145. package/lib/types/dsh-adapter/compat/settings.d.ts +33 -0
  146. package/lib/types/dsh-adapter/compat/settings.d.ts.map +1 -0
  147. package/lib/types/dsh-adapter/compat/settings.js +43 -0
  148. package/lib/types/dsh-adapter/compat/shell.d.ts +25 -0
  149. package/lib/types/dsh-adapter/compat/shell.d.ts.map +1 -0
  150. package/lib/types/dsh-adapter/compat/shell.js +11 -0
  151. package/lib/types/dsh-adapter/contract.d.ts +4 -4
  152. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  153. package/lib/types/dsh-adapter/contract.js +15 -6
  154. package/lib/types/dsh-adapter/ide-channel.d.ts +224 -0
  155. package/lib/types/dsh-adapter/ide-channel.d.ts.map +1 -0
  156. package/lib/types/dsh-adapter/ide-channel.js +556 -0
  157. package/lib/types/dsh-adapter/index.d.ts +18 -3
  158. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  159. package/lib/types/dsh-adapter/index.js +32 -5
  160. package/lib/types/dsh-adapter/plugin.d.ts +5 -2
  161. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  162. package/lib/types/dsh-adapter/plugin.js +173 -42
  163. package/lib/types/dsh-adapter/preset-resolution.d.ts +8 -0
  164. package/lib/types/dsh-adapter/preset-resolution.d.ts.map +1 -1
  165. package/lib/types/dsh-adapter/preset-resolution.js +10 -0
  166. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts +53 -0
  167. package/lib/types/dsh-adapter/session-mount-heartbeat.d.ts.map +1 -0
  168. package/lib/types/dsh-adapter/session-mount-heartbeat.js +100 -0
  169. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -1
  170. package/lib/types/dsh-adapter/sessionTree.js +3 -2
  171. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  172. package/lib/types/dsh-adapter/subagents.js +6 -5
  173. package/lib/types/dsh-adapter/workspaces.d.ts +46 -3
  174. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  175. package/lib/types/dsh-adapter/workspaces.js +116 -0
  176. package/lib/types/effortPrefs.d.ts +15 -3
  177. package/lib/types/effortPrefs.d.ts.map +1 -1
  178. package/lib/types/effortPrefs.js +37 -3
  179. package/lib/types/homePrefs.d.ts +15 -0
  180. package/lib/types/homePrefs.d.ts.map +1 -0
  181. package/lib/types/homePrefs.js +53 -0
  182. package/lib/types/i18n.d.ts +257 -296
  183. package/lib/types/i18n.d.ts.map +1 -1
  184. package/lib/types/i18n.js +78 -84
  185. package/lib/types/ink/colorize.d.ts +10 -0
  186. package/lib/types/ink/colorize.d.ts.map +1 -1
  187. package/lib/types/ink/colorize.js +21 -0
  188. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  189. package/lib/types/ink/components/ScrollBox.js +12 -1
  190. package/lib/types/ink/dom.d.ts +1 -0
  191. package/lib/types/ink/dom.d.ts.map +1 -1
  192. package/lib/types/ink/hooks/use-copy-on-select.d.ts +5 -1
  193. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  194. package/lib/types/ink/hooks/use-copy-on-select.js +15 -1
  195. package/lib/types/ink/ink.d.ts.map +1 -1
  196. package/lib/types/ink/ink.js +36 -1
  197. package/lib/types/ink/output.d.ts +10 -1
  198. package/lib/types/ink/output.d.ts.map +1 -1
  199. package/lib/types/ink/output.js +109 -17
  200. package/lib/types/ink/render-border.d.ts +6 -1
  201. package/lib/types/ink/render-border.d.ts.map +1 -1
  202. package/lib/types/ink/render-border.js +25 -9
  203. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  204. package/lib/types/ink/render-node-to-output.js +48 -6
  205. package/lib/types/ink/renderer.d.ts +6 -0
  206. package/lib/types/ink/renderer.d.ts.map +1 -1
  207. package/lib/types/ink/renderer.js +2 -1
  208. package/lib/types/ink/selection.d.ts +60 -0
  209. package/lib/types/ink/selection.d.ts.map +1 -1
  210. package/lib/types/ink/selection.js +129 -0
  211. package/lib/types/ink/sixel-graphics.d.ts +19 -1
  212. package/lib/types/ink/sixel-graphics.d.ts.map +1 -1
  213. package/lib/types/ink/sixel-graphics.js +124 -12
  214. package/lib/types/ink/terminal-image.d.ts +18 -1
  215. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  216. package/lib/types/ink/terminal.d.ts +17 -0
  217. package/lib/types/ink/terminal.d.ts.map +1 -1
  218. package/lib/types/ink/terminal.js +20 -1
  219. package/lib/types/screens/Chat.d.ts +17 -1
  220. package/lib/types/screens/Chat.d.ts.map +1 -1
  221. package/lib/types/screens/Chat.js +284 -72
  222. package/lib/types/screens/SessionSupervisor.d.ts +84 -0
  223. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -0
  224. package/lib/types/screens/SessionSupervisor.js +350 -0
  225. package/lib/types/screens/SessionTree.js +2 -2
  226. package/lib/types/screens/Settings.d.ts.map +1 -1
  227. package/lib/types/screens/Settings.js +32 -9
  228. package/lib/types/screens/StatusLine.d.ts +13 -0
  229. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  230. package/lib/types/screens/StatusLine.js +36 -17
  231. package/lib/types/screens/StatusMetrics.d.ts +79 -22
  232. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  233. package/lib/types/screens/StatusMetrics.js +153 -88
  234. package/lib/types/screens/sessionSupervisor/model.d.ts +83 -0
  235. package/lib/types/screens/sessionSupervisor/model.d.ts.map +1 -0
  236. package/lib/types/screens/sessionSupervisor/model.js +69 -0
  237. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +120 -0
  238. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -0
  239. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +593 -0
  240. package/lib/types/sessionMounts.d.ts +272 -0
  241. package/lib/types/sessionMounts.d.ts.map +1 -0
  242. package/lib/types/sessionMounts.js +706 -0
  243. package/lib/types/sessions/format.d.ts.map +1 -1
  244. package/lib/types/sessions/format.js +4 -1
  245. package/lib/types/sessions/resumeFailure.d.ts +38 -0
  246. package/lib/types/sessions/resumeFailure.d.ts.map +1 -0
  247. package/lib/types/sessions/resumeFailure.js +58 -0
  248. package/lib/types/terminal-utils/mermaid.d.ts +44 -0
  249. package/lib/types/terminal-utils/mermaid.d.ts.map +1 -0
  250. package/lib/types/terminal-utils/mermaid.js +83 -0
  251. package/lib/types/tips.d.ts.map +1 -1
  252. package/lib/types/tips.js +175 -19
  253. package/lib/types/tuiDisplayPrefs.d.ts +7 -9
  254. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  255. package/lib/types/tuiDisplayPrefs.js +31 -28
  256. package/lib/types/update.d.ts +61 -0
  257. package/lib/types/update.d.ts.map +1 -1
  258. package/lib/types/update.js +194 -0
  259. package/lib/types/utils/imageResize.d.ts +90 -0
  260. package/lib/types/utils/imageResize.d.ts.map +1 -0
  261. package/lib/types/utils/imageResize.js +272 -0
  262. package/lib/types/utils/keymap.d.ts +5 -1
  263. package/lib/types/utils/keymap.d.ts.map +1 -1
  264. package/lib/types/utils/keymap.js +23 -0
  265. package/lib/types/workspaces.d.ts +2 -1
  266. package/lib/types/workspaces.d.ts.map +1 -1
  267. package/lib/types/workspaces.js +1 -1
  268. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +10 -10
  269. package/package.json +120 -103
  270. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  271. package/presets/liangshen/agent.cordis.yml +15 -0
  272. package/lib/types/screens/AgentView.d.ts +0 -23
  273. package/lib/types/screens/AgentView.d.ts.map +0 -1
  274. package/lib/types/screens/AgentView.js +0 -546
  275. package/lib/types/screens/SessionBrowser.d.ts +0 -42
  276. package/lib/types/screens/SessionBrowser.d.ts.map +0 -1
  277. package/lib/types/screens/SessionBrowser.js +0 -911
@@ -0,0 +1,76 @@
1
+ /**
2
+ * The composer draft that a screen hands to its owner on the way out.
3
+ *
4
+ * Every screen that REPLACES the conversation (`/resume`'s session screen, the
5
+ * session tree, `/settings`, the jobs panel, the trajectory scene) is an early
6
+ * return in `Chat`, so `PromptInput` is unmounted while the user is away and
7
+ * its text, caret and staged-image bindings live in component state. The owner
8
+ * therefore holds one slot of this type and the composer fills it as it
9
+ * unmounts; on the way back it is read, checked and consumed.
10
+ *
11
+ * Deliberately dependency-free (it does not import `PromptInput`, which would
12
+ * be a cycle), so the component and the headless regressions can both assert
13
+ * against the same rules.
14
+ *
15
+ * The snapshot shape, the image-liveness rule and the carried edit state
16
+ * (fold block, fullscreen editor, vim mode) are taken from PR #847
17
+ * (`promptDraftCache.ts`, branch `fix/composer-draft-retention`); the owner
18
+ * check here adds the agent id, because a generation alone cannot tell two
19
+ * channels with a fixed generation apart.
20
+ */
21
+ /**
22
+ * The current binding generation, mirroring the channel expression: the agent
23
+ * binding generation advances on every agent replacement (`/resume` to another
24
+ * session, `/new`, `/bg`, attach, rewind) and wins; a simplified or embedded
25
+ * channel without it falls back to the staged-image generation, and 0 when it
26
+ * has neither.
27
+ * @param source - The channel, or anything carrying the same two members.
28
+ * @returns The generation the snapshot is fenced against.
29
+ */
30
+ export function resolveBindingGeneration(source) {
31
+ return source.agentBindingGeneration ?? source.stagedImageGeneration?.() ?? 0;
32
+ }
33
+ /**
34
+ * Whether a stored draft belongs to the conversation in front of us now.
35
+ *
36
+ * Both halves are required. The generation catches a replaced binding; the
37
+ * agent id catches the case a generation cannot describe — a test or embedded
38
+ * channel whose generation is a constant, where a stale draft would otherwise
39
+ * be adopted by a different conversation.
40
+ * @param snapshot - The stored draft, if any.
41
+ * @param ownerAgentId - Agent id of the composer that would restore it.
42
+ * @param generation - Current binding generation.
43
+ */
44
+ export function isUsableDraftSnapshot(snapshot, ownerAgentId, generation) {
45
+ return snapshot !== null
46
+ && snapshot !== undefined
47
+ && snapshot.ownerAgentId === ownerAgentId
48
+ && snapshot.bindingGeneration === generation;
49
+ }
50
+ /**
51
+ * Drop the image bindings whose capability is gone.
52
+ *
53
+ * A visible `[Image #N]` is only a label; what makes it an attachment is the
54
+ * stageId behind it. A session switch (or a discard) revokes the capability and
55
+ * the label must not be re-bound by reading the number back out of the text.
56
+ * Entries that fail the check are dropped silently and the caller keeps the
57
+ * visible token as ordinary text, which is the behaviour an edit already has.
58
+ * @param images - Stored pairs.
59
+ * @param hasStagedImage - Channel capability probe; absent means "none live".
60
+ */
61
+ export function filterLiveImageBindings(images, hasStagedImage) {
62
+ if (images === null || images === undefined)
63
+ return [];
64
+ const live = [];
65
+ for (const entry of images) {
66
+ if (!Array.isArray(entry) || entry.length !== 2)
67
+ continue;
68
+ const [token, stageId] = entry;
69
+ if (typeof token !== 'string' || typeof stageId !== 'string')
70
+ continue;
71
+ if (hasStagedImage?.(stageId) !== true)
72
+ continue;
73
+ live.push([token, stageId]);
74
+ }
75
+ return live;
76
+ }
@@ -72,6 +72,22 @@ export type AskUserQuestionPanelProps = {
72
72
  * verification injects a fake so paste outcomes are deterministic.
73
73
  */
74
74
  readonly readClipboardOverride?: () => Promise<ClipboardRead>;
75
+ /**
76
+ * Manual collapse (question panel fold, default Ctrl+K): while
77
+ * collapsed the panel STAYS MOUNTED — draft refs survive, the fold
78
+ * branch sits after every hook — and renders the two-line minimized
79
+ * bar instead, with editing/caret layers gated off. Its own input
80
+ * handler keeps Esc/Ctrl+C as "expand", never cancel, while folded.
81
+ */
82
+ readonly collapsed?: boolean;
83
+ /** Expand back from the minimized bar (fold key / Esc / mouse click). */
84
+ readonly onExpand?: () => void;
85
+ /** Fold from the expanded panel (fold key / header-row click). */
86
+ readonly onToggleFold?: () => void;
87
+ /** True when mouse tracking is on: mouse-only affordances (the
88
+ * multi-select submit row) render only then — inline hosts have no
89
+ * pointer, so an unclickable row would just eat a line. */
90
+ readonly fullscreen?: boolean;
75
91
  };
76
- export declare function AskUserQuestionPanel({ question, position, total, answered, initialDraft, onAnswer, onCancel, onBack, readClipboardOverride, }: AskUserQuestionPanelProps): React.ReactNode;
92
+ export declare function AskUserQuestionPanel({ question, position, total, answered, initialDraft, onAnswer, onCancel, onBack, readClipboardOverride, collapsed, onExpand, onToggleFold, fullscreen, }: AskUserQuestionPanelProps): React.ReactNode;
77
93
  //# sourceMappingURL=AskUserQuestionPanel.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AskUserQuestionPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/AskUserQuestionPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAKtF,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAkB5E,MAAM,MAAM,yBAAyB,GAAG;IACtC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;QAC9B;;oEAE4D;QAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;QAClC;;qEAE6D;QAC7D,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QAC5C;0EACkE;QAClE,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAC7E,CAAA;IACD,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACzD,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAChD;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;CAC9D,CAAA;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,qBAAqB,GACtB,EAAE,yBAAyB,GAAG,KAAK,CAAC,SAAS,CA+iB7C"}
1
+ {"version":3,"file":"AskUserQuestionPanel.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/AskUserQuestionPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAA;AAMzB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAMtF,OAAO,EAAiB,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAA;AAkB5E,MAAM,MAAM,yBAAyB,GAAG;IACtC,gEAAgE;IAChE,QAAQ,CAAC,QAAQ,EAAE;QACjB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,aAAa,CAAC;YAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;QAC3F,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;QAC9B;;oEAE4D;QAC5D,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAA;QAClC;;qEAE6D;QAC7D,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QAC5C;0EACkE;QAClE,QAAQ,CAAC,MAAM,CAAC,EAAE;YAAE,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;YAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;SAAE,CAAA;KAC7E,CAAA;IACD,2DAA2D;IAC3D,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,sDAAsD;IACtD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,iDAAiD;IACjD,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,CAAC,SAAS,EAAE,iBAAiB,KAAK,IAAI,CAAA;IACzD,iEAAiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;IAC7B,mEAAmE;IACnE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAA;IAChD;;;;OAIG;IACH,QAAQ,CAAC,qBAAqB,CAAC,EAAE,MAAM,OAAO,CAAC,aAAa,CAAC,CAAA;IAC7D;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAA;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B,kEAAkE;IAClE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,IAAI,CAAA;IAClC;;gEAE4D;IAC5D,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAC9B,CAAA;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,YAAY,EACZ,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,qBAAqB,EACrB,SAAiB,EACjB,QAAQ,EACR,YAAY,EACZ,UAAkB,GACnB,EAAE,yBAAyB,GAAG,KAAK,CAAC,SAAS,CAsmB7C"}
@@ -32,8 +32,9 @@ import { useDeclaredCursor } from '../../ink/hooks/use-declared-cursor.js';
32
32
  import { Divider } from '../design-system/Divider.js';
33
33
  import { POINTER } from '../../terminal-utils/figures.js';
34
34
  import { PlanReviewPanel } from './PlanReviewPanel.js';
35
+ import { QuestionMinimizedBar } from './QuestionMinimizedBar.js';
35
36
  import { isPlainReturnInput } from '../../utils/modifiers.js';
36
- import { actionMatches } from '../../utils/keymap.js';
37
+ import { actionMatches, effectiveComboDisplay } from '../../utils/keymap.js';
37
38
  import { flattenPasteInline } from '../../dsh-adapter/sanitize.js';
38
39
  import { readClipboard } from '../../utils/clipboard.js';
39
40
  import { listWindow } from '../listWindow.js';
@@ -50,7 +51,7 @@ const PENCIL = '✎';
50
51
  * PlanReviewPanel (the two panels share the same input contract).
51
52
  */
52
53
  const ANSWER_PASTE_MAX_POINTS = 8000;
53
- export function AskUserQuestionPanel({ question, position, total, answered, initialDraft, onAnswer, onCancel, onBack, readClipboardOverride, }) {
54
+ export function AskUserQuestionPanel({ question, position, total, answered, initialDraft, onAnswer, onCancel, onBack, readClipboardOverride, collapsed = false, onExpand, onToggleFold, fullscreen = false, }) {
54
55
  // Plan-mode's exit_plan_mode ask carries a presentation intent: render
55
56
  // the plan decision card instead of the generic questionnaire. The
56
57
  // branch precedes every hook so hook order stays stable per remount key.
@@ -134,7 +135,7 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
134
135
  // three visual variants): its nodeCache rect IS the caret cell, so (0, 0)
135
136
  // stays exact under CJK widths and line wrapping without any
136
137
  // layout-affecting wrapper Box.
137
- const caretRef = useDeclaredCursor({ line: 0, column: 0, active: !hideCustomInput });
138
+ const caretRef = useDeclaredCursor({ line: 0, column: 0, active: !hideCustomInput && !collapsed });
138
139
  const moveFocus = (delta) => {
139
140
  if (rowCount <= 1)
140
141
  return;
@@ -277,7 +278,22 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
277
278
  ...(textRef.current !== '' ? { custom: textRef.current } : {}),
278
279
  };
279
280
  };
280
- useInput((input, key) => {
281
+ useInput((input, key, event) => {
282
+ // The mounted panel owns folding, just like answering: hidden asks
283
+ // cannot steal approval/dialog keys, and an interrupting questionnaire
284
+ // still works when Chat yields to an underlying screen's open flag.
285
+ if (onToggleFold !== undefined && actionMatches('questionFold', input, key)) {
286
+ onToggleFold();
287
+ event.stopImmediatePropagation();
288
+ return;
289
+ }
290
+ if (collapsed) {
291
+ if (key.escape || (key.ctrl && input === 'c')) {
292
+ onExpand?.();
293
+ event.stopImmediatePropagation();
294
+ }
295
+ return;
296
+ }
281
297
  if (key.ctrl && input === 'c') {
282
298
  onCancel();
283
299
  return;
@@ -410,9 +426,14 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
410
426
  if (!multiSelect)
411
427
  setAttached(options[focusIndex]?.label ?? null);
412
428
  }
413
- }, { isActive: true });
429
+ });
414
430
  const remaining = total - answered;
431
+ /** Fold shortcut label for the hint row (follows /settings remaps). */
432
+ const foldCombo = effectiveComboDisplay('questionFold');
415
433
  const headerTitle = ` ${t('question-header-progress', { position, total, remaining: remaining > 1 ? t('question-remaining-more', { n: remaining }) : '' })} `;
434
+ /** First line of the question body for the minimized bar (whitespace
435
+ * flattened the same way windowedOptions flattens labels). */
436
+ const questionText = question.question.split('\n')[0]?.replace(/\s+/gu, ' ').trim() ?? '';
416
437
  // The caret counts code points (see the module header), so the caret
417
438
  // char and the visual split index into the point array — never raw
418
439
  // UTF-16 offsets, which could land inside a surrogate pair.
@@ -450,6 +471,10 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
450
471
  onAnswer({ selected: [label], ...(text !== '' ? { custom: text } : {}) });
451
472
  };
452
473
  const [hoverIndex, setHoverIndex] = React.useState(-1);
474
+ /** Header row hover — the row doubles as the mouse fold/unfold affordance. */
475
+ const [headerHovered, setHeaderHovered] = React.useState(false);
476
+ /** Mouse submit row hover (multi-select: clicking submits the checked set). */
477
+ const [submitHovered, setSubmitHovered] = React.useState(false);
453
478
  const renderInputRow = () => (_jsxs(Box, { flexDirection: "row", marginTop: inputFocused ? 1 : 0, onClick: focusInputRow, onMouseEnter: () => setHoverIndex(options.length), onMouseLeave: () => setHoverIndex(current => (current === options.length ? -1 : current)), backgroundColor: hoverIndex === options.length && !inputFocused ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: inputFocused ? 'accent' : undefined, bold: inputFocused, children: inputFocused ? POINTER : ' ' }) }), _jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: inputFocused ? 'accent' : 'suggestion', children: PENCIL }) }), _jsxs(Box, { flexDirection: "row", marginLeft: 1, children: [_jsx(Text, { bold: inputFocused, color: inputFocused ? 'accent' : 'suggestion', children: t('question-custom-tab') }), attached !== null && (_jsx(Text, { color: "suggestion", children: t('question-attached-label', { label: attached }) })), _jsx(Text, { dimColor: true, children: "\uFF1A" }), customText === '' && !inputFocused ? (_jsx(Text, { ref: caretRef, dimColor: true, children: t('question-direct-input') })) : (_jsxs(_Fragment, { children: [_jsx(Text, { wrap: "wrap", children: textPoints.slice(0, customCursor).join('') }), inputFocused
454
479
  ? _jsx(Text, { ref: caretRef, inverse: true, children: cursorChar })
455
480
  : _jsx(Text, { ref: caretRef, color: "suggestion", children: "\u258F" }), _jsx(Text, { wrap: "wrap", children: textPoints.slice(inputFocused ? customCursor + 1 : customCursor).join('') })] }))] })] }));
@@ -469,7 +494,7 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
469
494
  ? option.description?.replace(/\s+/gu, ' ').trim()
470
495
  : option.description;
471
496
  return (_jsxs(Box, { flexDirection: "row", marginTop: !windowedOptions && focused ? 1 : 0, onClick: () => clickOption(absoluteIndex), onMouseEnter: () => setHoverIndex(absoluteIndex), onMouseLeave: () => setHoverIndex(current => (current === absoluteIndex ? -1 : current)), backgroundColor: hoverIndex === absoluteIndex && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: focused ? 'accent' : undefined, bold: focused, children: pointer }) }), _jsx(Box, { width: 1, flexShrink: 0, children: _jsx(Text, { color: focused ? 'accent' : undefined, bold: selected, children: selected ? (multiSelect ? CHECKED : '●') : UNCHECKED }) }), _jsxs(Box, { flexDirection: "column", marginLeft: 1, children: [_jsx(Text, { bold: focused || selected, color: focused ? 'accent' : undefined, wrap: windowedOptions ? 'truncate' : 'wrap', children: label }), description !== undefined && (_jsx(Text, { dimColor: true, wrap: windowedOptions ? 'truncate' : 'wrap', children: description }))] })] }, `${absoluteIndex}:${option.label}`));
472
- }), hideCustomInput ? null : renderInputRow()] }));
497
+ }), hideCustomInput ? null : renderInputRow(), multiSelect && fullscreen && (checked.size > 0 || textRef.current !== '') && (_jsxs(Box, { flexDirection: "row", height: 1, marginTop: 1, onClick: submitOptions, onMouseEnter: () => setSubmitHovered(true), onMouseLeave: () => setSubmitHovered(false), backgroundColor: submitHovered ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { color: "accent", children: "\u2713 " }), _jsx(Text, { dimColor: true, children: t('question-submit-selection') })] }))] }));
473
498
  const hintParts = inputFocused
474
499
  ? [
475
500
  t('question-hint-type'),
@@ -479,6 +504,7 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
479
504
  onBack === undefined ? t('question-hint-esc') : t('question-hint-previous'),
480
505
  ...(onBack === undefined ? [] : [t('question-hint-cancel')]),
481
506
  ...(multiSelect && checked.size > 0 ? [t('question-hint-selected', { n: checked.size })] : []),
507
+ t('question-fold-hint', { combo: foldCombo }),
482
508
  ]
483
509
  : [
484
510
  t('question-hint-select'),
@@ -488,6 +514,13 @@ export function AskUserQuestionPanel({ question, position, total, answered, init
488
514
  onBack === undefined ? t('question-hint-esc') : t('question-hint-previous'),
489
515
  ...(onBack === undefined ? [] : [t('question-hint-cancel')]),
490
516
  ...(multiSelect && checked.size > 0 ? [t('question-hint-selected', { n: checked.size })] : []),
517
+ t('question-fold-hint', { combo: foldCombo }),
491
518
  ];
492
- return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Divider, { color: "permission", title: headerTitle }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [question.header !== undefined && (_jsxs(Text, { color: "suggestion", bold: true, children: ["\u25C8 ", question.header] })), _jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: question.detail.split('\n').map((line, index) => (_jsx(Text, { dimColor: true, italic: true, wrap: "wrap", children: line }, index))) }))] }), renderOptions(), error !== null && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "error", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: hintParts.join(' · ') }) })] }));
519
+ // Folded: render only the minimized bar. This branch sits AFTER every
520
+ // hook (and after the intent early-return), so folding never reorders
521
+ // hooks and the draft refs above stay alive for the expand.
522
+ if (collapsed) {
523
+ return _jsx(QuestionMinimizedBar, { progress: headerTitle, questionText: questionText, onExpand: onExpand ?? (() => { }) });
524
+ }
525
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsx(Box, { flexDirection: "column", onClick: onToggleFold, onMouseEnter: () => setHeaderHovered(true), onMouseLeave: () => setHeaderHovered(false), backgroundColor: headerHovered ? 'userMessageBackgroundHover' : undefined, children: _jsx(Divider, { color: "permission", title: `▾${headerTitle}` }) }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [question.header !== undefined && (_jsxs(Text, { color: "suggestion", bold: true, children: ["\u25C8 ", question.header] })), _jsx(Text, { bold: true, wrap: "wrap", children: question.question }), question.detail !== undefined && (_jsx(Box, { flexDirection: "column", marginTop: 1, children: question.detail.split('\n').map((line, index) => (_jsx(Text, { dimColor: true, italic: true, wrap: "wrap", children: line }, index))) }))] }), renderOptions(), error !== null && (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "error", children: error }) })), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: hintParts.join(' · ') }) })] }));
493
526
  }
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ export type QuestionMinimizedBarProps = {
3
+ /** Progress title, same text as the expanded panel's Divider header
4
+ * (e.g. ` 📋 提问 · 第 2/5 题 · 还剩 3 题 `). */
5
+ readonly progress: string;
6
+ /** First line of the question body, truncated by the layout. */
7
+ readonly questionText: string;
8
+ /** Expand back to the full panel (Ctrl+K / Esc / click). */
9
+ readonly onExpand: () => void;
10
+ };
11
+ export declare function QuestionMinimizedBar({ progress, questionText, onExpand, }: QuestionMinimizedBarProps): React.ReactNode;
12
+ //# sourceMappingURL=QuestionMinimizedBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"QuestionMinimizedBar.d.ts","sourceRoot":"","sources":["../../../../src/components/questions/QuestionMinimizedBar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AA0BzB,MAAM,MAAM,yBAAyB,GAAG;IACtC;+CAC2C;IAC3C,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,gEAAgE;IAChE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAA;CAC9B,CAAA;AAED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,YAAY,EACZ,QAAQ,GACT,EAAE,yBAAyB,GAAG,KAAK,CAAC,SAAS,CAwC7C"}
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from '../../ui.js';
4
+ import { t } from '../../i18n.js';
5
+ import { effectiveComboDisplay } from '../../utils/keymap.js';
6
+ /**
7
+ * 提问面板挂起条 — the two-line stand-in for a folded questionnaire
8
+ * (manual collapse; the ask stays parked and the panel stays mounted, so
9
+ * drafts survive).
10
+ *
11
+ * Line 1 mirrors the GoalTodoPanel fold-header language: a dim `▸` (folded
12
+ * state), the same progress title as the expanded header, then the first
13
+ * line of the question truncated to whatever width remains. The whole row
14
+ * is clickable (fullscreen mouse) and hover-highlighted to expand.
15
+ *
16
+ * Line 2 is the permanent "answer pending" affordance — a blinking pause
17
+ * glyph (`⏸` toggled dim/bright on a local interval that lives only while
18
+ * the bar is mounted) plus the waiting text and the fold key hint. The key
19
+ * label follows keymap remaps via effectiveComboString, like the /settings
20
+ * shortcut rows.
21
+ *
22
+ * The component is deliberately dumb: the parent formats the strings, so
23
+ * the bar owns only its local hover/blink state.
24
+ */
25
+ const BLINK_MS = 500;
26
+ export function QuestionMinimizedBar({ progress, questionText, onExpand, }) {
27
+ const [blinkOn, setBlinkOn] = React.useState(true);
28
+ const [hovered, setHovered] = React.useState(false);
29
+ // The pause glyph blinks by APPEARING and DISAPPEARING (a visible→gone
30
+ // beat reads as a true blink; dim↔normal is too subtle). The off phase
31
+ // must occupy exactly the glyph's width or the waiting text shifts left
32
+ // and right every beat: '⏸ ' measures 2 columns (⏸ is a narrow 1-col
33
+ // glyph here), so it is replaced by two spaces. Blink only while the
34
+ // bar exists; unmounting clears the interval.
35
+ React.useEffect(() => {
36
+ const id = setInterval(() => setBlinkOn(previous => !previous), BLINK_MS);
37
+ return () => clearInterval(id);
38
+ }, []);
39
+ const waiting = `${t('question-fold-waiting')} — ${t('question-fold-expand', {
40
+ combo: effectiveComboDisplay('questionFold'),
41
+ })}`;
42
+ return (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingLeft: 2, paddingRight: 2, width: "100%", children: [_jsxs(Box, { flexDirection: "row", height: 1, onClick: onExpand, onMouseEnter: () => setHovered(true), onMouseLeave: () => setHovered(false), backgroundColor: hovered ? 'userMessageBackgroundHover' : undefined, children: [_jsx(Text, { dimColor: true, children: '▸ ' }), _jsx(Text, { dimColor: true, children: progress }), _jsx(Box, { flexGrow: 1, flexShrink: 1, marginLeft: 1, children: _jsx(Text, { wrap: "truncate", children: questionText }) })] }), _jsxs(Box, { flexDirection: "row", height: 1, children: [_jsx(Text, { children: blinkOn ? '⏸ ' : ' ' }), _jsx(Text, { dimColor: true, wrap: "truncate", children: waiting })] })] }));
43
+ }
@@ -2,6 +2,7 @@ import React from 'react';
2
2
  import type { ClickEvent } from '../../ink/events/click-event.js';
3
3
  import type { ContextMenuEvent } from '../../ink/events/context-menu-event.js';
4
4
  import type { SessionSummary } from '../../dsh-adapter/sessions/index.js';
5
+ import type { AgentViewStatus } from '../../adapter/ports/channel-view.js';
5
6
  /**
6
7
  * One session in the browser's list: a title line and a metadata line.
7
8
  *
@@ -15,7 +16,7 @@ import type { SessionSummary } from '../../dsh-adapter/sessions/index.js';
15
16
  * stay exactly two lines at every terminal width, and a row that wraps
16
17
  * destroys the alignment that lets the eye scan a list at all.
17
18
  */
18
- export declare function SessionListRow({ session, width, depth, focused, pinned, now, onClick, onContextMenu, onTogglePin, }: {
19
+ export declare function SessionListRow({ session, width, depth, focused, pinned, now, onClick, onContextMenu, onTogglePin, liveStatus, current, occupiedPid, spinner, }: {
19
20
  session: SessionSummary;
20
21
  /** Columns available to the row, indentation included. */
21
22
  width: number;
@@ -32,5 +33,26 @@ export declare function SessionListRow({ session, width, depth, focused, pinned,
32
33
  onContextMenu?(event: ContextMenuEvent): void;
33
34
  /** 点击行内 ★/☆(fullscreen):切换固定状态,不冒泡成"打开会话"。 */
34
35
  onTogglePin?(): void;
36
+ /**
37
+ * Live status of an agent THIS terminal is running, when there is one.
38
+ * `working` animates instead of holding a static glyph, so a busy session
39
+ * is legible at a glance — the same vocabulary the session overview uses,
40
+ * which is what lets the two surfaces read as one feature.
41
+ */
42
+ liveStatus?: AgentViewStatus;
43
+ /** True when this is the session the terminal is attached to. */
44
+ current?: boolean;
45
+ /**
46
+ * The pid of ANOTHER TUI terminal holding this session, when one does. The
47
+ * row turns red and says so: the session stays visible but cannot be
48
+ * entered, which is the honest presentation of a claim this process must
49
+ * not break. Undefined means free, or already ours.
50
+ */
51
+ occupiedPid?: number;
52
+ /** Shared animation clock for the working glyph. */
53
+ spinner?: {
54
+ frame: number;
55
+ time: number;
56
+ };
35
57
  }): React.ReactNode;
36
58
  //# sourceMappingURL=SessionListRow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SessionListRow.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/SessionListRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAA;AAU9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AAEzE;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,MAAM,EACN,GAAG,EACH,OAAO,EACP,aAAa,EACb,WAAW,GACZ,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,sEAAsE;IACtE,MAAM,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,4CAA4C;IAC5C,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC,gDAAgD;IAChD,aAAa,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC7C,8CAA8C;IAC9C,WAAW,CAAC,IAAI,IAAI,CAAA;CACrB,GAAG,KAAK,CAAC,SAAS,CA8ElB"}
1
+ {"version":3,"file":"SessionListRow.d.ts","sourceRoot":"","sources":["../../../../src/components/sessions/SessionListRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAA;AAY9E,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qCAAqC,CAAA;AACzE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AA0B1E;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,MAAM,EACN,GAAG,EACH,OAAO,EACP,aAAa,EACb,WAAW,EACX,UAAU,EACV,OAAO,EACP,WAAW,EACX,OAAO,GACR,EAAE;IACD,OAAO,EAAE,cAAc,CAAA;IACvB,0DAA0D;IAC1D,KAAK,EAAE,MAAM,CAAA;IACb,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,OAAO,CAAA;IAChB,sEAAsE;IACtE,MAAM,EAAE,OAAO,CAAA;IACf,iEAAiE;IACjE,GAAG,EAAE,MAAM,CAAA;IACX,4CAA4C;IAC5C,OAAO,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IACjC,gDAAgD;IAChD,aAAa,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;IAC7C,8CAA8C;IAC9C,WAAW,CAAC,IAAI,IAAI,CAAA;IACpB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,eAAe,CAAA;IAC5B,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;CAC1C,GAAG,KAAK,CAAC,SAAS,CA6HlB"}
@@ -3,7 +3,31 @@ import { useState } from 'react';
3
3
  import { Box, Text } from '../../ui.js';
4
4
  import { t } from '../../i18n.js';
5
5
  import { useTooltip } from '../Tooltip.js';
6
+ import { SpinnerGlyph } from '../Spinner/SpinnerGlyph.js';
7
+ import { stringWidth } from '../../ink/stringWidth.js';
6
8
  import { formatAbsolute, formatBytes, formatWhen, kindMark, titleColor, truncateWidth, } from '../../sessions/format.js';
9
+ /**
10
+ * State presentation for a session this terminal is running: one glyph and one
11
+ * theme colour per status, matching the session overview's own vocabulary so
12
+ * the same session never looks like two different things on two screens.
13
+ * `stopped` is the "not running here" spelling and draws dim.
14
+ */
15
+ const LIVE_STATUS_GLYPH = {
16
+ 'needs-input': '✻',
17
+ working: '✽',
18
+ completed: '✓',
19
+ failed: '✕',
20
+ idle: '∙',
21
+ stopped: '∙',
22
+ };
23
+ const LIVE_STATUS_COLOR = {
24
+ 'needs-input': 'warning',
25
+ working: 'suggestion',
26
+ completed: 'success',
27
+ failed: 'error',
28
+ idle: undefined,
29
+ stopped: undefined,
30
+ };
7
31
  /**
8
32
  * One session in the browser's list: a title line and a metadata line.
9
33
  *
@@ -17,7 +41,7 @@ import { formatAbsolute, formatBytes, formatWhen, kindMark, titleColor, truncate
17
41
  * stay exactly two lines at every terminal width, and a row that wraps
18
42
  * destroys the alignment that lets the eye scan a list at all.
19
43
  */
20
- export function SessionListRow({ session, width, depth, focused, pinned, now, onClick, onContextMenu, onTogglePin, }) {
44
+ export function SessionListRow({ session, width, depth, focused, pinned, now, onClick, onContextMenu, onTogglePin, liveStatus, current, occupiedPid, spinner, }) {
21
45
  const indent = depth * 2;
22
46
  // Two cells for the focus marker, plus the indent for a nested run.
23
47
  const body = Math.max(8, width - 2 - indent);
@@ -30,10 +54,28 @@ export function SessionListRow({ session, width, depth, focused, pinned, now, on
30
54
  // and never the working directory, so that pair is always new information
31
55
  // for telling look-alike sessions apart.
32
56
  const titleText = session.label ?? session.title.text;
33
- const titleBudget = body - 2 - (mark === undefined ? 0 : 2);
34
- const shownTitle = truncateWidth(titleText, titleBudget);
57
+ /**
58
+ * The state cell holds EXACTLY two columns on every row, occupied or not,
59
+ * for the same reason the pin slot does: a column that appears only when a
60
+ * session is busy would shift every title on screen each time one starts or
61
+ * stops working.
62
+ */
63
+ const occupied = occupiedPid !== undefined;
64
+ const stateGlyph = occupied ? '⊘' : LIVE_STATUS_GLYPH[liveStatus ?? 'stopped'];
65
+ const stateColor = occupied
66
+ ? 'error'
67
+ : liveStatus === undefined
68
+ ? undefined
69
+ : LIVE_STATUS_COLOR[liveStatus];
70
+ const occupiedText = occupied ? ` ${t('supervisor-occupied-badge', { pid: occupiedPid })}` : '';
71
+ const currentText = current === true ? ` ${t('supervisor-current')}` : '';
72
+ const titleBudget = body - 2 - (mark === undefined ? 0 : 2) - 2
73
+ - stringWidth(occupiedText) - stringWidth(currentText);
74
+ const shownTitle = truncateWidth(titleText, Math.max(4, titleBudget));
35
75
  const titleTooltip = useTooltip(() => {
36
76
  const parts = [];
77
+ if (occupied)
78
+ parts.push(t('session-mount-occupied-short', { pid: occupiedPid }));
37
79
  if (shownTitle !== titleText)
38
80
  parts.push(titleText);
39
81
  parts.push(formatAbsolute(session.updatedAt));
@@ -52,10 +94,15 @@ export function SessionListRow({ session, width, depth, focused, pinned, now, on
52
94
  if (session.childCount > 0 && depth === 0) {
53
95
  facts.push(t('session-children', { n: session.childCount }));
54
96
  }
55
- return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, onClick: onClick, onContextMenu: onContextMenu, onMouseEnter: onClick !== undefined || onContextMenu !== undefined ? () => setHovered(true) : undefined, onMouseLeave: onClick !== undefined || onContextMenu !== undefined ? () => setHovered(false) : undefined, backgroundColor: focused || hovered ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { children: [_jsx(Text, { color: focused ? 'suggestion' : 'subtle', children: `${' '.repeat(indent)}${focused ? '❯ ' : ' '}` }), _jsx(Box, { ...pinTooltip, onClick: onTogglePin === undefined ? undefined : (event) => {
97
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, onClick: onClick, onContextMenu: onContextMenu, onMouseEnter: onClick !== undefined || onContextMenu !== undefined ? () => setHovered(true) : undefined, onMouseLeave: onClick !== undefined || onContextMenu !== undefined ? () => setHovered(false) : undefined,
98
+ // Hover is the BLUE prompt — pointer feedback and nothing else. Selection
99
+ // is green and never blue: a row the keyboard cursor is on must not look
100
+ // like a row the mouse happens to be over, so the two never share a
101
+ // background and a selected row keeps its own colour while hovered.
102
+ backgroundColor: hovered && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { children: [_jsx(Text, { color: focused ? 'success' : 'subtle', children: `${' '.repeat(indent)}${focused ? '❯ ' : ' '}` }), _jsx(Box, { ...pinTooltip, onClick: onTogglePin === undefined ? undefined : (event) => {
56
103
  // The star is a control on the row, not the row: a click here
57
104
  // toggles the pin and must never fall through to resume.
58
105
  event.stopImmediatePropagation();
59
106
  onTogglePin();
60
- }, children: _jsx(Text, { color: pinned ? 'remember' : undefined, dimColor: !pinned, children: pinned ? '★ ' : '☆ ' }) }), mark !== undefined && _jsx(Text, { color: mark.color, children: `${mark.glyph} ` }), _jsx(Box, { ...titleTooltip, children: _jsx(Text, { color: titleColor(session.title.source, focused), bold: focused, children: shownTitle }) })] }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: `${' '.repeat(indent + 2)}${truncateWidth(facts.join(' · '), body)}` }) })] }));
107
+ }, children: _jsx(Text, { color: pinned ? 'remember' : undefined, dimColor: !pinned, children: pinned ? '★ ' : '☆ ' }) }), _jsx(Box, { children: liveStatus === 'working' && !occupied && spinner !== undefined ? (_jsx(SpinnerGlyph, { frame: spinner.frame, messageColor: "suggestion", reducedMotion: false, time: spinner.time })) : (_jsx(Text, { color: stateColor, dimColor: liveStatus === undefined && !occupied, children: `${stateGlyph} ` })) }), mark !== undefined && _jsx(Text, { color: mark.color, children: `${mark.glyph} ` }), _jsx(Box, { ...titleTooltip, children: _jsx(Text, { color: occupied ? 'error' : titleColor(session.title.source, focused), bold: focused, children: shownTitle }) }), occupiedText !== '' && _jsx(Text, { color: "error", children: occupiedText }), currentText !== '' && _jsx(Text, { color: "success", children: currentText })] }), _jsx(Box, { children: _jsx(Text, { color: focused ? 'success' : undefined, dimColor: !focused, children: `${' '.repeat(indent + 2)}${truncateWidth(facts.join(' · '), body)}` }) })] }));
61
108
  }
@@ -0,0 +1,49 @@
1
+ import React from 'react';
2
+ import type { ClickEvent } from '../../ink/events/click-event.js';
3
+ import type { ContextMenuEvent } from '../../ink/events/context-menu-event.js';
4
+ /**
5
+ * One workspace in the home screen's left rail.
6
+ *
7
+ * Two lines, like every other navigable row in this app: the name line is what
8
+ * the user named it, the detail line is where it actually is and how much is in
9
+ * it. The path is not decoration — two workspaces can carry the same basename
10
+ * (`.../a/work` and `.../b/work`), and the ledger allows duplicate titles, so
11
+ * the path is the only thing that tells them apart.
12
+ *
13
+ * Widths are resolved here rather than left to flexbox for the same reason the
14
+ * session rows resolve them: a row must be exactly two lines at every terminal
15
+ * width, and a wrapped row destroys the alignment a list is scanned by.
16
+ */
17
+ export declare function HomeWorkspaceRow({ title, path, home, sessionCount, present, selected, focused, width, onSelect, onMenu, }: {
18
+ title: string;
19
+ /** Canonical directory path recorded in the ledger. */
20
+ path: string;
21
+ /** Home directory, for collapsing the path to `~`. */
22
+ home: string;
23
+ sessionCount: number;
24
+ /** False when the recorded directory no longer exists. */
25
+ present: boolean;
26
+ /** Whether this workspace's sessions are the ones on the right. */
27
+ selected: boolean;
28
+ /** Keyboard cursor. */
29
+ focused: boolean;
30
+ /** Columns available to the row. */
31
+ width: number;
32
+ /** Left click: show this workspace's sessions (same path as Enter). */
33
+ onSelect?(event: ClickEvent): void;
34
+ /** Right click: open the workspace action menu. */
35
+ onMenu?(event: ContextMenuEvent): void;
36
+ }): React.ReactNode;
37
+ /**
38
+ * The `+` row that opens the directory picker.
39
+ *
40
+ * Deliberately the FIRST row of the rail and always clickable: adding a
41
+ * workspace is the rail's only creation action, and a control that is only
42
+ * reachable by keyboard is a control most users never find in a terminal.
43
+ */
44
+ export declare function HomeAddWorkspaceRow({ focused, width, onOpen, }: {
45
+ focused: boolean;
46
+ width: number;
47
+ onOpen?(event: ClickEvent): void;
48
+ }): React.ReactNode;
49
+ //# sourceMappingURL=HomeWorkspaceRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HomeWorkspaceRow.d.ts","sourceRoot":"","sources":["../../../../src/components/workspaces/HomeWorkspaceRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmB,MAAM,OAAO,CAAA;AAGvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAA;AAG9E;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,IAAI,EACJ,IAAI,EACJ,YAAY,EACZ,OAAO,EACP,QAAQ,EACR,OAAO,EACP,KAAK,EACL,QAAQ,EACR,MAAM,GACP,EAAE;IACD,KAAK,EAAE,MAAM,CAAA;IACb,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAA;IAChB,mEAAmE;IACnE,QAAQ,EAAE,OAAO,CAAA;IACjB,uBAAuB;IACvB,OAAO,EAAE,OAAO,CAAA;IAChB,oCAAoC;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,uEAAuE;IACvE,QAAQ,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;IAClC,mDAAmD;IACnD,MAAM,CAAC,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI,CAAA;CACvC,GAAG,KAAK,CAAC,SAAS,CAuClB;AAED;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,KAAK,EACL,MAAM,GACP,EAAE;IACD,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI,CAAA;CACjC,GAAG,KAAK,CAAC,SAAS,CAoBlB"}
@@ -0,0 +1,50 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box, Text } from '../../ui.js';
4
+ import { t } from '../../i18n.js';
5
+ import { formatProject, spreadRow, truncateWidth } from '../../sessions/format.js';
6
+ /**
7
+ * One workspace in the home screen's left rail.
8
+ *
9
+ * Two lines, like every other navigable row in this app: the name line is what
10
+ * the user named it, the detail line is where it actually is and how much is in
11
+ * it. The path is not decoration — two workspaces can carry the same basename
12
+ * (`.../a/work` and `.../b/work`), and the ledger allows duplicate titles, so
13
+ * the path is the only thing that tells them apart.
14
+ *
15
+ * Widths are resolved here rather than left to flexbox for the same reason the
16
+ * session rows resolve them: a row must be exactly two lines at every terminal
17
+ * width, and a wrapped row destroys the alignment a list is scanned by.
18
+ */
19
+ export function HomeWorkspaceRow({ title, path, home, sessionCount, present, selected, focused, width, onSelect, onMenu, }) {
20
+ const [hovered, setHovered] = useState(false);
21
+ const body = Math.max(8, width - 4);
22
+ // The presence marker leads the badge on purpose: `spreadRow` may truncate
23
+ // the right-hand segment, and "this directory is gone" is the one fact that
24
+ // must never be the part that gets cut (the session count is also visible in
25
+ // the pane, the missing directory is not).
26
+ const count = t('home-sessions-count', { n: sessionCount });
27
+ const badge = present ? count : `${t('home-workspace-missing')} · ${count}`;
28
+ const heading = spreadRow(`${selected ? '▣' : '▢'} ${title}`, `${selected ? '✓ ' : ''}${badge}`, body);
29
+ const detail = formatProject(path, home);
30
+ // The cursor (green) outranks the selection ring (green); both outrank the
31
+ // idle colour. Blue is not part of this ladder at all — see the background.
32
+ const headingColor = (focused || selected) ? 'success' : present ? 'text' : 'inactive';
33
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, onClick: onSelect, onContextMenu: onMenu, onMouseEnter: onSelect === undefined && onMenu === undefined ? undefined : () => setHovered(true), onMouseLeave: onSelect === undefined && onMenu === undefined ? undefined : () => setHovered(false),
34
+ // Hover is the BLUE prompt — pointer feedback and nothing else, so it
35
+ // never marks the focused or the selected row. Selection itself is green
36
+ // in the text, which is why this row draws no box while selected.
37
+ backgroundColor: hovered && !focused ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Text, { color: focused ? 'success' : 'subtle', children: focused ? '❯ ' : ' ' }), _jsx(Text, { color: headingColor, bold: focused || selected, children: heading.left }), _jsx(Text, { dimColor: !focused, children: `${' '.repeat(heading.gap)}${heading.right}` })] }), _jsx(Text, { color: (focused || selected) ? 'success' : undefined, dimColor: !(focused || selected), wrap: "truncate-end", children: ` ${truncateWidth(detail, body - 2)}` })] }));
38
+ }
39
+ /**
40
+ * The `+` row that opens the directory picker.
41
+ *
42
+ * Deliberately the FIRST row of the rail and always clickable: adding a
43
+ * workspace is the rail's only creation action, and a control that is only
44
+ * reachable by keyboard is a control most users never find in a terminal.
45
+ */
46
+ export function HomeAddWorkspaceRow({ focused, width, onOpen, }) {
47
+ const [hovered, setHovered] = useState(false);
48
+ const body = Math.max(8, width - 4);
49
+ return (_jsxs(Box, { flexDirection: "column", flexShrink: 0, paddingX: 1, onClick: onOpen, onMouseEnter: onOpen === undefined ? undefined : () => setHovered(true), onMouseLeave: onOpen === undefined ? undefined : () => setHovered(false), backgroundColor: focused || hovered ? 'userMessageBackgroundHover' : undefined, children: [_jsxs(Box, { height: 1, flexShrink: 0, overflow: "hidden", children: [_jsx(Text, { color: focused ? 'suggestion' : 'subtle', children: focused ? '❯ ' : ' ' }), _jsx(Text, { color: "remember", bold: true, children: `+ ${t('home-add-workspace')}` })] }), _jsx(Text, { dimColor: true, wrap: "truncate-end", children: ` ${truncateWidth(t('home-add-hint'), body - 2)}` })] }));
50
+ }
@@ -0,0 +1,10 @@
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /**
3
+ * 0.1.7 removed directory discovery. Consume the official bundle definitions
4
+ * through its registry, without copying or reimplementing their tool sets.
5
+ * Web/profile declarations own their seats even while still activating.
6
+ * Returns false on the legacy directory-backed roster or while absent. An
7
+ * absent service is watched through Cordis so a late registry is not missed.
8
+ */
9
+ export declare function registerBundledPresets(ctx: Context): Promise<boolean>;
10
+ //# sourceMappingURL=bundled-presets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundled-presets.d.ts","sourceRoot":"","sources":["../../../src/dsh-adapter/bundled-presets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAwClD;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CA0C3E"}