@deepseek-harness-tui/dsh-tui 0.8.8 → 0.9.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 (467) hide show
  1. package/README.md +60 -238
  2. package/bin/dsh-tui.js +172 -5
  3. package/cordis.patch.yml +16 -4
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
  5. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
  7. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
  8. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
  9. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
  11. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
  12. package/dsh-ecosystem-spec/registry/README.md +14 -14
  13. package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
  14. package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
  15. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
  16. package/lib/types/cc/hyperlink.d.ts +7 -1
  17. package/lib/types/cc/hyperlink.d.ts.map +1 -1
  18. package/lib/types/cc/hyperlink.js +3 -2
  19. package/lib/types/cc/markdown.d.ts.map +1 -1
  20. package/lib/types/cc/markdown.js +33 -3
  21. package/lib/types/cc/sessionColors.d.ts +27 -0
  22. package/lib/types/cc/sessionColors.d.ts.map +1 -0
  23. package/lib/types/cc/sessionColors.js +39 -0
  24. package/lib/types/commands.d.ts.map +1 -1
  25. package/lib/types/commands.js +10 -2
  26. package/lib/types/components/ActivityPicker.d.ts +4 -1
  27. package/lib/types/components/ActivityPicker.d.ts.map +1 -1
  28. package/lib/types/components/ActivityPicker.js +2 -2
  29. package/lib/types/components/AutoRecapRow.d.ts +23 -0
  30. package/lib/types/components/AutoRecapRow.d.ts.map +1 -0
  31. package/lib/types/components/AutoRecapRow.js +23 -0
  32. package/lib/types/components/BtwPanel.d.ts.map +1 -1
  33. package/lib/types/components/BtwPanel.js +1 -1
  34. package/lib/types/components/Chat/SubagentMessage.d.ts +2 -1
  35. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -1
  36. package/lib/types/components/Chat/SubagentMessage.js +11 -3
  37. package/lib/types/components/ColorPicker.d.ts +17 -0
  38. package/lib/types/components/ColorPicker.d.ts.map +1 -0
  39. package/lib/types/components/ColorPicker.js +20 -0
  40. package/lib/types/components/CommandSuggestions.d.ts +23 -4
  41. package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
  42. package/lib/types/components/CommandSuggestions.js +41 -14
  43. package/lib/types/components/ContextBarView.d.ts +33 -0
  44. package/lib/types/components/ContextBarView.d.ts.map +1 -0
  45. package/lib/types/components/ContextBarView.js +46 -0
  46. package/lib/types/components/EffortInputBorder.d.ts +12 -1
  47. package/lib/types/components/EffortInputBorder.d.ts.map +1 -1
  48. package/lib/types/components/EffortInputBorder.js +22 -5
  49. package/lib/types/components/EffortSlider.d.ts +4 -1
  50. package/lib/types/components/EffortSlider.d.ts.map +1 -1
  51. package/lib/types/components/EffortSlider.js +4 -2
  52. package/lib/types/components/ExtensionDialog.js +6 -2
  53. package/lib/types/components/FileActionsPanel.d.ts +22 -0
  54. package/lib/types/components/FileActionsPanel.d.ts.map +1 -0
  55. package/lib/types/components/FileActionsPanel.js +28 -0
  56. package/lib/types/components/FileSuggestions.d.ts +26 -7
  57. package/lib/types/components/FileSuggestions.d.ts.map +1 -1
  58. package/lib/types/components/FileSuggestions.js +39 -12
  59. package/lib/types/components/GoalTodoPanel.d.ts +26 -2
  60. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  61. package/lib/types/components/GoalTodoPanel.js +86 -17
  62. package/lib/types/components/HelpMenu.d.ts +7 -1
  63. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  64. package/lib/types/components/HelpMenu.js +12 -4
  65. package/lib/types/components/HistorySearchDialog.d.ts +4 -1
  66. package/lib/types/components/HistorySearchDialog.d.ts.map +1 -1
  67. package/lib/types/components/HistorySearchDialog.js +2 -2
  68. package/lib/types/components/LangPicker.d.ts +14 -0
  69. package/lib/types/components/LangPicker.d.ts.map +1 -0
  70. package/lib/types/components/LangPicker.js +17 -0
  71. package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
  72. package/lib/types/components/LoadedContextPanel.js +3 -1
  73. package/lib/types/components/Markdown.d.ts.map +1 -1
  74. package/lib/types/components/Markdown.js +12 -3
  75. package/lib/types/components/MessageList.d.ts +45 -2
  76. package/lib/types/components/MessageList.d.ts.map +1 -1
  77. package/lib/types/components/MessageList.js +560 -41
  78. package/lib/types/components/ModelPicker.d.ts +27 -2
  79. package/lib/types/components/ModelPicker.d.ts.map +1 -1
  80. package/lib/types/components/ModelPicker.js +26 -6
  81. package/lib/types/components/PermissionsPicker.d.ts +21 -0
  82. package/lib/types/components/PermissionsPicker.d.ts.map +1 -0
  83. package/lib/types/components/PermissionsPicker.js +27 -0
  84. package/lib/types/components/PlanPicker.d.ts +15 -0
  85. package/lib/types/components/PlanPicker.d.ts.map +1 -0
  86. package/lib/types/components/PlanPicker.js +18 -0
  87. package/lib/types/components/PresetPicker.d.ts +4 -1
  88. package/lib/types/components/PresetPicker.d.ts.map +1 -1
  89. package/lib/types/components/PresetPicker.js +2 -2
  90. package/lib/types/components/PromptInput.d.ts +4 -3
  91. package/lib/types/components/PromptInput.d.ts.map +1 -1
  92. package/lib/types/components/PromptInput.js +602 -75
  93. package/lib/types/components/RecapPanel.d.ts +25 -0
  94. package/lib/types/components/RecapPanel.d.ts.map +1 -0
  95. package/lib/types/components/RecapPanel.js +55 -0
  96. package/lib/types/components/RewindPicker.d.ts +12 -1
  97. package/lib/types/components/RewindPicker.d.ts.map +1 -1
  98. package/lib/types/components/RewindPicker.js +6 -4
  99. package/lib/types/components/ScrollbarGutter.d.ts +28 -0
  100. package/lib/types/components/ScrollbarGutter.d.ts.map +1 -0
  101. package/lib/types/components/ScrollbarGutter.js +119 -0
  102. package/lib/types/components/Select.d.ts +8 -1
  103. package/lib/types/components/Select.d.ts.map +1 -1
  104. package/lib/types/components/Select.js +4 -2
  105. package/lib/types/components/SkillsPicker.d.ts +4 -1
  106. package/lib/types/components/SkillsPicker.d.ts.map +1 -1
  107. package/lib/types/components/SkillsPicker.js +2 -2
  108. package/lib/types/components/SubagentCard.d.ts +2 -1
  109. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  110. package/lib/types/components/SubagentCard.js +9 -2
  111. package/lib/types/components/SubagentDashboard.d.ts +4 -0
  112. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  113. package/lib/types/components/SubagentDashboard.js +9 -1
  114. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -1
  115. package/lib/types/components/SubagentDetailScene.js +5 -4
  116. package/lib/types/components/SuggestionCard.d.ts +65 -0
  117. package/lib/types/components/SuggestionCard.d.ts.map +1 -0
  118. package/lib/types/components/SuggestionCard.js +77 -0
  119. package/lib/types/components/ThemePicker.d.ts +4 -1
  120. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  121. package/lib/types/components/ThemePicker.js +2 -2
  122. package/lib/types/components/ThinkingToggle.d.ts +4 -1
  123. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  124. package/lib/types/components/ThinkingToggle.js +2 -2
  125. package/lib/types/components/TimelineRail.d.ts +53 -0
  126. package/lib/types/components/TimelineRail.d.ts.map +1 -0
  127. package/lib/types/components/TimelineRail.js +197 -0
  128. package/lib/types/components/WorkspaceFlowPicker.d.ts +6 -1
  129. package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
  130. package/lib/types/components/WorkspaceFlowPicker.js +4 -2
  131. package/lib/types/components/WorkspaceMenuPicker.d.ts +19 -0
  132. package/lib/types/components/WorkspaceMenuPicker.d.ts.map +1 -0
  133. package/lib/types/components/WorkspaceMenuPicker.js +19 -0
  134. package/lib/types/components/WorkspacePicker.d.ts +4 -1
  135. package/lib/types/components/WorkspacePicker.d.ts.map +1 -1
  136. package/lib/types/components/WorkspacePicker.js +2 -2
  137. package/lib/types/components/approvals/ApprovalPanel.d.ts.map +1 -1
  138. package/lib/types/components/approvals/ApprovalPanel.js +5 -1
  139. package/lib/types/components/design-system/ListItem.d.ts +8 -1
  140. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  141. package/lib/types/components/design-system/ListItem.js +8 -3
  142. package/lib/types/components/design-system/ThemeProvider.d.ts +7 -0
  143. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
  144. package/lib/types/components/design-system/ThemeProvider.js +2 -1
  145. package/lib/types/components/messages/AssistantTextMessage.d.ts +5 -2
  146. package/lib/types/components/messages/AssistantTextMessage.d.ts.map +1 -1
  147. package/lib/types/components/messages/AssistantTextMessage.js +6 -2
  148. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +2 -1
  149. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  150. package/lib/types/components/messages/AssistantThinkingMessage.js +10 -4
  151. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +15 -1
  152. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  153. package/lib/types/components/messages/AssistantToolUseMessage.js +37 -7
  154. package/lib/types/components/messages/UserPromptMessage.d.ts +2 -1
  155. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  156. package/lib/types/components/questions/AskUserQuestionPanel.d.ts +7 -3
  157. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  158. package/lib/types/components/questions/AskUserQuestionPanel.js +75 -11
  159. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  160. package/lib/types/components/questions/PlanReviewPanel.js +13 -2
  161. package/lib/types/components/sessions/SessionListRow.d.ts +4 -1
  162. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  163. package/lib/types/components/sessions/SessionListRow.js +4 -2
  164. package/lib/types/components/trajectory/HotspotView.d.ts +7 -1
  165. package/lib/types/components/trajectory/HotspotView.d.ts.map +1 -1
  166. package/lib/types/components/trajectory/HotspotView.js +8 -2
  167. package/lib/types/components/trajectory/Ledger.d.ts +9 -1
  168. package/lib/types/components/trajectory/Ledger.d.ts.map +1 -1
  169. package/lib/types/components/trajectory/Ledger.js +23 -5
  170. package/lib/types/components/trajectory/WaveBand.d.ts +10 -1
  171. package/lib/types/components/trajectory/WaveBand.d.ts.map +1 -1
  172. package/lib/types/components/trajectory/WaveBand.js +7 -2
  173. package/lib/types/dsh-adapter/channel.d.ts +117 -7
  174. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  175. package/lib/types/dsh-adapter/channel.js +1301 -122
  176. package/lib/types/dsh-adapter/compat/index.d.ts +8 -6
  177. package/lib/types/dsh-adapter/compat/index.d.ts.map +1 -1
  178. package/lib/types/dsh-adapter/compat/index.js +8 -6
  179. package/lib/types/dsh-adapter/compat/sessionLog.d.ts +150 -0
  180. package/lib/types/dsh-adapter/compat/sessionLog.d.ts.map +1 -1
  181. package/lib/types/dsh-adapter/compat/sessionLog.js +518 -41
  182. package/lib/types/dsh-adapter/contract.d.ts +1 -1
  183. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  184. package/lib/types/dsh-adapter/contract.js +1 -0
  185. package/lib/types/dsh-adapter/extension-events.d.ts +1 -1
  186. package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
  187. package/lib/types/dsh-adapter/index.d.ts +20 -2
  188. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  189. package/lib/types/dsh-adapter/index.js +9 -1
  190. package/lib/types/dsh-adapter/plugin.d.ts +0 -9
  191. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  192. package/lib/types/dsh-adapter/plugin.js +453 -14
  193. package/lib/types/dsh-adapter/presets.d.ts +18 -0
  194. package/lib/types/dsh-adapter/presets.d.ts.map +1 -1
  195. package/lib/types/dsh-adapter/presets.js +31 -0
  196. package/lib/types/dsh-adapter/providerWizard.d.ts +29 -1
  197. package/lib/types/dsh-adapter/providerWizard.d.ts.map +1 -1
  198. package/lib/types/dsh-adapter/providerWizard.js +89 -5
  199. package/lib/types/dsh-adapter/questions.d.ts +19 -2
  200. package/lib/types/dsh-adapter/questions.d.ts.map +1 -1
  201. package/lib/types/dsh-adapter/questions.js +53 -10
  202. package/lib/types/dsh-adapter/recap.d.ts +49 -0
  203. package/lib/types/dsh-adapter/recap.d.ts.map +1 -0
  204. package/lib/types/dsh-adapter/recap.js +121 -0
  205. package/lib/types/dsh-adapter/sessionTree.d.ts +314 -0
  206. package/lib/types/dsh-adapter/sessionTree.d.ts.map +1 -0
  207. package/lib/types/dsh-adapter/sessionTree.js +954 -0
  208. package/lib/types/dsh-adapter/shortcuts.d.ts +15 -44
  209. package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
  210. package/lib/types/dsh-adapter/shortcuts.js +28 -165
  211. package/lib/types/dsh-adapter/types.d.ts +1 -1
  212. package/lib/types/dsh-adapter/types.d.ts.map +1 -1
  213. package/lib/types/herdr.js +1 -1
  214. package/lib/types/i18n.d.ts +770 -181
  215. package/lib/types/i18n.d.ts.map +1 -1
  216. package/lib/types/i18n.js +257 -80
  217. package/lib/types/ink/components/AlternateScreen.d.ts.map +1 -1
  218. package/lib/types/ink/components/AlternateScreen.js +13 -1
  219. package/lib/types/ink/components/App.d.ts +12 -1
  220. package/lib/types/ink/components/App.d.ts.map +1 -1
  221. package/lib/types/ink/components/App.js +101 -4
  222. package/lib/types/ink/components/ClockContext.d.ts.map +1 -1
  223. package/lib/types/ink/components/ClockContext.js +2 -0
  224. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  225. package/lib/types/ink/components/ScrollBox.js +170 -91
  226. package/lib/types/ink/constants.d.ts +9 -0
  227. package/lib/types/ink/constants.d.ts.map +1 -1
  228. package/lib/types/ink/constants.js +9 -0
  229. package/lib/types/ink/events/click-event.d.ts +7 -16
  230. package/lib/types/ink/events/click-event.d.ts.map +1 -1
  231. package/lib/types/ink/events/click-event.js +16 -19
  232. package/lib/types/ink/events/event-handlers.d.ts +11 -1
  233. package/lib/types/ink/events/event-handlers.d.ts.map +1 -1
  234. package/lib/types/ink/events/event-handlers.js +2 -0
  235. package/lib/types/ink/events/input-event.d.ts +9 -0
  236. package/lib/types/ink/events/input-event.d.ts.map +1 -1
  237. package/lib/types/ink/events/input-event.js +4 -0
  238. package/lib/types/ink/events/pointer-event.d.ts +61 -0
  239. package/lib/types/ink/events/pointer-event.d.ts.map +1 -0
  240. package/lib/types/ink/events/pointer-event.js +61 -0
  241. package/lib/types/ink/events/wheel-event.d.ts +17 -0
  242. package/lib/types/ink/events/wheel-event.d.ts.map +1 -0
  243. package/lib/types/ink/events/wheel-event.js +20 -0
  244. package/lib/types/ink/geometry-trace.d.ts +51 -0
  245. package/lib/types/ink/geometry-trace.d.ts.map +1 -0
  246. package/lib/types/ink/geometry-trace.js +83 -0
  247. package/lib/types/ink/hit-test.d.ts +56 -1
  248. package/lib/types/ink/hit-test.d.ts.map +1 -1
  249. package/lib/types/ink/hit-test.js +146 -8
  250. package/lib/types/ink/ink.d.ts +58 -1
  251. package/lib/types/ink/ink.d.ts.map +1 -1
  252. package/lib/types/ink/ink.js +230 -29
  253. package/lib/types/ink/log-update.d.ts.map +1 -1
  254. package/lib/types/ink/log-update.js +21 -0
  255. package/lib/types/ink/node-cache.d.ts +5 -0
  256. package/lib/types/ink/node-cache.d.ts.map +1 -1
  257. package/lib/types/ink/parse-keypress.d.ts +8 -0
  258. package/lib/types/ink/parse-keypress.d.ts.map +1 -1
  259. package/lib/types/ink/parse-keypress.js +101 -12
  260. package/lib/types/ink/render-node-to-output.d.ts +10 -0
  261. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  262. package/lib/types/ink/render-node-to-output.js +111 -18
  263. package/lib/types/ink/timeline-rail.d.ts +138 -0
  264. package/lib/types/ink/timeline-rail.d.ts.map +1 -0
  265. package/lib/types/ink/timeline-rail.js +179 -0
  266. package/lib/types/ink/wrap-text.d.ts.map +1 -1
  267. package/lib/types/ink/wrap-text.js +47 -5
  268. package/lib/types/migrationPrefs.d.ts +60 -0
  269. package/lib/types/migrationPrefs.d.ts.map +1 -0
  270. package/lib/types/migrationPrefs.js +97 -0
  271. package/lib/types/modelGroups.d.ts +72 -0
  272. package/lib/types/modelGroups.d.ts.map +1 -0
  273. package/lib/types/modelGroups.js +97 -0
  274. package/lib/types/modelRecents.d.ts +39 -0
  275. package/lib/types/modelRecents.d.ts.map +1 -0
  276. package/lib/types/modelRecents.js +97 -0
  277. package/lib/types/native-ts/yoga-layout/index.d.ts +11 -0
  278. package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
  279. package/lib/types/native-ts/yoga-layout/index.js +31 -1
  280. package/lib/types/oauth.d.ts +15 -0
  281. package/lib/types/oauth.d.ts.map +1 -0
  282. package/lib/types/oauth.js +14 -0
  283. package/lib/types/reload.d.ts +109 -0
  284. package/lib/types/reload.d.ts.map +1 -0
  285. package/lib/types/reload.js +118 -0
  286. package/lib/types/screens/Chat.d.ts +3 -1
  287. package/lib/types/screens/Chat.d.ts.map +1 -1
  288. package/lib/types/screens/Chat.js +1377 -371
  289. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  290. package/lib/types/screens/SessionBrowser.js +56 -29
  291. package/lib/types/screens/SessionTree.d.ts +27 -0
  292. package/lib/types/screens/SessionTree.d.ts.map +1 -0
  293. package/lib/types/screens/SessionTree.js +555 -0
  294. package/lib/types/screens/Settings.d.ts.map +1 -1
  295. package/lib/types/screens/Settings.js +64 -22
  296. package/lib/types/screens/StatusLine.d.ts +0 -8
  297. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  298. package/lib/types/screens/StatusLine.js +224 -45
  299. package/lib/types/screens/StatusMetrics.d.ts +35 -3
  300. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  301. package/lib/types/screens/StatusMetrics.js +63 -8
  302. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  303. package/lib/types/screens/TrajectoryScene.js +81 -14
  304. package/lib/types/screens/chatOverlay.d.ts +237 -0
  305. package/lib/types/screens/chatOverlay.d.ts.map +1 -0
  306. package/lib/types/screens/chatOverlay.js +116 -0
  307. package/lib/types/tips.d.ts.map +1 -1
  308. package/lib/types/tips.js +64 -10
  309. package/lib/types/tuiDisplayPrefs.d.ts +15 -0
  310. package/lib/types/tuiDisplayPrefs.d.ts.map +1 -1
  311. package/lib/types/tuiDisplayPrefs.js +9 -0
  312. package/lib/types/update.d.ts +139 -0
  313. package/lib/types/update.d.ts.map +1 -1
  314. package/lib/types/update.js +382 -30
  315. package/lib/types/utils/debug.d.ts +10 -0
  316. package/lib/types/utils/debug.d.ts.map +1 -1
  317. package/lib/types/utils/debug.js +24 -0
  318. package/lib/types/utils/fileTarget.d.ts +46 -0
  319. package/lib/types/utils/fileTarget.d.ts.map +1 -0
  320. package/lib/types/utils/fileTarget.js +178 -0
  321. package/lib/types/utils/keymap.d.ts +134 -0
  322. package/lib/types/utils/keymap.d.ts.map +1 -0
  323. package/lib/types/utils/keymap.js +311 -0
  324. package/lib/types/utils/mentions.d.ts +25 -2
  325. package/lib/types/utils/mentions.d.ts.map +1 -1
  326. package/lib/types/utils/mentions.js +69 -10
  327. package/lib/types/utils/openExternal.d.ts +50 -0
  328. package/lib/types/utils/openExternal.d.ts.map +1 -0
  329. package/lib/types/utils/openExternal.js +170 -0
  330. package/lib/types/utils/paths.d.ts.map +1 -1
  331. package/lib/types/utils/paths.js +1 -0
  332. package/node_modules/@deepseek-harness-tui/dsh-auth/LICENSE +21 -0
  333. package/node_modules/@deepseek-harness-tui/dsh-auth/README.md +140 -0
  334. package/node_modules/@deepseek-harness-tui/dsh-auth/cordis.patch.yml +11 -0
  335. package/node_modules/@deepseek-harness-tui/dsh-auth/dsh-plugin.json +44 -0
  336. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.d.ts +14 -0
  337. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/command.js +67 -0
  338. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.d.ts +64 -0
  339. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/credentials.js +170 -0
  340. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.d.ts +79 -0
  341. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/index.js +196 -0
  342. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.d.ts +68 -0
  343. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/interaction.js +193 -0
  344. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.d.ts +32 -0
  345. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/opener.js +101 -0
  346. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.d.ts +31 -0
  347. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/profiles.js +68 -0
  348. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.d.ts +69 -0
  349. package/node_modules/@deepseek-harness-tui/dsh-auth/lib/service.js +129 -0
  350. package/node_modules/@deepseek-harness-tui/dsh-auth/package.json +70 -0
  351. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
  352. package/node_modules/@dsh-std/command/LICENSE +21 -21
  353. package/node_modules/@dsh-std/command/README.md +26 -26
  354. package/node_modules/@dsh-std/command/README.zh.md +28 -28
  355. package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
  356. package/node_modules/@dsh-std/command/package.json +22 -22
  357. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  358. package/node_modules/@dsh-std/connection/LICENSE +21 -21
  359. package/node_modules/@dsh-std/connection/README.md +42 -42
  360. package/node_modules/@dsh-std/connection/README.zh.md +42 -42
  361. package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  362. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  363. package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  364. package/node_modules/@dsh-std/connection/package.json +20 -20
  365. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  366. package/node_modules/@dsh-std/core/LICENSE +21 -21
  367. package/node_modules/@dsh-std/core/README.md +13 -13
  368. package/node_modules/@dsh-std/core/README.zh.md +13 -13
  369. package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  370. package/node_modules/@dsh-std/core/package.json +21 -21
  371. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  372. package/node_modules/@dsh-std/manifest/LICENSE +21 -21
  373. package/node_modules/@dsh-std/manifest/README.md +5 -5
  374. package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  375. package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  376. package/node_modules/@dsh-std/manifest/package.json +20 -20
  377. package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  378. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
  379. package/node_modules/@dsh-std/messages/LICENSE +21 -21
  380. package/node_modules/@dsh-std/messages/README.md +5 -5
  381. package/node_modules/@dsh-std/messages/README.zh.md +7 -7
  382. package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
  383. package/node_modules/@dsh-std/messages/package.json +20 -20
  384. package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
  385. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  386. package/node_modules/@dsh-std/presentation/LICENSE +21 -21
  387. package/node_modules/@dsh-std/presentation/README.md +27 -27
  388. package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  389. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  390. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  391. package/node_modules/@dsh-std/presentation/package.json +25 -25
  392. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
  393. package/node_modules/@dsh-std/storage/LICENSE +21 -21
  394. package/node_modules/@dsh-std/storage/README.md +5 -5
  395. package/node_modules/@dsh-std/storage/README.zh.md +7 -7
  396. package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
  397. package/node_modules/@dsh-std/storage/package.json +20 -20
  398. package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
  399. package/package.json +19 -5
  400. package/presets/liangshen/.dsh-tui-managed.json +1 -1
  401. package/presets/liangshen/instruction-hint.mjs +8 -0
  402. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  403. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
  404. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
  405. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
  406. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  407. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  408. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  409. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
  410. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  411. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
  412. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
  413. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
  414. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  415. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
  416. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  417. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
  418. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
  419. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  420. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  421. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
  422. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  423. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  424. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
  425. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
  426. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  427. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  428. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  429. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
  430. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  431. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
  432. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
  433. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
  434. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  435. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
  436. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  437. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
  438. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
  439. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
  440. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  441. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
  442. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  443. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
  444. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
  445. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
  446. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  447. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
  448. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  449. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
  450. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
  451. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
  452. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  453. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  454. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  455. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
  456. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  457. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
  458. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
  459. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
  460. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  461. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
  462. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  463. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
  464. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
  465. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
  466. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  467. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
@@ -13,19 +13,22 @@ import { ApprovalStore } from './approvals.js';
13
13
  import { registerPackagedSkills } from './packaged-skills.js';
14
14
  import { registerPromptDebug } from './promptDebug.js';
15
15
  import { readActivityFrames } from '../activityPrefs.js';
16
+ import { commitFullscreenFactoryMigration, planFullscreenFactoryMigration, readAppliedMigrations } from '../migrationPrefs.js';
16
17
  import { readModelPref } from '../modelPrefs.js';
17
18
  import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../modelRoute.js';
18
19
  import { readPresetPref } from '../presetPrefs.js';
19
- import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, runningPresetOf } from './presets.js';
20
+ import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, resolvePersistedRoute, runningPresetOf } from './presets.js';
20
21
  import { ensurePackagedPresets } from './packaged-presets.js';
21
22
  import { ensureLegacySessionEventTypes } from './compat/index.js';
22
23
  import { clearResumeTarget, writeResumeTarget } from '../sessionHistory.js';
23
24
  import { resolveSessionCwd } from '../utils/workspaceRoot.js';
24
- import { checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isVersionNewer, resolveDshProfileName, resolveTuiUpdateTarget, updateTuiAndRestart } from '../update.js';
25
+ import { beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice } from '../update.js';
25
26
  import { getLang, isLang, resolveStartupLang, setLang, t, writeLangPref } from '../i18n.js';
26
- import { DEFAULT_STATUS_BAR, normalizeStatusBar, normalizeToolBackground } from '../tuiDisplayPrefs.js';
27
+ import { DEFAULT_STATUS_BAR, normalizeScrollGutter, normalizeStatusBar, normalizeToolBackground } from '../tuiDisplayPrefs.js';
28
+ import { draftComboConflicts, effectiveComboString, parseComboDraft, setKeymapOverrides, SHORTCUT_ACTIONS, } from '../utils/keymap.js';
27
29
  import { detectLegacyEnv, migrateLegacyDataDir, RENAMED_ENV } from '../utils/paths.js';
28
30
  import { attachHerdrIntegration } from '../herdr.js';
31
+ import { logMouseDebug } from '../utils/debug.js';
29
32
  import { Chat } from '../screens/Chat.js';
30
33
  import { getHostDialogStore } from './dialogs.js';
31
34
  import { getHostStatusStore } from './status.js';
@@ -48,8 +51,79 @@ import { CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, supportsTabStatus, wrapForMult
48
51
  * `dsh-jsonrpc`: the surrounding `cordis.yml` supplies the agent spine, the
49
52
  * LLM adapter, and the tool plugins.
50
53
  */
54
+ /**
55
+ * Fullscreen decision latched across host recomposes. The launcher disposes
56
+ * and re-mounts the plugin tree (teardown → apply() runs again) — a fresh
57
+ * apply() re-resolves `bootedFullscreen` from cordis config, and the
58
+ * settings user layer (settings.yaml) can arrive after the 300ms
59
+ * `settingsReady` bound when the recompose is also re-mounting the settings
60
+ * service. The tree would then mount INLINE and `fullscreenFrozen` would
61
+ * swallow the late application — the app lands on the main screen
62
+ * ("exited fullscreen", dead mouse, unpinned input) until restart. A
63
+ * session that already mounted fullscreen must never regress on a
64
+ * recompose: latch the decision.
65
+ */
66
+ let lastBootedFullscreen;
51
67
  export async function apply(ctx, config) {
68
+ // /restart handoff diagnosis: the replacement process is marked by env and
69
+ // logs its boot progress to ~/.dsh-tui/restart.log (ordinary launches stay
70
+ // silent). First line lands before anything in this function can throw.
71
+ if (process.env.DSH_TUI_RESTART_CHILD === '1') {
72
+ logRestartEvent('boot: plugin apply', {
73
+ stdoutTty: process.stdout.isTTY === true,
74
+ stdinTty: process.stdin.isTTY === true,
75
+ stderrTty: process.stderr.isTTY === true,
76
+ });
77
+ // Field evidence (2026-08-24): the restarted TUI mounts but takes no
78
+ // input, and the terminal's DA reply surfaced on PS's prompt line in an
79
+ // earlier attempt — meaning NO process was reading console input. Probe
80
+ // whether THIS process's stdin pump ever sees bytes: wrap read()
81
+ // transparently (delegates; purely observational) and sample the pump
82
+ // state, so the log distinguishes "bytes never arrive" (console-level
83
+ // theft/mode) from "bytes arrive but the UI ignores them".
84
+ const probedStdin = process.stdin;
85
+ const originalRead = probedStdin.read.bind(probedStdin);
86
+ let loggedChunks = 0;
87
+ probedStdin.read = ((...args) => {
88
+ const chunk = originalRead(...args);
89
+ if (chunk !== null && chunk !== '' && loggedChunks < 12) {
90
+ loggedChunks += 1;
91
+ const text = String(chunk);
92
+ logRestartEvent('boot: stdin chunk arrived', {
93
+ bytes: text.length,
94
+ preview: text.slice(0, 24).replace(/[^\x20-\x7e]/g, '.'),
95
+ });
96
+ }
97
+ return chunk;
98
+ });
99
+ const sampleStdinState = (label) => {
100
+ logRestartEvent(`boot: ${label}`, {
101
+ isRaw: probedStdin.isRaw === true,
102
+ readableListeners: probedStdin.listenerCount('readable'),
103
+ dataListeners: probedStdin.listenerCount('data'),
104
+ paused: probedStdin.isPaused,
105
+ buffered: probedStdin.readableLength,
106
+ chunksSeen: loggedChunks,
107
+ });
108
+ };
109
+ sampleStdinState('stdin state at plugin apply');
110
+ let pendingSamples = 0;
111
+ const sampleAt = (delayMs, label) => {
112
+ pendingSamples += 1;
113
+ const timer = setTimeout(() => {
114
+ pendingSamples -= 1;
115
+ sampleStdinState(label);
116
+ }, delayMs);
117
+ timer.unref?.();
118
+ };
119
+ sampleAt(2000, 'stdin state +2s');
120
+ sampleAt(5000, 'stdin state +5s');
121
+ sampleAt(12000, 'stdin state +12s');
122
+ }
52
123
  if (!process.stdout.isTTY) {
124
+ if (process.env.DSH_TUI_RESTART_CHILD === '1') {
125
+ logRestartEvent('boot: TTY gate failed - stdout is not a TTY');
126
+ }
53
127
  throw new Error('dsh-tui requires an interactive terminal (stdout must be a TTY).');
54
128
  }
55
129
  // The official profile launcher owns the system preset root and replaces
@@ -310,6 +384,10 @@ export async function apply(ctx, config) {
310
384
  // only explicit values so the target session's own record wins.
311
385
  configuredModel: config.model,
312
386
  configuredProvider: config.provider,
387
+ // Raw cordis.yml `lang` / `activityFrames`: /reload must not override a
388
+ // static deployment choice with the persisted preference.
389
+ configuredLang: config.lang,
390
+ configuredActivityFrames: config.activityFrames,
313
391
  effort: config.effort,
314
392
  activity: config.activity,
315
393
  // Explicit cordis.yml value (static deployment choice) wins over the
@@ -329,18 +407,44 @@ export async function apply(ctx, config) {
329
407
  diffLayout: config.diffLayout,
330
408
  thinkingFold: config.thinkingFold,
331
409
  toolBackground: config.toolBackground,
410
+ scrollGutter: config.scrollGutter,
411
+ promptSessionLabel: config.promptSessionLabel,
332
412
  statusBar: config.statusBar,
333
413
  handle,
334
414
  });
415
+ // Fullscreen layout decision: the settings user layer (edited through the
416
+ // /settings screen) overrides cordis.yml when set. The settings injection
417
+ // below resolves it synchronously when the host settings service is up —
418
+ // i.e. before the tree mounts. `fullscreenFrozen` latches at mount: the
419
+ // exit funnel and the AlternateScreen wrap must keep reading the mode this
420
+ // session ACTUALLY runs, never a mid-session edit meant for the next boot
421
+ // (swapping layouts requires re-mounting the whole tree).
422
+ let bootedFullscreen = config.fullscreen === true;
423
+ let fullscreenFrozen = false;
424
+ // The settings service may come up AFTER this plugin's apply: the cordis
425
+ // inject callback defers until the service registers, so the first
426
+ // `apply(scope.get())` below can land after the mount (field report: the
427
+ // /settings fullscreen toggle never took effect — the frozen latch below
428
+ // swallowed the late callback and bootedFullscreen stayed false). The
429
+ // mount must therefore WAIT for the first settings application (bounded —
430
+ // a bare embedder without a settings service must not deadlock).
431
+ let resolveSettingsReady;
432
+ const settingsReady = new Promise(resolve => {
433
+ resolveSettingsReady = () => resolve();
434
+ setTimeout(resolve, 300);
435
+ });
335
436
  // Register the dsh-tui settings namespace so the /settings screen can
336
437
  // edit it (the section below was '命名空间未注册' without this): the
337
438
  // user layer in settings.yaml wins over cordis.yml's diffLayout, and
338
439
  // watch() lands commits on the live channel — no recompose needed.
339
440
  ctx.inject(['settings'], (settingsCtx) => {
340
- const scope = settingsCtx.settings.register(settingsNamespace('dsh-tui'), Schema.object({
441
+ const tuiSettingsNs = settingsNamespace('dsh-tui');
442
+ const scope = settingsCtx.settings.register(tuiSettingsNs, Schema.object({
341
443
  diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
342
444
  thinkingFold: Schema.union(['preview', 'full']).default('preview'),
343
445
  toolBackground: Schema.union(['none', 'subtle', 'strong']).default('none'),
446
+ scrollGutter: Schema.union(['timeline', 'scrollbar', 'hidden']).default('timeline'),
447
+ promptSessionLabel: Schema.boolean().default(false),
344
448
  statusBar: Schema.object({
345
449
  compact: Schema.boolean().default(DEFAULT_STATUS_BAR.compact),
346
450
  model: Schema.boolean().default(DEFAULT_STATUS_BAR.model),
@@ -352,6 +456,8 @@ export async function apply(ctx, config) {
352
456
  tps: Schema.boolean().default(DEFAULT_STATUS_BAR.tps),
353
457
  gitBranch: Schema.boolean().default(DEFAULT_STATUS_BAR.gitBranch),
354
458
  sessionTitle: Schema.boolean().default(DEFAULT_STATUS_BAR.sessionTitle),
459
+ sessionId: Schema.boolean().default(DEFAULT_STATUS_BAR.sessionId),
460
+ goal: Schema.boolean().default(DEFAULT_STATUS_BAR.goal),
355
461
  mode: Schema.boolean().default(DEFAULT_STATUS_BAR.mode),
356
462
  contextBar: Schema.boolean().default(DEFAULT_STATUS_BAR.contextBar),
357
463
  activity: Schema.boolean().default(DEFAULT_STATUS_BAR.activity),
@@ -367,6 +473,13 @@ export async function apply(ctx, config) {
367
473
  // the effective language (see the section's format below) and lets
368
474
  // cordis.yml / lang.json keep their precedence.
369
475
  lang: Schema.union(['zh', 'en']),
476
+ // Same no-default rule: unset keeps cordis.yml's `fullscreen`
477
+ // decisive; set overrides it from the next boot on.
478
+ fullscreen: Schema.boolean(),
479
+ // Built-in action-shortcut overrides, one optional combo string per
480
+ // action (see src/utils/keymap.ts). Unset keeps the default binding
481
+ // and the section's format() shows the effective combos.
482
+ shortcuts: Schema.object(Object.fromEntries(SHORTCUT_ACTIONS.map(action => [action.id, Schema.string().required(false)]))).required(false),
370
483
  }));
371
484
  const applyLayout = (value) => {
372
485
  channel.setDiffLayout(value.diffLayout ?? config.diffLayout ?? 'auto');
@@ -377,6 +490,15 @@ export async function apply(ctx, config) {
377
490
  const applyMinimal = (value) => {
378
491
  channel.setMinimal(value.minimal ?? false);
379
492
  };
493
+ // Fullscreen: only meaningful before the tree mounts (the freeze latch
494
+ // above). A later doc change (mid-session /settings edit) is persisted
495
+ // by the service and picked up on the next boot; the watch below says
496
+ // so with a notify.
497
+ const applyFullscreen = (value) => {
498
+ if (!fullscreenFrozen && typeof value.fullscreen === 'boolean') {
499
+ bootedFullscreen = value.fullscreen;
500
+ }
501
+ };
380
502
  // The /settings language field writes `lang` through the settings
381
503
  // service (user layer): apply it live and mirror it to lang.json so
382
504
  // the /lang command and next-boot resolution agree. DSH_TUI_LANG
@@ -393,30 +515,173 @@ export async function apply(ctx, config) {
393
515
  const applyDisplay = (value) => {
394
516
  channel.setThinkingFold(value.thinkingFold ?? config.thinkingFold ?? 'preview');
395
517
  channel.setToolBackground(normalizeToolBackground(value.toolBackground ?? config.toolBackground));
518
+ channel.setScrollGutter(normalizeScrollGutter(value.scrollGutter ?? config.scrollGutter));
519
+ channel.setPromptSessionLabel(value.promptSessionLabel ?? config.promptSessionLabel ?? false);
396
520
  channel.setStatusBar(normalizeStatusBar(value.statusBar ?? config.statusBar));
397
521
  };
522
+ // Shortcut overrides resolve per action: settings user layer wins over
523
+ // cordis.yml's `shortcuts` (same precedence as every other field);
524
+ // unset everywhere keeps the registry default. Applied live into the
525
+ // keymap module — the very next keypress matches the new combos.
526
+ const applyShortcuts = (value) => {
527
+ const userLayer = value.shortcuts ?? {};
528
+ const configLayer = config.shortcuts ?? {};
529
+ const merged = {};
530
+ for (const action of SHORTCUT_ACTIONS) {
531
+ const user = userLayer[action.id];
532
+ const pinned = configLayer[action.id];
533
+ const chosen = typeof user === 'string' && user.trim() !== ''
534
+ ? user
535
+ : (typeof pinned === 'string' && pinned.trim() !== '' ? pinned : undefined);
536
+ if (chosen !== undefined)
537
+ merged[action.id] = chosen;
538
+ }
539
+ setKeymapOverrides(merged);
540
+ };
398
541
  const apply = (next) => {
399
542
  applyLayout(next);
400
543
  applyWhale(next);
401
544
  applyMinimal(next);
402
545
  applyLang(next);
403
546
  applyDisplay(next);
547
+ applyShortcuts(next);
548
+ applyFullscreen(next);
404
549
  };
405
- apply(scope.get());
550
+ // One-time fullscreen factory-default migration (companion to the
551
+ // schema + cordis.patch.yml flip false→true): a `fullscreen: false`
552
+ // pinned in the settings user layer BEFORE the flip keeps overriding
553
+ // the new default on every boot. The first boot past this code clears
554
+ // that stale explicit choice; the migrations.json marker makes it
555
+ // strictly once, so a `false` re-pinned afterwards always stands. The
556
+ // boot decision cannot wait for the async doc write — the stale value
557
+ // is shadowed out of the first apply below (destructuring omission,
558
+ // not an explicit undefined), and the later watch commit (fullscreen
559
+ // back to undefined) is a no-op for applyFullscreen.
560
+ const bootSettings = scope.get();
561
+ const fullscreenMigration = planFullscreenFactoryMigration(bootSettings.fullscreen, readAppliedMigrations());
562
+ void commitFullscreenFactoryMigration(fullscreenMigration, {
563
+ unset: () => settingsCtx.settings.mutate(tuiSettingsNs, [{ op: 'unset', path: ['fullscreen'] }]),
564
+ });
565
+ if (fullscreenMigration === 'unset') {
566
+ channel.notify(t('settings-fullscreen-migrated'), { color: 'warning' });
567
+ }
568
+ const { fullscreen: staleFullscreen, ...migratedSettings } = bootSettings;
569
+ apply(fullscreenMigration === 'unset' ? migratedSettings : bootSettings);
406
570
  scope.watch(next => {
407
571
  apply(next);
572
+ if (typeof next.fullscreen === 'boolean' && next.fullscreen !== bootedFullscreen) {
573
+ channel.notify(t('settings-fullscreen-restart'), { color: 'warning' });
574
+ }
408
575
  });
576
+ resolveSettingsReady?.();
409
577
  });
410
578
  // The /settings screen's own section: the dsh-tui namespace comes from
411
579
  // the settings registration above, and the declared selects write `lang`
412
580
  // and `diffLayout` back through the settings service's revision-fenced
413
581
  // mutate (the watch applies both live).
582
+ //
583
+ // Shortcut fields: one text field per customizable action. The draft is
584
+ // one or more ctrl+/alt+ combos (comma-separated); blank restores the
585
+ // default, and a combo another action or a fixed editor binding already
586
+ // owns is refused as invalid so remaps can never silently shadow.
587
+ const shortcutFieldMeta = {
588
+ paste: {
589
+ label: 'Paste shortcut',
590
+ zh: '粘贴快捷键',
591
+ hintEn: d => `Clipboard paste (text, file paths, images). Default: ${d}. Alt+V works where the terminal eats Ctrl+V.`,
592
+ hintZh: d => `剪贴板粘贴(文本、文件路径、图片)。默认 ${d}。终端吞掉 Ctrl+V 时可用 Alt+V。`,
593
+ },
594
+ history: {
595
+ label: 'History search shortcut',
596
+ zh: '历史搜索快捷键',
597
+ hintEn: d => `Open the prompt-history search. Default: ${d}.`,
598
+ hintZh: d => `打开输入历史搜索。默认 ${d}。`,
599
+ },
600
+ editor: {
601
+ label: 'External editor shortcut',
602
+ zh: '外部编辑器快捷键',
603
+ hintEn: d => `Edit the draft in $VISUAL/$EDITOR. Default: ${d}.`,
604
+ hintZh: d => `在 $VISUAL/$EDITOR 外部编辑器中编辑草稿。默认 ${d}。`,
605
+ },
606
+ transcript: {
607
+ label: 'Transcript mode shortcut',
608
+ zh: '转录模式快捷键',
609
+ hintEn: d => `Toggle expanded transcript mode. Default: ${d}.`,
610
+ hintZh: d => `切换展开转录模式。默认 ${d}。`,
611
+ },
612
+ trajectory: {
613
+ label: 'Trajectory scene shortcut',
614
+ zh: '轨迹场景快捷键',
615
+ hintEn: d => `Open the trajectory scene. Default: ${d}.`,
616
+ hintZh: d => `打开轨迹场景。默认 ${d}。`,
617
+ },
618
+ dashboard: {
619
+ label: 'Subagent dashboard shortcut',
620
+ zh: '子代理面板快捷键',
621
+ hintEn: d => `Open the subagent dashboard. Default: ${d}.`,
622
+ hintZh: d => `打开子代理面板。默认 ${d}。`,
623
+ },
624
+ contextPanel: {
625
+ label: 'Loaded-context panel shortcut',
626
+ zh: '加载上下文面板快捷键',
627
+ hintEn: d => `Toggle the startup loaded-context panel. Default: ${d}.`,
628
+ hintZh: d => `切换启动时的已加载上下文面板。默认 ${d}。`,
629
+ },
630
+ showAll: {
631
+ label: 'Show-all shortcut',
632
+ zh: '显示全部消息快捷键',
633
+ hintEn: d => `Toggle show-all-messages. Default: ${d}.`,
634
+ hintZh: d => `切换显示全部消息。默认 ${d}。`,
635
+ },
636
+ redraw: {
637
+ label: 'Redraw shortcut',
638
+ zh: '终端重绘快捷键',
639
+ hintEn: d => `Clear and repaint the terminal. Default: ${d}.`,
640
+ hintZh: d => `清空并重绘终端。默认 ${d}。`,
641
+ },
642
+ todoFold: {
643
+ label: 'Todo fold shortcut',
644
+ zh: '待办折叠快捷键',
645
+ hintEn: d => `Fold/unfold the goal/todo panel. Default: ${d}.`,
646
+ hintZh: d => `折叠/展开目标与待办面板。默认 ${d}。`,
647
+ },
648
+ };
649
+ const shortcutFields = SHORTCUT_ACTIONS.map(action => {
650
+ const meta = shortcutFieldMeta[action.id];
651
+ const defaults = action.defaults.join(', ');
652
+ return {
653
+ path: ['shortcuts', action.id],
654
+ label: meta.label,
655
+ descriptions: { zh: meta.zh },
656
+ hint: meta.hintEn(defaults),
657
+ hintDescriptions: { zh: meta.hintZh(defaults) },
658
+ group: 'shortcuts',
659
+ kind: 'text',
660
+ format(value) {
661
+ return typeof value === 'string' && value.trim() !== '' ? value : effectiveComboString(action.id);
662
+ },
663
+ parse(text) {
664
+ const draft = parseComboDraft(text);
665
+ if (draft === undefined)
666
+ return undefined;
667
+ if (draft.combos.length === 0)
668
+ return { kind: 'clear' };
669
+ if (draftComboConflicts(action.id, draft.combos))
670
+ return undefined;
671
+ return { kind: 'set', value: draft.combos.join(', ') };
672
+ },
673
+ };
674
+ });
414
675
  const settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections'));
415
676
  if (settingsSections !== undefined) {
416
677
  const unregister = settingsSections.register({
417
678
  ns: 'dsh-tui',
418
679
  title: 'dsh-tui',
419
- groups: [{ id: 'status-bar', title: 'Status bar', descriptions: { zh: '底栏设置' } }],
680
+ groups: [
681
+ { id: 'status-bar', title: 'Status bar', descriptions: { zh: '底栏设置' } },
682
+ { id: 'shortcuts', title: 'Shortcuts', descriptions: { zh: '快捷键' } },
683
+ { id: 'session', title: 'Session', descriptions: { zh: '会话' } },
684
+ ],
420
685
  fields: [
421
686
  {
422
687
  path: ['lang'],
@@ -436,6 +701,22 @@ export async function apply(ctx, config) {
436
701
  return value === undefined || value === null ? getLang() : String(value);
437
702
  },
438
703
  },
704
+ {
705
+ path: ['fullscreen'],
706
+ label: 'Fullscreen mode',
707
+ descriptions: { zh: '全屏模式' },
708
+ // 故意不用 "alt-screen" 这类终端术语:读者要的是行为差异。鼠标
709
+ // 两种模式都可用(整屏页面自带鼠标跟踪),别让描述暗示关掉就
710
+ // 没有鼠标——最常见的误解。长度对齐既有最长 hint(单行假设)。
711
+ hint: 'On: app takes the whole screen (vim/less style), in-app mouse. Off: native scrollback; full-page screens keep the mouse. Restart to apply.',
712
+ hintDescriptions: { zh: '开启:接管整个终端(同 vim/less),应用内鼠标;关闭:终端原生滚动选择;整屏页两种模式都有鼠标。重启生效。' },
713
+ kind: 'boolean',
714
+ format(value) {
715
+ // Unset in settings.yaml: show what THIS session booted with
716
+ // (the cordis.yml resolution) instead of a misleading false.
717
+ return value === undefined || value === null ? String(bootedFullscreen) : String(value);
718
+ },
719
+ },
439
720
  {
440
721
  path: ['diffLayout'],
441
722
  label: 'Diff layout',
@@ -474,6 +755,40 @@ export async function apply(ctx, config) {
474
755
  { value: 'strong', label: 'Strong', descriptions: { zh: '明显' } },
475
756
  ],
476
757
  },
758
+ {
759
+ path: ['scrollGutter'],
760
+ label: 'Transcript gutter',
761
+ descriptions: { zh: '转录边栏' },
762
+ hint: 'Right gutter of the fullscreen transcript: per-turn timeline ticks, a proportional scrollbar, or nothing.',
763
+ hintDescriptions: { zh: '全屏转录区右侧边栏:按轮次的时间线节点、比例滚动条,或留空。' },
764
+ kind: 'select',
765
+ options: [
766
+ { value: 'timeline', label: 'Turn timeline', descriptions: { zh: '轮次时间线' } },
767
+ { value: 'scrollbar', label: 'Scrollbar', descriptions: { zh: '滚动条' } },
768
+ { value: 'hidden', label: 'Hidden', descriptions: { zh: '隐藏' } },
769
+ ],
770
+ },
771
+ {
772
+ path: ['promptSessionLabel'],
773
+ label: 'Session name chip',
774
+ descriptions: { zh: '会话名标签' },
775
+ hint: 'Show the session name on the prompt top border, right corner. Off by default.',
776
+ hintDescriptions: { zh: '在输入框顶边框右上角显示会话名。默认关闭。' },
777
+ kind: 'boolean',
778
+ },
779
+ {
780
+ path: ['recapOnOpen'],
781
+ label: 'Auto recap on open',
782
+ descriptions: { zh: '打开会话时自动总结' },
783
+ hint: 'On: opening/resuming a session automatically summarizes its recent activity into a dim line at the bottom of the transcript (hover/click to view or apply the suggested title). Off: use /recap manually.',
784
+ hintDescriptions: { zh: '开启:打开/恢复会话时自动把最近活动总结成一行灰字显示在会话底部(可悬停/点击查看或应用建议标题);关闭:手动使用 /recap。' },
785
+ kind: 'boolean',
786
+ format(value) {
787
+ // Unset in settings.yaml: the default is on.
788
+ return value === undefined || value === null ? 'true' : String(value);
789
+ },
790
+ },
791
+ ...shortcutFields,
477
792
  {
478
793
  path: ['statusBar', 'compact'],
479
794
  label: 'Compact status bar',
@@ -564,6 +879,24 @@ export async function apply(ctx, config) {
564
879
  group: 'status-bar',
565
880
  kind: 'boolean',
566
881
  },
882
+ {
883
+ path: ['statusBar', 'sessionId'],
884
+ label: 'Show session id',
885
+ descriptions: { zh: '显示会话 ID' },
886
+ hint: 'Show the short session id (# + first 8 chars) — it matches the session log filename for --resume.',
887
+ hintDescriptions: { zh: '显示短会话 ID(# + 前 8 位)——与日志文件名对应,方便 --resume 定位。' },
888
+ group: 'status-bar',
889
+ kind: 'boolean',
890
+ },
891
+ {
892
+ path: ['statusBar', 'goal'],
893
+ label: 'Show goal status',
894
+ descriptions: { zh: '显示 Goal 状态' },
895
+ hint: 'Show a compact goal chip (phase glyph + rounds) in the status footer while a goal exists.',
896
+ hintDescriptions: { zh: '存在 Goal 时,在底部状态栏显示紧凑的 Goal 状态(阶段符号与轮次)。' },
897
+ group: 'status-bar',
898
+ kind: 'boolean',
899
+ },
567
900
  {
568
901
  path: ['statusBar', 'mode'],
569
902
  label: 'Show session mode',
@@ -678,6 +1011,10 @@ export async function apply(ctx, config) {
678
1011
  let exited = false;
679
1012
  let updateRequested = false;
680
1013
  let updateTargetVersion;
1014
+ // `/restart` flag: same exit funnel as `/update` minus the pnpm step —
1015
+ // write the resume target, restore the terminal, respawn the process with
1016
+ // the original argv, and let the fresh boot attach the same session.
1017
+ let restartRequested = false;
681
1018
  // The profile this process was booted with (`dsh --profile <name>`); dsh
682
1019
  // exposes it nowhere else, and /update must update the installation the
683
1020
  // user is actually running, not a hard-coded one.
@@ -695,7 +1032,7 @@ export async function apply(ctx, config) {
695
1032
  if (error !== undefined) {
696
1033
  const message = error instanceof Error ? error.message : String(error);
697
1034
  ctx.logger.error(`dsh-tui: exit after error: ${message}`);
698
- void finishExit(ctx, instance, config.fullscreen === true, undefined, `dsh-tui crashed: ${message}`, () => disposeRootAndExit(ctx, 1));
1035
+ void finishExit(ctx, instance, bootedFullscreen, undefined, `dsh-tui crashed: ${message}`, () => disposeRootAndExit(ctx, 1));
699
1036
  return;
700
1037
  }
701
1038
  if (updateRequested) {
@@ -705,7 +1042,26 @@ export async function apply(ctx, config) {
705
1042
  catch {
706
1043
  // Resume persistence is best effort and must never block an update.
707
1044
  }
708
- void finishExit(ctx, instance, config.fullscreen === true, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId, updateTargetVersion));
1045
+ void finishExit(ctx, instance, bootedFullscreen, 'Updating @deepseek-harness-tui/dsh-tui and restarting…', undefined, () => runUpdate(ctx, profile, channel.agentId, updateTargetVersion));
1046
+ return;
1047
+ }
1048
+ // `/restart`: same handoff as the update path, no installation step.
1049
+ // The resume target is written unconditionally — the user asked to
1050
+ // restart THIS session, blank or not (mirrors the update contract).
1051
+ if (restartRequested) {
1052
+ beginRestartAttempt(channel.agentId);
1053
+ logRestartEvent('funnel: /restart branch entered');
1054
+ try {
1055
+ writeResumeTarget(channel.agentId);
1056
+ logRestartEvent('funnel: resume target written');
1057
+ }
1058
+ catch (error) {
1059
+ // Resume persistence is best effort and must never block a restart.
1060
+ logRestartEvent('funnel: resume target write failed', {
1061
+ message: error instanceof Error ? error.message : String(error),
1062
+ });
1063
+ }
1064
+ void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, channel.agentId));
709
1065
  return;
710
1066
  }
711
1067
  // Judge against the live session behind the channel (channel.agentId),
@@ -729,10 +1085,19 @@ export async function apply(ctx, config) {
729
1085
  const hint = resumable
730
1086
  ? `Resume with the command below:\n${resumeCommand(profile, channel.agentId)}`
731
1087
  : undefined;
732
- void finishExit(ctx, instance, config.fullscreen === true, hint, undefined, () => disposeRootAndExit(ctx, 0));
1088
+ void finishExit(ctx, instance, bootedFullscreen, hint, undefined, () => disposeRootAndExit(ctx, 0));
733
1089
  },
734
1090
  });
735
1091
  const handleExit = funnel.handleExit;
1092
+ // Chat's `fullscreen` prop must match the root wrap below, or the
1093
+ // full-screen surfaces inside Chat (session browser, settings, trajectory,
1094
+ // subagent pages) would nest a SECOND <AlternateScreen> — whose unmount
1095
+ // writes DEC 1049 exit and drops the whole app back to the main screen
1096
+ // (the stable /resume→Esc "exited fullscreen" repro). The prop is captured
1097
+ // when the element is created, so wait for the settings first-application
1098
+ // BEFORE creating Chat: the element must see the same bootedFullscreen the
1099
+ // root tree resolves after settingsReady below.
1100
+ await settingsReady;
736
1101
  const chat = React.createElement(Chat, {
737
1102
  channel,
738
1103
  questionStore,
@@ -746,8 +1111,17 @@ export async function apply(ctx, config) {
746
1111
  // Full-screen surfaces inside Chat — the trajectory scene and the session
747
1112
  // browser — enter the alt screen themselves in inline mode; in fullscreen
748
1113
  // the tree is already wrapped below, so they must not nest.
749
- fullscreen: config.fullscreen === true,
1114
+ fullscreen: bootedFullscreen,
750
1115
  onExit: () => handleExit(),
1116
+ // `/restart`: respawn this process and resume the session, no update.
1117
+ onRestart: () => {
1118
+ if (exited || restartRequested)
1119
+ return;
1120
+ restartRequested = true;
1121
+ logRestartEvent('command: /restart accepted');
1122
+ channel.notify(t('restart-starting'));
1123
+ handleExit();
1124
+ },
751
1125
  // Only a `dsh --profile <name>` launch has a profile installation for
752
1126
  // `/update` to act on; source checkouts and `--config` overlays get the
753
1127
  // unavailable notice instead.
@@ -791,12 +1165,34 @@ export async function apply(ctx, config) {
791
1165
  });
792
1166
  },
793
1167
  });
1168
+ // Freeze the fullscreen decision only NOW, right before the tree mounts:
1169
+ // Chat above was created after the same settingsReady await, so the root
1170
+ // wrap and the `fullscreen` prop share one value; a mid-session /settings
1171
+ // edit from here on is persisted for the next boot (the watch notifies),
1172
+ // never applied live (swapping layouts requires re-mounting the tree).
1173
+ // Host recompose hardening: never regress a fullscreen session to inline
1174
+ // on a re-mount whose settings application arrived late (see the module
1175
+ // latch note). A fresh process still resolves from config + settings
1176
+ // normally — the latch is undefined there.
1177
+ if (bootedFullscreen === false && lastBootedFullscreen === true) {
1178
+ bootedFullscreen = true;
1179
+ }
1180
+ fullscreenFrozen = true;
794
1181
  // fullscreen: wrap the tree in <AlternateScreen> (DEC 1049 + SGR mouse
795
1182
  // tracking), which turns on in-app text selection (copy-on-select via
796
1183
  // useCopyOnSelect), wheel scroll, and click/hover hit-testing. Inline
797
1184
  // mode leaves the mouse to the terminal emulator's native selection.
798
- const tree = React.createElement(ThemeProvider, null, config.fullscreen ? React.createElement(AlternateScreen, null, chat) : chat);
1185
+ const tree = React.createElement(ThemeProvider, null, bootedFullscreen ? React.createElement(AlternateScreen, null, chat) : chat);
799
1186
  instance = await render(tree, { exitOnCtrlC: false });
1187
+ const isRecompose = lastBootedFullscreen !== undefined;
1188
+ lastBootedFullscreen = bootedFullscreen;
1189
+ logMouseDebug('apply mount', { bootedFullscreen, isRecompose });
1190
+ // /restart handoff diagnosis: the replacement got all the way to a mounted
1191
+ // UI, so any later death is post-boot (and its stderr keeps flowing to the
1192
+ // parent only within the survival window — this line is the durable mark).
1193
+ if (process.env.DSH_TUI_RESTART_CHILD === '1') {
1194
+ logRestartEvent('boot: UI mounted', { fullscreen: bootedFullscreen, isRecompose });
1195
+ }
800
1196
  // Check in the background so registry latency never delays the first frame.
801
1197
  // A failed/offline check is intentionally silent; the manual `/update`
802
1198
  // command remains available regardless of network access.
@@ -814,6 +1210,7 @@ export async function apply(ctx, config) {
814
1210
  // skill commands (issue #86) would survive this exact recompose and the
815
1211
  // re-mounted channel would find the names taken, freezing its menu.
816
1212
  ctx.effect(() => () => {
1213
+ logMouseDebug('apply teardown');
817
1214
  funnel.markTeardown();
818
1215
  channel.releaseContributions();
819
1216
  instance?.unmount();
@@ -847,9 +1244,13 @@ export async function apply(ctx, config) {
847
1244
  async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRoute, meta, configuredPreset) {
848
1245
  // Resume override (issue #67): cordis.yml overrides the target session's
849
1246
  // recorded route only when it pins BOTH halves; undefined halves let the
850
- // session's own request/header records win (issue #30).
1247
+ // session's own request/header records win (issue #30). The recorded route
1248
+ // is ALSO fed back into agentOptions (not just the status line): a resume
1249
+ // whose cordis.yml pins only `provider` would otherwise leave
1250
+ // agentOptions.model undefined, which breaks the `{{model}}` persona
1251
+ // variable for the resumed agent's own assembly and for every subagent it
1252
+ // spawns (dsh-subagent inherits `parent.options.model`).
851
1253
  const resumeRoute = explicitModelRoute(configuredRoute);
852
- const resumeOptions = { provider: resumeRoute?.provider, model: resumeRoute?.model };
853
1254
  if (requestedSessionId !== undefined) {
854
1255
  const resumeId = SessionId(requestedSessionId);
855
1256
  const existing = ctx.agents.get(resumeId);
@@ -866,6 +1267,11 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
866
1267
  // caller's current preference.
867
1268
  const persisted = await resolvePersistedPreset(ctx, resumeId);
868
1269
  const composed = await composePreset(ctx, persisted);
1270
+ const recorded = await resolvePersistedRoute(ctx, resumeId);
1271
+ const resumeOptions = {
1272
+ provider: resumeRoute?.provider ?? recorded?.provider,
1273
+ model: resumeRoute?.model ?? recorded?.model,
1274
+ };
869
1275
  const resumed = await ctx.agents.resume({
870
1276
  resumeSessionId: resumeId,
871
1277
  agentOptions: resumeOptions,
@@ -1065,6 +1471,34 @@ function writeStream(stream, data) {
1065
1471
  }
1066
1472
  });
1067
1473
  }
1474
+ /**
1475
+ * Restart the TUI in place and resume the same session — the `/restart`
1476
+ * tail of `/reload`: the soft reload cannot re-read boot-time-only state
1477
+ * (cordis.yml root config, frozen fullscreen layout, newly built code), so
1478
+ * /restart respawns the process with the original argv through the same
1479
+ * terminal handoff the /update path uses, minus the installation step.
1480
+ * The resume contract is dual-written (env + resume.txt) before this runs.
1481
+ */
1482
+ function runRestart(ctx, profile, sessionId) {
1483
+ logRestartEvent('runRestart: entered, disposing cordis root');
1484
+ disposeRootAndThen(ctx, () => {
1485
+ logRestartEvent('runRestart: root disposed, starting restartTui');
1486
+ void restartTui(sessionId).then(restartCode => {
1487
+ logRestartEvent('runRestart: restartTui resolved', { restartCode });
1488
+ if (restartCode !== 0) {
1489
+ writeHandoffNotice(`\ndsh-tui restart failed to spawn (exit ${restartCode}). Your session is preserved — resume with:\n` +
1490
+ `${resumeCommand(profile, sessionId)}\n\n`);
1491
+ }
1492
+ process.exit(restartCode);
1493
+ }, restartError => {
1494
+ const message = restartError instanceof Error ? restartError.message : String(restartError);
1495
+ logRestartEvent('runRestart: restartTui rejected', { message });
1496
+ writeHandoffNotice(`\ndsh-tui restart failed: ${message}. Your session is preserved — resume with:\n` +
1497
+ `${resumeCommand(profile, sessionId)}\n\n`);
1498
+ process.exit(1);
1499
+ });
1500
+ });
1501
+ }
1068
1502
  function runUpdate(ctx, profile, sessionId, targetVersion) {
1069
1503
  disposeRootAndThen(ctx, () => {
1070
1504
  if (profile === undefined) {
@@ -1113,7 +1547,12 @@ function resumeCommand(profile, sessionId) {
1113
1547
  * reporting failure on a clean exit would mislead wrapper scripts.
1114
1548
  */
1115
1549
  function disposeRootAndThen(ctx, done, fallbackCode = 1) {
1116
- const timer = setTimeout(() => process.exit(fallbackCode), 5000);
1550
+ const timer = setTimeout(() => {
1551
+ // Diagnosis for a stalled disposal: without this line the fallback exit
1552
+ // is indistinguishable from a successful handoff in the field.
1553
+ logRestartEvent('dispose: timeout, taking fallback exit', { fallbackCode });
1554
+ process.exit(fallbackCode);
1555
+ }, 5000);
1117
1556
  timer.unref();
1118
1557
  void withHostRootCapability(() => ctx.root.fiber.dispose()).then(() => {
1119
1558
  clearTimeout(timer);