@deepseek-harness-tui/dsh-tui 0.14.0 → 0.15.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 (735) hide show
  1. package/README.md +73 -14
  2. package/bin/dsh-tui.js +200 -34
  3. package/cordis.patch.yml +16 -52
  4. package/cordis.yml +6 -2
  5. package/guide/dsh-tui-guide/claude-backend.en.md +44 -7
  6. package/guide/dsh-tui-guide/claude-backend.md +28 -6
  7. package/guide/dsh-tui-guide/codex-backend.en.md +34 -7
  8. package/guide/dsh-tui-guide/codex-backend.md +17 -2
  9. package/guide/dsh-tui-guide/configuration.en.md +129 -16
  10. package/guide/dsh-tui-guide/configuration.md +110 -14
  11. package/guide/dsh-tui-guide/interaction.en.md +57 -24
  12. package/guide/dsh-tui-guide/interaction.md +42 -19
  13. package/guide/dsh-tui-guide/plugins.en.md +36 -3
  14. package/guide/dsh-tui-guide/plugins.md +21 -2
  15. package/guide/dsh-tui-guide/themes.en.md +23 -14
  16. package/guide/dsh-tui-guide/themes.md +19 -12
  17. package/guide/dsh-tui-guide/user-guide.en.md +51 -20
  18. package/guide/dsh-tui-guide/user-guide.md +41 -21
  19. package/lib/settings.json +103 -9
  20. package/lib/types/adapter/channel/ui-policy.d.ts +1 -1
  21. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  22. package/lib/types/adapter/channel/ui-policy.js +3 -0
  23. package/lib/types/adapter/channel/ui.d.ts +2 -0
  24. package/lib/types/adapter/channel/ui.d.ts.map +1 -1
  25. package/lib/types/adapter/channel/ui.js +6 -2
  26. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  27. package/lib/types/adapter/kernel/runtime.js +2 -0
  28. package/lib/types/adapter/ports/channel-settings.d.ts +9 -0
  29. package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
  30. package/lib/types/adapter/ports/channel-ui.d.ts +50 -12
  31. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  32. package/lib/types/adapter/ports/channel-view.d.ts +21 -0
  33. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  34. package/lib/types/adapter/spec/backend-capabilities.d.ts +22 -0
  35. package/lib/types/adapter/spec/backend-capabilities.d.ts.map +1 -0
  36. package/lib/types/adapter/spec/backend-capabilities.js +49 -0
  37. package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -1
  38. package/lib/types/adapter/spec/protocol-constants.js +10 -3
  39. package/lib/types/adapter/spec/tui-contributions.d.ts +3 -0
  40. package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -1
  41. package/lib/types/adapter/spec/tui-contributions.js +7 -0
  42. package/lib/types/adapter/standard/registry.d.ts +6 -0
  43. package/lib/types/adapter/standard/registry.d.ts.map +1 -1
  44. package/lib/types/adapter/standard/registry.js +65 -15
  45. package/lib/types/adapter/standard/tui-extension.d.ts.map +1 -1
  46. package/lib/types/adapter/standard/tui-extension.js +7 -1
  47. package/lib/types/adapter/standard/types.d.ts +10 -1
  48. package/lib/types/adapter/standard/types.d.ts.map +1 -1
  49. package/lib/types/adapter/upstream/host-descriptor-driver.d.ts.map +1 -1
  50. package/lib/types/adapter/upstream/host-descriptor-driver.js +52 -0
  51. package/lib/types/agent/backend-manifest.d.ts +152 -0
  52. package/lib/types/agent/backend-manifest.d.ts.map +1 -0
  53. package/lib/types/agent/backend-manifest.js +14 -0
  54. package/lib/types/agent/backend.d.ts +71 -5
  55. package/lib/types/agent/backend.d.ts.map +1 -1
  56. package/lib/types/agent/capabilities.d.ts +6 -0
  57. package/lib/types/agent/capabilities.d.ts.map +1 -1
  58. package/lib/types/agent/index.d.ts +2 -0
  59. package/lib/types/agent/index.d.ts.map +1 -1
  60. package/lib/types/agent/refs.d.ts +11 -1
  61. package/lib/types/agent/refs.d.ts.map +1 -1
  62. package/lib/types/agent/starting-session.d.ts +4 -0
  63. package/lib/types/agent/starting-session.d.ts.map +1 -0
  64. package/lib/types/agent/starting-session.js +19 -0
  65. package/lib/types/backends/claude/activity.d.ts +1 -1
  66. package/lib/types/backends/claude/activity.d.ts.map +1 -1
  67. package/lib/types/backends/claude/activity.js +5 -5
  68. package/lib/types/backends/claude/auth.d.ts.map +1 -1
  69. package/lib/types/backends/claude/auth.js +10 -3
  70. package/lib/types/backends/claude/backend.d.ts.map +1 -1
  71. package/lib/types/backends/claude/backend.js +103 -29
  72. package/lib/types/backends/claude/catalog.d.ts +2 -1
  73. package/lib/types/backends/claude/catalog.d.ts.map +1 -1
  74. package/lib/types/backends/claude/catalog.js +15 -5
  75. package/lib/types/backends/claude/channels.d.ts +5 -3
  76. package/lib/types/backends/claude/channels.d.ts.map +1 -1
  77. package/lib/types/backends/claude/channels.js +5 -4
  78. package/lib/types/backends/claude/contract.d.ts +4 -0
  79. package/lib/types/backends/claude/contract.d.ts.map +1 -1
  80. package/lib/types/backends/claude/contract.js +4 -0
  81. package/lib/types/backends/claude/controls.d.ts +4 -0
  82. package/lib/types/backends/claude/controls.d.ts.map +1 -1
  83. package/lib/types/backends/claude/controls.js +34 -42
  84. package/lib/types/backends/claude/dialogs.d.ts +8 -2
  85. package/lib/types/backends/claude/dialogs.d.ts.map +1 -1
  86. package/lib/types/backends/claude/dialogs.js +24 -16
  87. package/lib/types/backends/claude/images.d.ts +1 -1
  88. package/lib/types/backends/claude/images.d.ts.map +1 -1
  89. package/lib/types/backends/claude/images.js +7 -7
  90. package/lib/types/backends/claude/manifest.d.ts +21 -0
  91. package/lib/types/backends/claude/manifest.d.ts.map +1 -0
  92. package/lib/types/backends/claude/manifest.js +29 -0
  93. package/lib/types/backends/claude/older-history.js +2 -2
  94. package/lib/types/backends/claude/permissions.js +11 -11
  95. package/lib/types/backends/claude/prefs.d.ts +4 -2
  96. package/lib/types/backends/claude/prefs.d.ts.map +1 -1
  97. package/lib/types/backends/claude/prefs.js +4 -3
  98. package/lib/types/backends/claude/process.d.ts +16 -4
  99. package/lib/types/backends/claude/process.d.ts.map +1 -1
  100. package/lib/types/backends/claude/process.js +100 -37
  101. package/lib/types/backends/claude/send-message.d.ts +1 -1
  102. package/lib/types/backends/claude/send-message.d.ts.map +1 -1
  103. package/lib/types/backends/claude/session/auth.js +22 -22
  104. package/lib/types/backends/claude/session/channels.d.ts +4 -0
  105. package/lib/types/backends/claude/session/channels.d.ts.map +1 -1
  106. package/lib/types/backends/claude/session/channels.js +5 -3
  107. package/lib/types/backends/claude/session/lifecycle.d.ts.map +1 -1
  108. package/lib/types/backends/claude/session/lifecycle.js +31 -23
  109. package/lib/types/backends/claude/session/store.js +8 -8
  110. package/lib/types/backends/claude/session/subagents.js +2 -2
  111. package/lib/types/backends/claude/session/types.d.ts +7 -0
  112. package/lib/types/backends/claude/session/types.d.ts.map +1 -1
  113. package/lib/types/backends/claude/side-query.js +3 -3
  114. package/lib/types/backends/claude/task-output.js +5 -5
  115. package/lib/types/backends/claude/text.d.ts +778 -0
  116. package/lib/types/backends/claude/text.d.ts.map +1 -0
  117. package/lib/types/backends/claude/text.js +298 -0
  118. package/lib/types/backends/claude/transcript-file.d.ts +1 -1
  119. package/lib/types/backends/claude/transcript-file.d.ts.map +1 -1
  120. package/lib/types/backends/claude/transcript-file.js +4 -4
  121. package/lib/types/backends/claude/translate/attempts.js +2 -2
  122. package/lib/types/backends/claude/translate/core.js +3 -3
  123. package/lib/types/backends/claude/translate/messages.js +4 -4
  124. package/lib/types/backends/claude/translate/system.js +14 -14
  125. package/lib/types/backends/claude/translate/tasks.js +2 -2
  126. package/lib/types/backends/claude/translate/types.d.ts +1 -1
  127. package/lib/types/backends/claude/translate/types.d.ts.map +1 -1
  128. package/lib/types/backends/codex/auth/external-tokens.js +16 -16
  129. package/lib/types/backends/codex/auth/status.js +14 -14
  130. package/lib/types/backends/codex/backend.d.ts +4 -19
  131. package/lib/types/backends/codex/backend.d.ts.map +1 -1
  132. package/lib/types/backends/codex/backend.js +118 -25
  133. package/lib/types/backends/codex/catalog.d.ts +2 -1
  134. package/lib/types/backends/codex/catalog.d.ts.map +1 -1
  135. package/lib/types/backends/codex/catalog.js +63 -18
  136. package/lib/types/backends/codex/channels.d.ts +6 -3
  137. package/lib/types/backends/codex/channels.d.ts.map +1 -1
  138. package/lib/types/backends/codex/channels.js +15 -13
  139. package/lib/types/backends/codex/detect.d.ts.map +1 -1
  140. package/lib/types/backends/codex/detect.js +9 -6
  141. package/lib/types/backends/codex/manifest.d.ts +30 -0
  142. package/lib/types/backends/codex/manifest.d.ts.map +1 -0
  143. package/lib/types/backends/codex/manifest.js +23 -0
  144. package/lib/types/backends/codex/prefs.d.ts +4 -2
  145. package/lib/types/backends/codex/prefs.d.ts.map +1 -1
  146. package/lib/types/backends/codex/prefs.js +4 -3
  147. package/lib/types/backends/codex/rpc/hub.js +2 -2
  148. package/lib/types/backends/codex/session/activity.d.ts +1 -1
  149. package/lib/types/backends/codex/session/activity.d.ts.map +1 -1
  150. package/lib/types/backends/codex/session/activity.js +13 -13
  151. package/lib/types/backends/codex/session/approvals.js +30 -30
  152. package/lib/types/backends/codex/session/commands.d.ts +8 -2
  153. package/lib/types/backends/codex/session/commands.d.ts.map +1 -1
  154. package/lib/types/backends/codex/session/commands.js +71 -23
  155. package/lib/types/backends/codex/session/controls.d.ts +1 -1
  156. package/lib/types/backends/codex/session/controls.d.ts.map +1 -1
  157. package/lib/types/backends/codex/session/controls.js +21 -16
  158. package/lib/types/backends/codex/session/goals.d.ts.map +1 -1
  159. package/lib/types/backends/codex/session/goals.js +3 -3
  160. package/lib/types/backends/codex/session/history.js +2 -2
  161. package/lib/types/backends/codex/session/images.js +8 -8
  162. package/lib/types/backends/codex/session/input.d.ts +6 -2
  163. package/lib/types/backends/codex/session/input.d.ts.map +1 -1
  164. package/lib/types/backends/codex/session/input.js +55 -13
  165. package/lib/types/backends/codex/session/plan-review.js +6 -6
  166. package/lib/types/backends/codex/session/session.d.ts.map +1 -1
  167. package/lib/types/backends/codex/session/session.js +66 -34
  168. package/lib/types/backends/codex/session/side-query.d.ts.map +1 -1
  169. package/lib/types/backends/codex/session/side-query.js +27 -9
  170. package/lib/types/backends/codex/session/state.d.ts +6 -1
  171. package/lib/types/backends/codex/session/state.d.ts.map +1 -1
  172. package/lib/types/backends/codex/session/subagents.js +5 -5
  173. package/lib/types/backends/codex/session/tasks.js +4 -4
  174. package/lib/types/backends/codex/text.d.ts +933 -0
  175. package/lib/types/backends/codex/text.d.ts.map +1 -0
  176. package/lib/types/backends/codex/text.js +313 -0
  177. package/lib/types/backends/codex/translate/items.js +4 -4
  178. package/lib/types/backends/codex/translate/live.d.ts.map +1 -1
  179. package/lib/types/backends/codex/translate/live.js +11 -8
  180. package/lib/types/backends/codex/translate/notices.js +10 -10
  181. package/lib/types/backends/shared/channel-slug.d.ts +4 -0
  182. package/lib/types/backends/shared/channel-slug.d.ts.map +1 -0
  183. package/lib/types/backends/shared/channel-slug.js +25 -0
  184. package/lib/types/backends/shared/channel-tokens.d.ts.map +1 -1
  185. package/lib/types/backends/shared/channel-tokens.js +30 -23
  186. package/lib/types/{channel → backends/shared}/elicitation.d.ts +78 -21
  187. package/lib/types/backends/shared/elicitation.d.ts.map +1 -0
  188. package/lib/types/{channel → backends/shared}/elicitation.js +64 -66
  189. package/lib/types/backends/shared/image-size.d.ts +33 -0
  190. package/lib/types/backends/shared/image-size.d.ts.map +1 -0
  191. package/lib/types/backends/shared/image-size.js +134 -0
  192. package/lib/types/{utils → backends/shared}/jsonl.d.ts +5 -1
  193. package/lib/types/backends/shared/jsonl.d.ts.map +1 -0
  194. package/lib/types/{utils → backends/shared}/jsonl.js +5 -1
  195. package/lib/types/backends/shared/localized-text.d.ts +30 -0
  196. package/lib/types/backends/shared/localized-text.d.ts.map +1 -0
  197. package/lib/types/backends/shared/localized-text.js +24 -0
  198. package/lib/types/backends/shared/platform-text.d.ts +88 -0
  199. package/lib/types/backends/shared/platform-text.d.ts.map +1 -0
  200. package/lib/types/backends/shared/platform-text.js +33 -0
  201. package/lib/types/branding.d.ts +13 -0
  202. package/lib/types/branding.d.ts.map +1 -1
  203. package/lib/types/branding.js +18 -0
  204. package/lib/types/channel/channel-slug.d.ts +3 -1
  205. package/lib/types/channel/channel-slug.d.ts.map +1 -1
  206. package/lib/types/channel/channel-slug.js +3 -1
  207. package/lib/types/channel/sanitize.d.ts +7 -0
  208. package/lib/types/channel/sanitize.d.ts.map +1 -1
  209. package/lib/types/channel/sanitize.js +9 -0
  210. package/lib/types/channel/usage.d.ts.map +1 -1
  211. package/lib/types/channel/usage.js +5 -0
  212. package/lib/types/commands.d.ts +5 -0
  213. package/lib/types/commands.d.ts.map +1 -1
  214. package/lib/types/commands.js +47 -8
  215. package/lib/types/components/AgentMessageComposer.d.ts.map +1 -1
  216. package/lib/types/components/AgentMessageComposer.js +3 -2
  217. package/lib/types/components/BtwPanel.d.ts.map +1 -1
  218. package/lib/types/components/BtwPanel.js +4 -2
  219. package/lib/types/components/Chat/JobCard.js +3 -3
  220. package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
  221. package/lib/types/components/CommandSuggestions.js +20 -6
  222. package/lib/types/components/ContextBarView.d.ts +5 -1
  223. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  224. package/lib/types/components/ContextBarView.js +6 -3
  225. package/lib/types/components/ExtensionDialog.d.ts.map +1 -1
  226. package/lib/types/components/ExtensionDialog.js +2 -3
  227. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  228. package/lib/types/components/GoalTodoPanel.js +14 -3
  229. package/lib/types/components/InlineMathParagraph.js +1 -1
  230. package/lib/types/components/KernelPicker.d.ts.map +1 -1
  231. package/lib/types/components/KernelPicker.js +1 -1
  232. package/lib/types/components/LogoV2.d.ts.map +1 -1
  233. package/lib/types/components/LogoV2.js +9 -6
  234. package/lib/types/components/MarkdownTable.d.ts.map +1 -1
  235. package/lib/types/components/MarkdownTable.js +4 -3
  236. package/lib/types/components/MessageList.d.ts.map +1 -1
  237. package/lib/types/components/MessageList.js +18 -5
  238. package/lib/types/components/ModelPicker.d.ts +24 -31
  239. package/lib/types/components/ModelPicker.d.ts.map +1 -1
  240. package/lib/types/components/ModelPicker.js +115 -40
  241. package/lib/types/components/PromptEditor.d.ts +3 -3
  242. package/lib/types/components/PromptInput.d.ts +21 -3
  243. package/lib/types/components/PromptInput.d.ts.map +1 -1
  244. package/lib/types/components/PromptInput.js +58 -118
  245. package/lib/types/components/SdkInstallWizard.d.ts +3 -1
  246. package/lib/types/components/SdkInstallWizard.d.ts.map +1 -1
  247. package/lib/types/components/SdkInstallWizard.js +8 -1
  248. package/lib/types/components/SearchBox.d.ts +13 -15
  249. package/lib/types/components/SearchBox.d.ts.map +1 -1
  250. package/lib/types/components/SearchBox.js +58 -27
  251. package/lib/types/components/ThemePicker.d.ts +1 -1
  252. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  253. package/lib/types/components/ThemePicker.js +14 -11
  254. package/lib/types/components/Tooltip.js +1 -1
  255. package/lib/types/components/WorkingSpinner.js +1 -1
  256. package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
  257. package/lib/types/components/WorkspaceFlowPicker.js +4 -3
  258. package/lib/types/components/composerImageTokens.d.ts +24 -0
  259. package/lib/types/components/composerImageTokens.d.ts.map +1 -0
  260. package/lib/types/components/composerImageTokens.js +37 -0
  261. package/lib/types/components/design-system/InputCaret.d.ts +7 -0
  262. package/lib/types/components/design-system/InputCaret.d.ts.map +1 -0
  263. package/lib/types/components/design-system/InputCaret.js +9 -0
  264. package/lib/types/components/design-system/ListItem.d.ts +13 -2
  265. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  266. package/lib/types/components/design-system/ListItem.js +11 -5
  267. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
  268. package/lib/types/components/design-system/ThemeProvider.js +6 -8
  269. package/lib/types/components/kernelCatalog.d.ts +55 -11
  270. package/lib/types/components/kernelCatalog.d.ts.map +1 -1
  271. package/lib/types/components/kernelCatalog.js +42 -23
  272. package/lib/types/components/launchpadActions.d.ts +4 -0
  273. package/lib/types/components/launchpadActions.d.ts.map +1 -1
  274. package/lib/types/components/launchpadActions.js +5 -4
  275. package/lib/types/components/launchpadLayout.d.ts.map +1 -1
  276. package/lib/types/components/launchpadLayout.js +2 -2
  277. package/lib/types/components/messages/UserPromptMessage.d.ts +4 -3
  278. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  279. package/lib/types/components/messages/UserPromptMessage.js +4 -3
  280. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  281. package/lib/types/components/questions/AskUserQuestionPanel.js +8 -3
  282. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  283. package/lib/types/components/questions/PlanReviewPanel.js +4 -3
  284. package/lib/types/components/questions/ProviderModelEditor.d.ts.map +1 -1
  285. package/lib/types/components/questions/ProviderModelEditor.js +3 -13
  286. package/lib/types/components/sessions/ForeignSessionRow.d.ts +3 -1
  287. package/lib/types/components/sessions/ForeignSessionRow.d.ts.map +1 -1
  288. package/lib/types/components/sessions/ForeignSessionRow.js +5 -2
  289. package/lib/types/components/sessions/SessionListRow.d.ts +3 -1
  290. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  291. package/lib/types/components/sessions/SessionListRow.js +5 -2
  292. package/lib/types/components/sidePanel/PanelBar.d.ts +1 -0
  293. package/lib/types/components/sidePanel/PanelBar.d.ts.map +1 -1
  294. package/lib/types/components/sidePanel/PanelBar.js +21 -17
  295. package/lib/types/components/sidePanel/SidePanelColumn.d.ts +1 -1
  296. package/lib/types/components/sidePanel/SidePanelColumn.d.ts.map +1 -1
  297. package/lib/types/components/sidePanel/SidePanelColumn.js +2 -1
  298. package/lib/types/components/sidePanel/SidePanelLayout.d.ts +6 -4
  299. package/lib/types/components/sidePanel/SidePanelLayout.d.ts.map +1 -1
  300. package/lib/types/components/sidePanel/SidePanelLayout.js +33 -7
  301. package/lib/types/components/sidePanel/btw/BtwComposer.d.ts +4 -2
  302. package/lib/types/components/sidePanel/btw/BtwComposer.d.ts.map +1 -1
  303. package/lib/types/components/sidePanel/btw/BtwComposer.js +12 -4
  304. package/lib/types/components/sidePanel/btw/BtwPanelAdapter.d.ts.map +1 -1
  305. package/lib/types/components/sidePanel/btw/BtwPanelAdapter.js +59 -15
  306. package/lib/types/components/sidePanel/btw/BtwThreadScene.d.ts.map +1 -1
  307. package/lib/types/components/sidePanel/btw/BtwThreadScene.js +4 -2
  308. package/lib/types/components/sidePanel/btw/BtwThreadView.d.ts +1 -1
  309. package/lib/types/components/sidePanel/btw/BtwThreadView.d.ts.map +1 -1
  310. package/lib/types/components/sidePanel/btw/BtwThreadView.js +12 -6
  311. package/lib/types/components/sidePanel/companion/CompanionPanel.js +2 -2
  312. package/lib/types/components/sidePanel/types.d.ts +1 -1
  313. package/lib/types/components/sidePanel/types.d.ts.map +1 -1
  314. package/lib/types/components/sidePanel/useSidePanel.d.ts +2 -0
  315. package/lib/types/components/sidePanel/useSidePanel.d.ts.map +1 -1
  316. package/lib/types/components/sidePanel/useSidePanel.js +11 -1
  317. package/lib/types/components/splashFonts.d.ts +24 -1
  318. package/lib/types/components/splashFonts.d.ts.map +1 -1
  319. package/lib/types/components/splashFonts.js +40 -5
  320. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +3 -1
  321. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -1
  322. package/lib/types/components/workspaces/HomeWorkspaceRow.js +5 -2
  323. package/lib/types/dsh-adapter/admission-loader.d.ts +20 -0
  324. package/lib/types/dsh-adapter/admission-loader.d.ts.map +1 -0
  325. package/lib/types/dsh-adapter/admission-loader.js +256 -0
  326. package/lib/types/dsh-adapter/backend/translate.d.ts.map +1 -1
  327. package/lib/types/dsh-adapter/backend/translate.js +3 -0
  328. package/lib/types/dsh-adapter/backend-admission.d.ts +64 -0
  329. package/lib/types/dsh-adapter/backend-admission.d.ts.map +1 -0
  330. package/lib/types/dsh-adapter/backend-admission.js +34 -0
  331. package/lib/types/dsh-adapter/backend-contribution.d.ts +26 -0
  332. package/lib/types/dsh-adapter/backend-contribution.d.ts.map +1 -0
  333. package/lib/types/dsh-adapter/backend-contribution.js +37 -0
  334. package/lib/types/dsh-adapter/backend-registry.d.ts +136 -0
  335. package/lib/types/dsh-adapter/backend-registry.d.ts.map +1 -0
  336. package/lib/types/dsh-adapter/backend-registry.js +285 -0
  337. package/lib/types/dsh-adapter/backends.d.ts +67 -24
  338. package/lib/types/dsh-adapter/backends.d.ts.map +1 -1
  339. package/lib/types/dsh-adapter/backends.generated.d.ts +12 -0
  340. package/lib/types/dsh-adapter/backends.generated.d.ts.map +1 -0
  341. package/lib/types/dsh-adapter/backends.generated.js +6 -0
  342. package/lib/types/dsh-adapter/backends.js +92 -46
  343. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +2 -0
  344. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  345. package/lib/types/dsh-adapter/channel/action-readiness.js +9 -3
  346. package/lib/types/dsh-adapter/channel/agent-view-projection.js +2 -2
  347. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  348. package/lib/types/dsh-adapter/channel/background-action.js +8 -3
  349. package/lib/types/dsh-adapter/channel/binding-events.d.ts +16 -3
  350. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  351. package/lib/types/dsh-adapter/channel/binding-events.js +18 -46
  352. package/lib/types/dsh-adapter/channel/channel-host.d.ts +45 -0
  353. package/lib/types/dsh-adapter/channel/channel-host.d.ts.map +1 -0
  354. package/lib/types/dsh-adapter/channel/channel-host.js +1 -0
  355. package/lib/types/dsh-adapter/channel/command-completions.d.ts +5 -0
  356. package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -1
  357. package/lib/types/dsh-adapter/channel/command-completions.js +16 -0
  358. package/lib/types/dsh-adapter/channel/composer-images.d.ts +4 -2
  359. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  360. package/lib/types/dsh-adapter/channel/composer-images.js +36 -5
  361. package/lib/types/dsh-adapter/channel/cordis-host.d.ts +14 -0
  362. package/lib/types/dsh-adapter/channel/cordis-host.d.ts.map +1 -0
  363. package/lib/types/dsh-adapter/channel/cordis-host.js +35 -0
  364. package/lib/types/dsh-adapter/channel/core/actions.d.ts +3 -3
  365. package/lib/types/dsh-adapter/channel/core/actions.d.ts.map +1 -1
  366. package/lib/types/dsh-adapter/channel/core/actions.js +15 -6
  367. package/lib/types/dsh-adapter/channel/core/binding-feed.d.ts +7 -3
  368. package/lib/types/dsh-adapter/channel/core/binding-feed.d.ts.map +1 -1
  369. package/lib/types/dsh-adapter/channel/core/binding-feed.js +3 -0
  370. package/lib/types/dsh-adapter/channel/core/compose.d.ts +12 -28
  371. package/lib/types/dsh-adapter/channel/core/compose.d.ts.map +1 -1
  372. package/lib/types/dsh-adapter/channel/core/compose.js +318 -117
  373. package/lib/types/dsh-adapter/channel/core/files.d.ts +7 -2
  374. package/lib/types/dsh-adapter/channel/core/files.d.ts.map +1 -1
  375. package/lib/types/dsh-adapter/channel/core/files.js +1 -0
  376. package/lib/types/dsh-adapter/channel/core/host.d.ts +12 -6
  377. package/lib/types/dsh-adapter/channel/core/host.d.ts.map +1 -1
  378. package/lib/types/dsh-adapter/channel/core/host.js +69 -35
  379. package/lib/types/dsh-adapter/channel/core/local-actions.d.ts +5 -3
  380. package/lib/types/dsh-adapter/channel/core/local-actions.d.ts.map +1 -1
  381. package/lib/types/dsh-adapter/channel/core/local-actions.js +5 -2
  382. package/lib/types/dsh-adapter/channel/core/session-controls.js +2 -2
  383. package/lib/types/dsh-adapter/channel/core/session-switch.d.ts +7 -4
  384. package/lib/types/dsh-adapter/channel/core/session-switch.d.ts.map +1 -1
  385. package/lib/types/dsh-adapter/channel/core/session-switch.js +8 -4
  386. package/lib/types/dsh-adapter/channel/core/sessions.d.ts +1 -1
  387. package/lib/types/dsh-adapter/channel/core/sessions.d.ts.map +1 -1
  388. package/lib/types/dsh-adapter/channel/core/sessions.js +75 -16
  389. package/lib/types/dsh-adapter/channel/extensions.d.ts +4 -3
  390. package/lib/types/dsh-adapter/channel/extensions.d.ts.map +1 -1
  391. package/lib/types/dsh-adapter/channel/extensions.js +41 -22
  392. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +2 -2
  393. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  394. package/lib/types/dsh-adapter/channel/input-delivery.js +14 -5
  395. package/lib/types/dsh-adapter/channel/job-projection.d.ts +4 -0
  396. package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
  397. package/lib/types/dsh-adapter/channel/job-projection.js +205 -24
  398. package/lib/types/dsh-adapter/channel/mentions.d.ts +3 -3
  399. package/lib/types/dsh-adapter/channel/mentions.d.ts.map +1 -1
  400. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +1 -0
  401. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
  402. package/lib/types/dsh-adapter/channel/mode-permission-actions.js +138 -8
  403. package/lib/types/dsh-adapter/channel/model-actions.d.ts +5 -1
  404. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  405. package/lib/types/dsh-adapter/channel/model-actions.js +40 -28
  406. package/lib/types/dsh-adapter/channel/model-selection.d.ts +17 -0
  407. package/lib/types/dsh-adapter/channel/model-selection.d.ts.map +1 -0
  408. package/lib/types/dsh-adapter/channel/model-selection.js +109 -0
  409. package/lib/types/dsh-adapter/channel/model-switch.d.ts +12 -21
  410. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  411. package/lib/types/dsh-adapter/channel/model-switch.js +39 -102
  412. package/lib/types/dsh-adapter/channel/projection.js +1 -1
  413. package/lib/types/dsh-adapter/channel/reports.d.ts +1 -1
  414. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  415. package/lib/types/dsh-adapter/channel/reports.js +11 -3
  416. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  417. package/lib/types/dsh-adapter/channel/session-fork.js +33 -20
  418. package/lib/types/dsh-adapter/channel/session-listing.d.ts +20 -0
  419. package/lib/types/dsh-adapter/channel/session-listing.d.ts.map +1 -0
  420. package/lib/types/dsh-adapter/channel/session-listing.js +51 -0
  421. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +3 -1
  422. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  423. package/lib/types/dsh-adapter/channel/session-metadata.js +56 -27
  424. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  425. package/lib/types/dsh-adapter/channel/session-resume.js +15 -8
  426. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  427. package/lib/types/dsh-adapter/channel/session-rewind.js +38 -24
  428. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  429. package/lib/types/dsh-adapter/channel/session-tree-actions.js +38 -24
  430. package/lib/types/dsh-adapter/channel/settings-host.d.ts +2 -2
  431. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  432. package/lib/types/dsh-adapter/channel/settings-host.js +1 -0
  433. package/lib/types/dsh-adapter/channel/state.d.ts +37 -1
  434. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  435. package/lib/types/dsh-adapter/channel/state.js +14 -1
  436. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  437. package/lib/types/dsh-adapter/channel/subagent-projection.js +66 -6
  438. package/lib/types/dsh-adapter/channel/task-completion.d.ts +6 -0
  439. package/lib/types/dsh-adapter/channel/task-completion.d.ts.map +1 -0
  440. package/lib/types/dsh-adapter/channel/task-completion.js +114 -0
  441. package/lib/types/dsh-adapter/channel/transcript.d.ts +1 -1
  442. package/lib/types/dsh-adapter/channel/transcript.js +1 -1
  443. package/lib/types/dsh-adapter/channel/upstream-retry.d.ts +81 -0
  444. package/lib/types/dsh-adapter/channel/upstream-retry.d.ts.map +1 -0
  445. package/lib/types/dsh-adapter/channel/upstream-retry.js +138 -0
  446. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  447. package/lib/types/dsh-adapter/channel/workspace-actions.js +9 -8
  448. package/lib/types/dsh-adapter/channel-ui.d.ts +3 -1
  449. package/lib/types/dsh-adapter/channel-ui.d.ts.map +1 -1
  450. package/lib/types/dsh-adapter/channel-ui.js +4 -2
  451. package/lib/types/dsh-adapter/channel.d.ts +2 -2
  452. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  453. package/lib/types/dsh-adapter/channel.js +19 -5
  454. package/lib/types/dsh-adapter/context-occupancy.d.ts +81 -4
  455. package/lib/types/dsh-adapter/context-occupancy.d.ts.map +1 -1
  456. package/lib/types/dsh-adapter/context-occupancy.js +108 -17
  457. package/lib/types/dsh-adapter/contract.d.ts +1 -1
  458. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  459. package/lib/types/dsh-adapter/contract.js +15 -0
  460. package/lib/types/dsh-adapter/entry-slot.d.ts +36 -0
  461. package/lib/types/dsh-adapter/entry-slot.d.ts.map +1 -0
  462. package/lib/types/dsh-adapter/entry-slot.js +28 -0
  463. package/lib/types/dsh-adapter/fresh-agent.d.ts +22 -0
  464. package/lib/types/dsh-adapter/fresh-agent.d.ts.map +1 -0
  465. package/lib/types/dsh-adapter/fresh-agent.js +221 -0
  466. package/lib/types/dsh-adapter/host-access.d.ts +16 -1
  467. package/lib/types/dsh-adapter/host-access.d.ts.map +1 -1
  468. package/lib/types/dsh-adapter/host-access.js +53 -2
  469. package/lib/types/dsh-adapter/host-contract.d.ts +103 -0
  470. package/lib/types/dsh-adapter/host-contract.d.ts.map +1 -0
  471. package/lib/types/dsh-adapter/host-contract.js +76 -0
  472. package/lib/types/dsh-adapter/host-dsh.d.ts +106 -0
  473. package/lib/types/dsh-adapter/host-dsh.d.ts.map +1 -0
  474. package/lib/types/dsh-adapter/host-dsh.js +466 -0
  475. package/lib/types/dsh-adapter/host-entry.d.ts +9 -0
  476. package/lib/types/dsh-adapter/host-entry.d.ts.map +1 -0
  477. package/lib/types/dsh-adapter/host-entry.js +228 -0
  478. package/lib/types/dsh-adapter/index.d.ts +55 -18
  479. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  480. package/lib/types/dsh-adapter/index.js +59 -7
  481. package/lib/types/dsh-adapter/install/executors.d.ts +44 -0
  482. package/lib/types/dsh-adapter/install/executors.d.ts.map +1 -0
  483. package/lib/types/dsh-adapter/install/executors.js +15 -0
  484. package/lib/types/dsh-adapter/install/pnpm-profile-add.d.ts +3 -0
  485. package/lib/types/dsh-adapter/install/pnpm-profile-add.d.ts.map +1 -0
  486. package/lib/types/dsh-adapter/install/pnpm-profile-add.js +150 -0
  487. package/lib/types/dsh-adapter/jobs.d.ts +7 -0
  488. package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
  489. package/lib/types/dsh-adapter/jobs.js +13 -0
  490. package/lib/types/dsh-adapter/lite-profile.d.ts +50 -0
  491. package/lib/types/dsh-adapter/lite-profile.d.ts.map +1 -0
  492. package/lib/types/dsh-adapter/lite-profile.js +49 -0
  493. package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts +1 -1
  494. package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts.map +1 -1
  495. package/lib/types/dsh-adapter/migrate/parse/jsonl.js +4 -3
  496. package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -1
  497. package/lib/types/dsh-adapter/oauth/profiles.js +5 -1
  498. package/lib/types/dsh-adapter/panels.d.ts +7 -0
  499. package/lib/types/dsh-adapter/panels.d.ts.map +1 -1
  500. package/lib/types/dsh-adapter/panels.js +35 -2
  501. package/lib/types/dsh-adapter/plugin-completions.d.ts +32 -0
  502. package/lib/types/dsh-adapter/plugin-completions.d.ts.map +1 -0
  503. package/lib/types/dsh-adapter/plugin-completions.js +80 -0
  504. package/lib/types/dsh-adapter/plugin-declaration.d.ts +21 -0
  505. package/lib/types/dsh-adapter/plugin-declaration.d.ts.map +1 -0
  506. package/lib/types/dsh-adapter/plugin-declaration.js +53 -0
  507. package/lib/types/dsh-adapter/plugin-host.d.ts +13 -2
  508. package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
  509. package/lib/types/dsh-adapter/plugin-host.js +34 -8
  510. package/lib/types/dsh-adapter/plugin-manager.d.ts +68 -0
  511. package/lib/types/dsh-adapter/plugin-manager.d.ts.map +1 -0
  512. package/lib/types/dsh-adapter/plugin-manager.js +51 -0
  513. package/lib/types/dsh-adapter/plugin.d.ts +50 -1
  514. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  515. package/lib/types/dsh-adapter/plugin.js +870 -233
  516. package/lib/types/dsh-adapter/plugins-info.d.ts +44 -8
  517. package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -1
  518. package/lib/types/dsh-adapter/plugins-info.js +354 -34
  519. package/lib/types/dsh-adapter/process-exit.d.ts +43 -0
  520. package/lib/types/dsh-adapter/process-exit.d.ts.map +1 -0
  521. package/lib/types/dsh-adapter/process-exit.js +60 -0
  522. package/lib/types/dsh-adapter/root-dispose.d.ts +18 -0
  523. package/lib/types/dsh-adapter/root-dispose.d.ts.map +1 -0
  524. package/lib/types/dsh-adapter/root-dispose.js +28 -0
  525. package/lib/types/dsh-adapter/session-list-metadata.d.ts +20 -0
  526. package/lib/types/dsh-adapter/session-list-metadata.d.ts.map +1 -0
  527. package/lib/types/dsh-adapter/session-list-metadata.js +56 -0
  528. package/lib/types/dsh-adapter/session-seed.d.ts +11 -0
  529. package/lib/types/dsh-adapter/session-seed.d.ts.map +1 -0
  530. package/lib/types/dsh-adapter/session-seed.js +47 -0
  531. package/lib/types/dsh-adapter/sessions/list.d.ts +3 -1
  532. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
  533. package/lib/types/dsh-adapter/sessions/list.js +11 -5
  534. package/lib/types/dsh-adapter/sessions/snapshot.d.ts +0 -2
  535. package/lib/types/dsh-adapter/sessions/snapshot.d.ts.map +1 -1
  536. package/lib/types/dsh-adapter/sessions/snapshot.js +7 -70
  537. package/lib/types/dsh-adapter/startup-args.d.ts +2 -0
  538. package/lib/types/dsh-adapter/startup-args.d.ts.map +1 -1
  539. package/lib/types/dsh-adapter/startup-args.js +5 -0
  540. package/lib/types/dsh-adapter/subagents.d.ts +33 -1
  541. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  542. package/lib/types/dsh-adapter/subagents.js +110 -8
  543. package/lib/types/dsh-adapter/tui-settings.d.ts +57 -0
  544. package/lib/types/dsh-adapter/tui-settings.d.ts.map +1 -0
  545. package/lib/types/dsh-adapter/tui-settings.js +199 -0
  546. package/lib/types/effortPrefs.d.ts +6 -3
  547. package/lib/types/effortPrefs.d.ts.map +1 -1
  548. package/lib/types/effortPrefs.js +7 -4
  549. package/lib/types/hostEntryRoute.d.ts +18 -0
  550. package/lib/types/hostEntryRoute.d.ts.map +1 -0
  551. package/lib/types/hostEntryRoute.js +57 -0
  552. package/lib/types/i18n.d.ts +363 -1432
  553. package/lib/types/i18n.d.ts.map +1 -1
  554. package/lib/types/i18n.js +155 -385
  555. package/lib/types/ink/components/AlternateScreen.js +2 -2
  556. package/lib/types/ink/components/App.d.ts +1 -0
  557. package/lib/types/ink/components/App.d.ts.map +1 -1
  558. package/lib/types/ink/components/App.js +16 -4
  559. package/lib/types/ink/components/CursorDeclarationContext.d.ts +9 -0
  560. package/lib/types/ink/components/CursorDeclarationContext.d.ts.map +1 -1
  561. package/lib/types/ink/components/CursorDeclarationContext.js +2 -0
  562. package/lib/types/ink/hooks/use-copy-on-select.d.ts +2 -2
  563. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  564. package/lib/types/ink/hooks/use-copy-on-select.js +13 -7
  565. package/lib/types/ink/hooks/use-declared-cursor.d.ts +11 -3
  566. package/lib/types/ink/hooks/use-declared-cursor.d.ts.map +1 -1
  567. package/lib/types/ink/hooks/use-declared-cursor.js +30 -10
  568. package/lib/types/ink/ink.d.ts +22 -17
  569. package/lib/types/ink/ink.d.ts.map +1 -1
  570. package/lib/types/ink/ink.js +151 -40
  571. package/lib/types/ink/kitty-graphics.d.ts +2 -0
  572. package/lib/types/ink/kitty-graphics.d.ts.map +1 -1
  573. package/lib/types/ink/kitty-graphics.js +6 -1
  574. package/lib/types/ink/node-cache.d.ts +2 -1
  575. package/lib/types/ink/node-cache.d.ts.map +1 -1
  576. package/lib/types/ink/output.d.ts +10 -4
  577. package/lib/types/ink/output.d.ts.map +1 -1
  578. package/lib/types/ink/output.js +14 -10
  579. package/lib/types/ink/render-node-to-output.d.ts +5 -2
  580. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  581. package/lib/types/ink/render-node-to-output.js +27 -9
  582. package/lib/types/ink/renderer.d.ts +1 -0
  583. package/lib/types/ink/renderer.d.ts.map +1 -1
  584. package/lib/types/ink/renderer.js +4 -3
  585. package/lib/types/ink/root.d.ts +4 -1
  586. package/lib/types/ink/root.d.ts.map +1 -1
  587. package/lib/types/ink/screen.d.ts +4 -2
  588. package/lib/types/ink/screen.d.ts.map +1 -1
  589. package/lib/types/ink/selection.d.ts +10 -1
  590. package/lib/types/ink/selection.d.ts.map +1 -1
  591. package/lib/types/ink/selection.js +94 -25
  592. package/lib/types/ink/stdin-handoff.d.ts +11 -0
  593. package/lib/types/ink/stdin-handoff.d.ts.map +1 -0
  594. package/lib/types/ink/stdin-handoff.js +20 -0
  595. package/lib/types/ink/styles.d.ts +2 -1
  596. package/lib/types/ink/styles.d.ts.map +1 -1
  597. package/lib/types/ink/terminal-image.d.ts +6 -4
  598. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  599. package/lib/types/ink/terminal-image.js +16 -39
  600. package/lib/types/ink/terminal-notification.d.ts +8 -0
  601. package/lib/types/ink/terminal-notification.d.ts.map +1 -0
  602. package/lib/types/ink/terminal-notification.js +83 -0
  603. package/lib/types/ink/terminal.d.ts +9 -0
  604. package/lib/types/ink/terminal.d.ts.map +1 -1
  605. package/lib/types/ink/terminal.js +34 -1
  606. package/lib/types/ink/termio/csi.d.ts +11 -0
  607. package/lib/types/ink/termio/csi.d.ts.map +1 -1
  608. package/lib/types/ink/termio/csi.js +13 -0
  609. package/lib/types/ink/update-overflow-guard.d.ts +6 -0
  610. package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
  611. package/lib/types/ink/update-overflow-guard.js +17 -0
  612. package/lib/types/ink/wrap-text.d.ts +6 -0
  613. package/lib/types/ink/wrap-text.d.ts.map +1 -1
  614. package/lib/types/ink/wrap-text.js +17 -0
  615. package/lib/types/kernelPrefs.d.ts +129 -16
  616. package/lib/types/kernelPrefs.d.ts.map +1 -1
  617. package/lib/types/kernelPrefs.js +98 -35
  618. package/lib/types/math/inline-layout.d.ts +2 -0
  619. package/lib/types/math/inline-layout.d.ts.map +1 -1
  620. package/lib/types/math/inline-layout.js +5 -27
  621. package/lib/types/modelGroups.d.ts +15 -27
  622. package/lib/types/modelGroups.d.ts.map +1 -1
  623. package/lib/types/modelGroups.js +17 -31
  624. package/lib/types/modelPrefs.d.ts +5 -4
  625. package/lib/types/modelPrefs.d.ts.map +1 -1
  626. package/lib/types/modelPrefs.js +5 -4
  627. package/lib/types/modelRoute.d.ts +23 -9
  628. package/lib/types/modelRoute.d.ts.map +1 -1
  629. package/lib/types/modelRoute.js +25 -11
  630. package/lib/types/permissionPrefs.d.ts +34 -0
  631. package/lib/types/permissionPrefs.d.ts.map +1 -0
  632. package/lib/types/permissionPrefs.js +61 -0
  633. package/lib/types/plugin-host.d.ts +1 -0
  634. package/lib/types/plugin-host.d.ts.map +1 -1
  635. package/lib/types/presetPrefs.d.ts +17 -3
  636. package/lib/types/presetPrefs.d.ts.map +1 -1
  637. package/lib/types/presetPrefs.js +20 -3
  638. package/lib/types/reload.d.ts +6 -10
  639. package/lib/types/reload.d.ts.map +1 -1
  640. package/lib/types/reload.js +14 -14
  641. package/lib/types/screens/Chat.d.ts +22 -15
  642. package/lib/types/screens/Chat.d.ts.map +1 -1
  643. package/lib/types/screens/Chat.js +442 -272
  644. package/lib/types/screens/Launchpad.d.ts +27 -17
  645. package/lib/types/screens/Launchpad.d.ts.map +1 -1
  646. package/lib/types/screens/Launchpad.js +241 -78
  647. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
  648. package/lib/types/screens/SessionSupervisor.js +53 -12
  649. package/lib/types/screens/Settings.d.ts.map +1 -1
  650. package/lib/types/screens/Settings.js +6 -0
  651. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  652. package/lib/types/screens/StatusLine.js +24 -7
  653. package/lib/types/screens/StatusMetrics.d.ts +46 -1
  654. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  655. package/lib/types/screens/StatusMetrics.js +72 -0
  656. package/lib/types/screens/chat/useKernelPicker.d.ts +8 -4
  657. package/lib/types/screens/chat/useKernelPicker.d.ts.map +1 -1
  658. package/lib/types/screens/chat/useKernelPicker.js +61 -21
  659. package/lib/types/screens/chat/useModelPicker.d.ts +42 -0
  660. package/lib/types/screens/chat/useModelPicker.d.ts.map +1 -0
  661. package/lib/types/screens/chat/useModelPicker.js +182 -0
  662. package/lib/types/screens/chatOverlay.d.ts +11 -4
  663. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  664. package/lib/types/screens/chatOverlay.js +0 -1
  665. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts +4 -1
  666. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts.map +1 -1
  667. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.js +26 -5
  668. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +1 -1
  669. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
  670. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +51 -29
  671. package/lib/types/sessionHistory.d.ts +5 -0
  672. package/lib/types/sessionHistory.d.ts.map +1 -1
  673. package/lib/types/sessionHistory.js +5 -0
  674. package/lib/types/sessions/listSnapshot.d.ts +6 -0
  675. package/lib/types/sessions/listSnapshot.d.ts.map +1 -0
  676. package/lib/types/sessions/listSnapshot.js +67 -0
  677. package/lib/types/settings/definitions.d.ts +50 -0
  678. package/lib/types/settings/definitions.d.ts.map +1 -1
  679. package/lib/types/settings/definitions.js +42 -8
  680. package/lib/types/terminal-utils/cjk-emphasis.d.ts +28 -0
  681. package/lib/types/terminal-utils/cjk-emphasis.d.ts.map +1 -0
  682. package/lib/types/terminal-utils/cjk-emphasis.js +31 -0
  683. package/lib/types/terminal-utils/markdown.d.ts +3 -1
  684. package/lib/types/terminal-utils/markdown.d.ts.map +1 -1
  685. package/lib/types/terminal-utils/markdown.js +7 -2
  686. package/lib/types/theme.d.ts +29 -12
  687. package/lib/types/theme.d.ts.map +1 -1
  688. package/lib/types/theme.js +310 -23
  689. package/lib/types/themeCatalog.d.ts.map +1 -1
  690. package/lib/types/themeCatalog.js +21 -2
  691. package/lib/types/tips.js +4 -4
  692. package/lib/types/tuiSettingsFile.d.ts +56 -0
  693. package/lib/types/tuiSettingsFile.d.ts.map +1 -0
  694. package/lib/types/tuiSettingsFile.js +206 -0
  695. package/lib/types/ui.d.ts +2 -0
  696. package/lib/types/ui.d.ts.map +1 -1
  697. package/lib/types/ui.js +2 -0
  698. package/lib/types/update.d.ts +72 -26
  699. package/lib/types/update.d.ts.map +1 -1
  700. package/lib/types/update.js +131 -43
  701. package/lib/types/utils/bmp.d.ts +7 -0
  702. package/lib/types/utils/bmp.d.ts.map +1 -1
  703. package/lib/types/utils/bmp.js +27 -0
  704. package/lib/types/utils/bootTrace.d.ts +2 -0
  705. package/lib/types/utils/bootTrace.d.ts.map +1 -0
  706. package/lib/types/utils/bootTrace.js +20 -0
  707. package/lib/types/utils/clipboard.d.ts.map +1 -1
  708. package/lib/types/utils/clipboard.js +11 -3
  709. package/lib/types/utils/credentials.d.ts +14 -2
  710. package/lib/types/utils/credentials.d.ts.map +1 -1
  711. package/lib/types/utils/credentials.js +34 -11
  712. package/lib/types/utils/imageResize.d.ts +10 -20
  713. package/lib/types/utils/imageResize.d.ts.map +1 -1
  714. package/lib/types/utils/imageResize.js +61 -134
  715. package/lib/types/utils/inputPaste.d.ts +2 -2
  716. package/lib/types/utils/inputPaste.js +2 -2
  717. package/lib/types/utils/pastedImagePath.d.ts +5 -0
  718. package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
  719. package/lib/types/utils/pastedImagePath.js +33 -0
  720. package/package.json +36 -6
  721. package/presets/winbash.mjs +716 -0
  722. package/tui-profile/protocols/profile-definitions.d.ts +1 -0
  723. package/tui-profile/protocols/profile-definitions.js +14 -0
  724. package/tui-profile/protocols/tui-contributions.d.ts +55 -0
  725. package/tui-profile/protocols/tui-contributions.js +141 -0
  726. package/tui-profile/registry/contracts/backend-v1alpha1.json +13 -0
  727. package/tui-profile/registry/registry-0.15.json +12 -0
  728. package/lib/types/backends/claude/install.d.ts +0 -15
  729. package/lib/types/backends/claude/install.d.ts.map +0 -1
  730. package/lib/types/backends/claude/install.js +0 -91
  731. package/lib/types/channel/elicitation.d.ts.map +0 -1
  732. package/lib/types/dsh-adapter/channel/session-lineage.d.ts +0 -42
  733. package/lib/types/dsh-adapter/channel/session-lineage.d.ts.map +0 -1
  734. package/lib/types/dsh-adapter/channel/session-lineage.js +0 -51
  735. package/lib/types/utils/jsonl.d.ts.map +0 -1
@@ -4,12 +4,17 @@ import { SessionId } from '@deepseek-ai/dsh-session';
4
4
  import UserQuestionService from '@deepseek-ai/dsh-user-questions';
5
5
  import * as toolAskUser from '@deepseek-ai/dsh-tool-ask-user';
6
6
  import Schema from '@deepseek-ai/schemastery';
7
- import { Config, normalizeBackendChoice } from './index.js';
7
+ import { Config } from './index.js';
8
8
  import { configValues, createSettingsScope, resolveSettingsNamespace } from './compat/settings.js';
9
9
  import { createChannel } from './channel.js';
10
10
  import { createDshSession } from './backend/session.js';
11
- import { BACKEND_LOADERS, closeBackendResources, openBackendStartup, probeKernels, sdkInstall } from './backends.js';
11
+ import { createTuiSettingsService } from './tui-settings.js';
12
+ import { EDITABLE_CONFIG_KEYS } from '../settings/definitions.js';
13
+ import { kernelEntriesOf } from '../components/kernelCatalog.js';
14
+ import { openBackendStartup, prepareBackendStartup, probeKernels, installSurfaceFor } from './backends.js';
15
+ import { backendLabel, isBackendIdSyntax, isRegisteredBackend, listBackends, listOfferedBackends, loadBackend, parseBackendChoice, unloadBackends } from './backend-registry.js';
12
16
  import { formatSessionRef } from '../agent/refs.js';
17
+ import { createStartingSession } from '../agent/starting-session.js';
13
18
  import { createChannelSceneOutlet } from './channel-scene-outlet.js';
14
19
  import { mountChannelUi } from './channel-ui.js';
15
20
  import { bindChannelCommands } from './channel/commands.js';
@@ -18,6 +23,7 @@ import { createChildStderrReporter, installChildStderrGuard } from './childStder
18
23
  import { removeClipboardImageDir } from '../utils/clipboard.js';
19
24
  import { appendCrashLog, serializeCrashDetail, unserializableCrashDetail } from '../utils/crashDetail.js';
20
25
  import { logForDebugging } from '../utils/debug.js';
26
+ import { createTerminalNotifier } from '../ink/terminal-notification.js';
21
27
  import { isEnvTruthy } from '../utils/envUtils.js';
22
28
  import { QuestionStore, bindQuestionStore } from './questions.js';
23
29
  import { prepareQuestionAnswerer } from './questions-answerer.js';
@@ -29,23 +35,24 @@ import { readActivityFrames } from '../activityPrefs.js';
29
35
  import { commitFullscreenFactoryMigration, planFullscreenFactoryMigration, readAppliedMigrations } from '../migrationPrefs.js';
30
36
  import { readModelPref } from '../modelPrefs.js';
31
37
  import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../modelRoute.js';
32
- import { migratePresetPref, readPresetPref } from '../presetPrefs.js';
38
+ import { migratePresetPref, presetOverrideFromEnv, readPresetPref } from '../presetPrefs.js';
33
39
  import { readEffortPref } from '../effortPrefs.js';
34
40
  import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, resolvePersistedRoute, runningPresetOf } from './presets.js';
41
+ import { createFreshAgent, isUnstoredFreshSession } from './fresh-agent.js';
35
42
  import { ensurePackagedPresets } from './packaged-presets.js';
36
43
  import { registerBundledPresets } from './bundled-presets.js';
37
44
  import { ensureLegacySessionEventTypes, snapshotLiveSessionEvents } from './compat/index.js';
38
- import { clearResumeTarget, resumeTargetFromArgv, writeResumeTarget } from '../sessionHistory.js';
39
- import { initialPromptFromCmdlineArgs } from './startup-args.js';
45
+ import { clearResumeTarget, resumeTargetFromArgv, stripResumeArgs, writeResumeTarget } from '../sessionHistory.js';
46
+ import { cmdlineArgsOf, initialPromptFromCmdlineArgs } from './startup-args.js';
40
47
  import { readHomePrefs } from '../homePrefs.js';
41
48
  import { handoffEventTag, formatHandoffNotice } from '../handoffEvents.js';
42
49
  import { armFirstFrameAck, beginHandoffAck, handoffAttemptId, ownsAltScreenExit } from '../handoffAck.js';
43
- import { KERNEL_IDS, KERNEL_SWITCH_HANDOFF_ENV, kernelDisplayName, readKernelPrefs, resolveRememberedBackend, writeKernelPrefs } from '../kernelPrefs.js';
50
+ import { DSH_BACKEND_ID, HOST_NOTICE_ENV, KERNEL_SWITCH_HANDOFF_ENV, RESUME_BACKEND_ENV, RESUME_RETRY_ENV, parseBackendId, readKernelPrefs, resolveRememberedBackend, resolveResumeTarget, writeKernelPrefs } from '../kernelPrefs.js';
44
51
  import { shouldOfferOnboarding } from '../onboardingPrefs.js';
45
52
  import { resolveSessionCwd } from '../utils/workspaceRoot.js';
46
- import { beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice, writeLastRunRecord } from '../update.js';
53
+ import { LAUNCH_PROMPT_SENT_ENV, beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice, writeLastRunRecord } from '../update.js';
47
54
  import { getLang, isLang, resolveStartupLang, setLang, t, writeLangPref } from '../i18n.js';
48
- import { applyBtwContextBudget, applyBtwContextTurns, applyCodeFrameStyle, applyCompanionSkin, applyImageBacking, applyMathImageBacking, applyMathImageScale, applyMathRendering, applyMermaidDiagrams, applyPageMargin, applySidePanelOpen, applySidePanelPanels, applySidePanelRatio, applySidePanelSplitEnabled, BTW_CONTEXT_BUDGET_MAX, BTW_CONTEXT_BUDGET_MIN, BTW_CONTEXT_TURNS_MAX, BTW_CONTEXT_TURNS_MIN, DEFAULT_PAGE_MARGIN, DEFAULT_SIDE_PANEL_IDS, DEFAULT_STATUS_BAR, isPageMarginMode, normalizeJobGroupFold, normalizePageMargin, normalizeScrollGutter, normalizeSidePanelPanels, normalizeStatusBar, normalizeToolBackground, parsePageMarginSpec, resolveMathRendering, SIDE_PANEL_ID_PATTERN } from '../tuiDisplayPrefs.js';
55
+ import { applyBtwContextBudget, applyBtwContextTurns, applyCodeFrameStyle, applyCompanionSkin, applyImageBacking, applyMathImageBacking, applyMathImageScale, applyMathRendering, applyMermaidDiagrams, applyPageMargin, applySidePanelOpen, applySidePanelRatio, applySidePanelSplitEnabled, BTW_CONTEXT_BUDGET_MAX, BTW_CONTEXT_BUDGET_MIN, BTW_CONTEXT_TURNS_MAX, BTW_CONTEXT_TURNS_MIN, DEFAULT_PAGE_MARGIN, DEFAULT_SIDE_PANEL_IDS, DEFAULT_STATUS_BAR, isPageMarginMode, normalizeJobGroupFold, normalizePageMargin, normalizeScrollGutter, normalizeSidePanelPanels, normalizeStatusBar, normalizeToolBackground, parsePageMarginSpec, resolveMathRendering, SIDE_PANEL_ID_PATTERN } from '../tuiDisplayPrefs.js';
49
56
  import { draftComboConflicts, effectiveComboString, parseComboDraft, setKeymapOverrides, SHORTCUT_ACTIONS, } from '../utils/keymap.js';
50
57
  import { attachHerdrIntegration } from '../herdr.js';
51
58
  import { logMouseDebug } from '../utils/debug.js';
@@ -55,6 +62,7 @@ import { startSessionMountHeartbeat } from './session-mount-heartbeat.js';
55
62
  import { reserveMount } from '../sessionMounts.js';
56
63
  import { getHostDialogStore } from './dialogs.js';
57
64
  import { getHostStatusStore } from './status.js';
65
+ import { attachSessionListMetadata } from './session-list-metadata.js';
58
66
  import { createActivityStore } from './activity-store.js';
59
67
  import { createContextOccupancyStore } from './context-occupancy.js';
60
68
  import { getHostToastStore } from './toast.js';
@@ -64,6 +72,9 @@ import { attachSessionToWorkspace } from './workspace.js';
64
72
  import { createLocalWorkspaceRuntime, getHostWorkspaceRuntime } from './workspaces.js';
65
73
  import { getHostSettingsSections, getLocalSettingsSectionsHost } from './settings-sections.js';
66
74
  import { compositionRoot, withHostRootCapability } from './host-access.js';
75
+ import { armAdmissionLoader } from './admission-loader.js';
76
+ import { applyConfiguredSidePanelPanels, applySidePanelPanelsFromSettings } from './panels.js';
77
+ import { disposeRootSettled } from './root-dispose.js';
67
78
  import { render, ThemeProvider, AlternateScreen } from '../ui.js';
68
79
  import { PageMargin } from '../components/PageMargin.js';
69
80
  import { normalizeSplashFont } from '../components/splashFonts.js';
@@ -73,7 +84,11 @@ import instances from '../ink/instances.js';
73
84
  import { cursorMove, DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, DISABLE_WIN32_INPUT_MODE } from '../ink/termio/csi.js';
74
85
  import { DBP, DFE, DISABLE_MOUSE_TRACKING, EXIT_ALT_SCREEN, SHOW_CURSOR } from '../ink/termio/dec.js';
75
86
  import { CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, supportsTabStatus, wrapForMultiplexer } from '../ink/termio/osc.js';
76
- import { fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update-overflow-guard.js';
87
+ import { addProcessErrorAbsorber, fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update-overflow-guard.js';
88
+ import { CHANNEL_UI_LIFETIME_ENDED } from '../adapter/channel/ui.js';
89
+ import { markBoot } from '../utils/bootTrace.js';
90
+ import { TERMINATION_SIGNALS, dieBySignal } from './process-exit.js';
91
+ import { StartupOpenError } from './channel/state.js';
77
92
  /**
78
93
  * Interactive TUI front door for DeepSeek Harness agents.
79
94
  *
@@ -98,6 +113,11 @@ import { fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update
98
113
  let lastBootedFullscreen;
99
114
  // Image preferences also stay fixed across host recomposes until /restart.
100
115
  let lastBootedTerminalImages;
116
+ // A replacement's first process already sent the launch prompt. Read once per
117
+ // process (a recompose must not resend it) and dropped from the environment,
118
+ // so a `dsh-tui "prompt"` run from inside the session keeps its own prompt.
119
+ const launchPromptSent = process.env[LAUNCH_PROMPT_SENT_ENV] === '1';
120
+ delete process.env[LAUNCH_PROMPT_SENT_ENV];
101
121
  // Kept importable from here: the startup parser moved to its own dependency-free
102
122
  // module so argv probes can load it without the whole plugin graph.
103
123
  export { initialPromptFromCmdlineArgs };
@@ -128,8 +148,10 @@ export function resolveTuiHostMode(stdoutIsTTY = process.stdout.isTTY === true,
128
148
  const explicitTuiLaunch = env.DSH_TUI_LAUNCHER_VERSION !== undefined || isStandaloneRuntime();
129
149
  return explicitTuiLaunch ? 'invalid-explicit-launch' : 'headless-host';
130
150
  }
131
- export async function apply(ctx, runtimeConfig, configOwner = ctx) {
151
+ export async function apply(ctx, runtimeConfig, configOwner = ctx, runtimeOptions = {}) {
132
152
  const config = configValues(runtimeConfig);
153
+ const taskNotifier = createTerminalNotifier(process.stdout);
154
+ taskNotifier.configure(config.taskNotifications, config.notificationMethod);
133
155
  // /restart handoff diagnosis: the replacement process is marked by env and
134
156
  // logs its boot progress to ~/.dsh-tui/restart.log (ordinary launches stay
135
157
  // silent). First line lands before anything in this function can throw.
@@ -203,21 +225,40 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
203
225
  }
204
226
  // Validate settings before creating an agent or taking over the terminal.
205
227
  const tuiSettingsNs = resolveSettingsNamespace(configOwner, Config);
228
+ // Register before agent creation so its first checkpoint includes web list metadata.
229
+ attachSessionListMetadata(ctx);
230
+ // The TUI's own settings layer (~/.dsh-tui/settings.json): the same
231
+ // document on every kernel and entry. Other namespaces still go
232
+ // to the host's settings service (absent in the standalone entry).
233
+ const tuiSettings = createTuiSettingsService({
234
+ ns: tuiSettingsNs,
235
+ profile: resolveDshProfileName() ?? runtimeOptions.profile ?? 'dsh-tui',
236
+ keys: EDITABLE_CONFIG_KEYS,
237
+ delegate: () => ctx.get('settings'),
238
+ });
239
+ // The in-process DSH kernel's screen mounts before DSH has composed: what
240
+ // needs DSH's services runs later, in `attachDsh`.
241
+ const entrySlot = runtimeOptions.entrySlot;
242
+ const dshInEntry = entrySlot?.dsh === true;
206
243
  // Modern hosts own a declarative registry; old hosts discover directories.
207
244
  // A modern bundle failure must not silently fall back to obsolete files.
208
- if (!await registerBundledPresets(ctx))
209
- try {
210
- for (const result of ensurePackagedPresets()) {
211
- if (result.status === 'conflict') {
212
- ctx.logger.warn(`dsh-tui: packaged preset "${result.id}" was not installed because an unmanaged preset already uses that id`);
245
+ const installPresets = async (presetCtx) => {
246
+ if (!await registerBundledPresets(presetCtx))
247
+ try {
248
+ for (const result of ensurePackagedPresets()) {
249
+ if (result.status === 'conflict') {
250
+ presetCtx.logger.warn(`dsh-tui: packaged preset "${result.id}" was not installed because an unmanaged preset already uses that id`);
251
+ }
213
252
  }
214
253
  }
215
- }
216
- catch (error) {
217
- // A read-only home must not make the whole terminal unusable; the other
218
- // official and user presets remain available.
219
- ctx.logger.warn(`dsh-tui: unable to install packaged presets (${error instanceof Error ? error.message : String(error)})`);
220
- }
254
+ catch (error) {
255
+ // A read-only home must not make the whole terminal unusable; the other
256
+ // official and user presets remain available.
257
+ presetCtx.logger.warn(`dsh-tui: unable to install packaged presets (${error instanceof Error ? error.message : String(error)})`);
258
+ }
259
+ };
260
+ if (!dshInEntry)
261
+ await installPresets(ctx);
221
262
  // UI language resolution: DSH_TUI_LANG env var wins, then the
222
263
  // settings.yaml `dsh-tui.lang` user layer (applied once the settings
223
264
  // namespace registers below), then cordis.yml `lang`, then the
@@ -263,6 +304,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
263
304
  }
264
305
  }
265
306
  }
307
+ // The questionnaire store every backend's questions park in (a non-DSH
308
+ // session's `question.request` events too); Chat renders it.
309
+ const questionStore = new QuestionStore(adapterRuntimeFor(ctx));
310
+ bindQuestionStore(ctx, questionStore);
311
+ // One store, one teardown effect on both API lines. The compatibility
312
+ // adapter binds either registration to this Cordis fiber; this separate
313
+ // effect rejects asks still parked in the UI during teardown.
314
+ ctx.effect(() => () => questionStore.rejectAll());
266
315
  // DSH user-interaction seam: the model's ask_user_question tool parks on
267
316
  // the userQuestions service until a UI answerer responds. Mount the
268
317
  // service when the composition doesn't (the official dsh-base
@@ -272,46 +321,45 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
272
321
  // seat is registered below; the 0.1.2 line's agent-aware waterfall needs the
273
322
  // channel owner and is therefore registered immediately after the channel
274
323
  // is created. Optional-service access goes through `ctx.get`, not the
275
- // inject proxy.
276
- const userQuestions = ctx.get('userQuestions') ?? new UserQuestionService(ctx);
277
- ctx.plugin(toolAskUser);
278
- // The host-level tool mount above is intentional for the TUI and for user
279
- // presets, but the official Minimal preset is a single-tool trajectory (one
280
- // persistent shell: bash on POSIX, pwsh on Windows). Filter only that preset
281
- // at the final assembly boundary. Reading the session on every assembly also
282
- // makes blank-session /preset switches and resumed sessions behave correctly.
283
- ctx.on('system-prompt/assemble', async (_assembly, context, next) => {
284
- const assembled = await next();
285
- const presetId = context.agent === undefined ? undefined : runningPresetOf(context.agent.session);
286
- return filterMinimalPresetTools(assembled, presetId);
287
- });
288
- const questionStore = new QuestionStore(adapterRuntimeFor(ctx));
289
- bindQuestionStore(ctx, questionStore);
290
- // One store, one teardown effect on both API lines. The compatibility
291
- // adapter binds either registration to this Cordis fiber; this separate
292
- // effect rejects asks still parked in the UI during teardown.
293
- ctx.effect(() => () => questionStore.rejectAll());
294
- // `/debug-prompt` snapshots the final provider-neutral request at the
295
- // llm/stream boundary, after every prompt and tool contributor has run.
296
- registerPromptDebug(ctx);
297
- // API selection and registration live behind one adapter boundary. The UI
298
- // bootstrap only translates a legacy seat conflict into its visible notice.
299
- const questionAnswererRegistration = prepareQuestionAnswerer(ctx, userQuestions, questionStore);
300
- const questionSeatDecision = questionAnswererRegistration.kind === 'legacy'
301
- ? questionAnswererRegistration.yieldDecision
302
- : undefined;
303
- let questionSeatNotice;
304
- if (questionSeatDecision?.action === 'alert-unverified') {
305
- ctx.logger.error(`dsh-tui: user-questions provider seat is held by a component self-reporting as ${questionSeatDecision.incumbentId} ` +
306
- '(identity not host-verified); this TUI will not register its questionnaire and model questions may be answered by it');
307
- questionSeatNotice = t('question-provider-occupied-unverified', { id: questionSeatDecision.incumbentId ?? '' });
308
- }
309
- else if (questionSeatDecision?.action === 'alert') {
310
- const displayId = questionSeatDecision.incumbentId ?? t('question-provider-occupied-unknown');
311
- ctx.logger.error(`dsh-tui: user-questions provider seat is held by a non-host component (${displayId}); ` +
312
- 'this TUI will not register its questionnaire and model questions may be answered by it');
313
- questionSeatNotice = t('question-provider-occupied', { id: displayId });
314
- }
324
+ // inject proxy. The in-process DSH kernel mounts these from its dsh-tui row
325
+ // once DSH has composed: mounting them on the entry's root first would
326
+ // take the `userQuestions` seat the profile's own row registers.
327
+ const mountDshQuestionSeams = (dshCtx) => {
328
+ const userQuestions = dshCtx.get('userQuestions') ?? new UserQuestionService(dshCtx);
329
+ dshCtx.plugin(toolAskUser);
330
+ // The host-level tool mount above is intentional for the TUI and for user
331
+ // presets, but the official Minimal preset is a single-tool trajectory (one
332
+ // persistent shell: bash on POSIX, pwsh on Windows). Filter only that preset
333
+ // at the final assembly boundary. Reading the session on every assembly also
334
+ // makes blank-session /preset switches and resumed sessions behave correctly.
335
+ dshCtx.on('system-prompt/assemble', async (_assembly, context, next) => {
336
+ const assembled = await next();
337
+ const presetId = context.agent === undefined ? undefined : runningPresetOf(context.agent.session);
338
+ return filterMinimalPresetTools(assembled, presetId);
339
+ });
340
+ // `/debug-prompt` snapshots the final provider-neutral request at the
341
+ // llm/stream boundary, after every prompt and tool contributor has run.
342
+ registerPromptDebug(dshCtx);
343
+ // API selection and registration live behind one adapter boundary. The UI
344
+ // bootstrap only translates a legacy seat conflict into its visible notice.
345
+ const registration = prepareQuestionAnswerer(dshCtx, userQuestions, questionStore);
346
+ const seatDecision = registration.kind === 'legacy' ? registration.yieldDecision : undefined;
347
+ let notice;
348
+ if (seatDecision?.action === 'alert-unverified') {
349
+ dshCtx.logger.error(`dsh-tui: user-questions provider seat is held by a component self-reporting as ${seatDecision.incumbentId} ` +
350
+ '(identity not host-verified); this TUI will not register its questionnaire and model questions may be answered by it');
351
+ notice = t('question-provider-occupied-unverified', { id: seatDecision.incumbentId ?? '' });
352
+ }
353
+ else if (seatDecision?.action === 'alert') {
354
+ const displayId = seatDecision.incumbentId ?? t('question-provider-occupied-unknown');
355
+ dshCtx.logger.error(`dsh-tui: user-questions provider seat is held by a non-host component (${displayId}); ` +
356
+ 'this TUI will not register its questionnaire and model questions may be answered by it');
357
+ notice = t('question-provider-occupied', { id: displayId });
358
+ }
359
+ return { registration, notice };
360
+ };
361
+ const questionSeams = dshInEntry ? undefined : mountDshQuestionSeams(ctx);
362
+ let questionSeatNotice = questionSeams?.notice;
315
363
  // Child-process stderr guard (issue #17): MCP servers spawned with an
316
364
  // inherited stderr (the MCP SDK's stdio default) write straight to the
317
365
  // terminal device from the child process, bypassing the renderer's own
@@ -344,20 +392,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
344
392
  provider: config.provider,
345
393
  model: config.model,
346
394
  };
347
- // Atomic route resolution (issue #67): a complete cordis.yml route wins
348
- // whole, else the persisted `/model` choice wins whole, else Harness's
349
- // provider-neutral agent-default-model selection. The local DeepSeek pair
350
- // remains the final fallback for bare embedders without that service. This
351
- // lets optional provider bundles supply the same default to Web and TUI
352
- // without patching this front door by name.
353
- const configuredDefault = ctx.get('agentDefaultModel')?.currentSelection?.();
354
- const harnessDefault = typeof configuredDefault?.provider === 'string'
355
- && configuredDefault.provider.length > 0
356
- && typeof configuredDefault.model === 'string'
357
- && configuredDefault.model.length > 0
358
- ? { provider: configuredDefault.provider, model: configuredDefault.model }
359
- : undefined;
360
- const startupRoute = resolveModelRoute(configuredRoute, readModelPref(), harnessDefault);
395
+ // Atomic route resolution (issue #67): the persisted `/model` choice — the
396
+ // route the last session ran — wins whole; else a complete cordis.yml route
397
+ // wins whole as the deployment DEFAULT (it decides the first run, never a
398
+ // later one); else Harness's provider-neutral agent-default-model
399
+ // selection. The local DeepSeek pair remains the final fallback for bare
400
+ // embedders without that service. This lets optional provider bundles supply
401
+ // the same default to Web and TUI without patching this front door by name.
402
+ const { startupRoute, deploymentRoute } = startupRoutes(ctx, configuredRoute);
361
403
  // Session cwd (issue #96): explicit cordis.yml `cwd` wins; otherwise the
362
404
  // git worktree root containing the launch directory (the launch directory
363
405
  // itself outside any worktree), so `@` completion and mention expansion
@@ -413,11 +455,16 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
413
455
  ctx.logger.warn('dsh-tui: tuiPluginHost service is not mounted; plugin grant store, runtime generation and Host Descriptor are unavailable. ' +
414
456
  'The bundle patch is older than the installed dsh-tui package — update the globally installed dsh-tui launcher to match the profile (issue #183).');
415
457
  }
458
+ // Admit third-party activations from their package-root `dsh-plugin.json`.
459
+ armAdmissionLoader(ctx);
460
+ // The in-process DSH kernel mounts before the profile's workspace providers
461
+ // exist: a provider URI resolves in `attachDsh`, and an unresolvable one
462
+ // fails the startup session there instead of the mount.
416
463
  const initialWorkspace = requestedWorkspace === undefined
417
464
  ? undefined
418
465
  : await workspaceService.resolve(requestedWorkspace);
419
- if (requestedWorkspace !== undefined && initialWorkspace === undefined) {
420
- throw new Error(`dsh-tui: unsupported or unavailable workspace target: ${requestedWorkspace}`);
466
+ if (requestedWorkspace !== undefined && initialWorkspace === undefined && !dshInEntry) {
467
+ throw new Error(unsupportedWorkspaceTarget(requestedWorkspace));
421
468
  }
422
469
  const sessionCwd = initialWorkspace?.cwd ?? resolveSessionCwd(config.cwd);
423
470
  const meta = { cwd: sessionCwd };
@@ -425,8 +472,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
425
472
  // `dsh --profile tui` forwards `--resume` verbatim instead, so fall back to
426
473
  // the same app-argv snapshot as the initial prompt. Raw process.argv also
427
474
  // contains the DSH launcher's own -- and is only a legacy embedder fallback.
428
- const cmdline = ctx.cmdlineArgs;
429
- const cmdlineArgs = cmdline?.get?.() ?? cmdline?.args;
475
+ const cmdlineArgs = cmdlineArgsOf(ctx);
476
+ // The raw request, deliberately self-contained: `scripts/verify-startup-argv.mjs`
477
+ // replays this very statement (picked out of the compiled `apply` by name) to check
478
+ // the launcher's argv interception end to end, so it has to stay computable from
479
+ // `config` and `cmdlineArgs` alone. Whether the target may actually be used is
480
+ // decided further down, once the backend this boot lands on is known.
430
481
  const launchSessionId = config.sessionId ?? resumeTargetFromArgv(cmdlineArgs ?? process.argv.slice(2));
431
482
  // The session's backend (Config `backend`, `dsh-tui --backend`). A non-DSH
432
483
  // backend opens its own session here and skips everything DSH-specific
@@ -447,21 +498,109 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
447
498
  const handoffBackendRaw = process.env[KERNEL_SWITCH_HANDOFF_ENV];
448
499
  if (handoffBackendRaw !== undefined)
449
500
  delete process.env[KERNEL_SWITCH_HANDOFF_ENV];
450
- const handoffBackend = normalizeBackendChoice(handoffBackendRaw);
501
+ const handoffBackend = parseBackendChoice(handoffBackendRaw);
451
502
  // Fullscreen kernel switch: the old process spawned this one with an ACK
452
503
  // pipe on fd 3 and the alternate screen still open (see handoffAck.ts).
453
504
  const handoffAck = beginHandoffAck();
454
505
  if (handoffAck !== undefined) {
455
506
  logRestartEvent('handoff/boot: ack armed', { attemptId: handoffAttemptId() ?? '' });
456
507
  }
457
- const backendChoice = resolveRememberedBackend({
508
+ // Every source is parsed in two stages (P0 D1): syntax first, then the
509
+ // registry. An id that is syntactically valid but not installed must behave
510
+ // exactly like an unknown value did before the set was open — dsh plus a
511
+ // warning, never a crashed boot. `envKnown` carries that into the resolver for
512
+ // the one source that arrives raw; the other three are parsed above/beside.
513
+ const configuredBackend = parseBackendChoice(config.backend);
514
+ const rememberedBackend = readKernelPrefs().backend;
515
+ const entryKernel = runtimeOptions.entryKernel;
516
+ const backendChoice = entryKernel ?? (dshInEntry ? DSH_BACKEND_ID : resolveRememberedBackend({
458
517
  ...(handoffBackend === undefined ? {} : { handoff: handoffBackend }),
459
- configured: config.backend,
518
+ configured: configuredBackend,
460
519
  envRaw: rawBackend,
461
- memory: readKernelPrefs().backend,
520
+ envKnown: isRegisteredBackend,
521
+ memory: isRegisteredBackend(rememberedBackend) ? rememberedBackend : undefined,
522
+ }));
523
+ const rawBackendGiven = rawBackend === undefined ? '' : rawBackend.trim();
524
+ // The launcher marks a *derived* resume target with the backend it read it from
525
+ // (RESUME_BACKEND_ENV); a target the user placed carries no mark. Read and
526
+ // deleted right away, like the kernel handoff above, so no child inherits it.
527
+ // The mark is parsed with the syntax gate alone: the source is typically a
528
+ // backend that is NOT registered here, which is the whole reason to check.
529
+ // A marked target belongs to that one backend, and this boot may have landed
530
+ // elsewhere — an uninstalled plugin id falls back to dsh (P0 D1), an unset
531
+ // DSH_TUI_BACKEND follows the remembered kernel instead. Handing it over either
532
+ // fails or resumes an unrelated session that happens to share the id, so it is
533
+ // revoked rather than carried across (PR #1380 review R2) — and a revocation is
534
+ // a refusal, not a cold start (roadmap §6 item 11, B-2a).
535
+ const resumeBackendRaw = process.env[RESUME_BACKEND_ENV];
536
+ if (resumeBackendRaw !== undefined)
537
+ delete process.env[RESUME_BACKEND_ENV];
538
+ // A safe-mode retry carries a licence to survive a revoked target (the launcher
539
+ // sets RESUME_RETRY_ENV; see resolveResumeTarget). Read and deleted right away,
540
+ // like the mark above: no child may inherit it.
541
+ const resumeRetry = process.env[RESUME_RETRY_ENV] !== undefined;
542
+ delete process.env[RESUME_RETRY_ENV];
543
+ const resumeTarget = resolveResumeTarget({
544
+ sessionId: launchSessionId,
545
+ sourceBackend: parseBackendId(resumeBackendRaw),
546
+ backendChoice,
547
+ retry: resumeRetry,
462
548
  });
463
- if (rawBackend !== undefined && rawBackend.trim() !== '' && normalizeBackendChoice(rawBackend) === undefined) {
464
- ctx.logger.warn(`dsh-tui: DSH_TUI_BACKEND="${rawBackend}" names no known backend (${KERNEL_IDS.join(', ')}); starting on dsh`);
549
+ const effectiveSessionId = resumeTarget.kind === 'usable' ? resumeTarget.sessionId : undefined;
550
+ // The non-DSH path below opens `backendChoice`'s own session — a revoked target
551
+ // must not reach it either.
552
+ const configuredSessionId = resumeTarget.kind === 'revoked' ? undefined : config.sessionId;
553
+ // Safe-mode retries replay the original argv. Revocation must also remove its
554
+ // resume flags, or either startup path can reopen the replacement backend's
555
+ // own last session after promising a cold start. Prompt parsing keeps raw argv.
556
+ const startupArgv = resumeTarget.kind === 'revoked'
557
+ ? stripResumeArgs(cmdlineArgs ?? process.argv.slice(2))
558
+ : cmdlineArgs ?? process.argv.slice(2);
559
+ if (resumeTarget.kind === 'revoked') {
560
+ // Fail closed (roadmap §6 item 11): the target belongs to another backend, so
561
+ // this boot neither carries it over nor quietly starts a session nobody asked
562
+ // for. A fatal refusal is thrown into the startup funnel, which reports it on
563
+ // stderr and exits non-zero after restoring the terminal; the only tolerated
564
+ // case is the safe-mode retry, whose target comes from last-run.json and whose
565
+ // failure would close the user's last way back (RESUME_RETRY_ENV).
566
+ // The advice must be executable (PR #1449 review R2): when the source backend
567
+ // is not registered here, `--backend <from> --resume` would hit this same
568
+ // refusal, so that branch says the backend is unavailable instead. The other
569
+ // branch names the id (`kernelId`), never the display label — `--backend`
570
+ // takes an id.
571
+ const refusalKey = !resumeTarget.fatal
572
+ ? 'resume-target-revoked-retry'
573
+ : isRegisteredBackend(resumeTarget.from) ? 'resume-target-revoked' : 'resume-target-revoked-uninstalled';
574
+ const refusal = t(refusalKey, {
575
+ from: resumeTarget.from,
576
+ kernel: backendLabel(backendChoice),
577
+ kernelId: backendChoice,
578
+ });
579
+ if (resumeTarget.fatal)
580
+ throw new Error(refusal);
581
+ ctx.logger.warn(refusal);
582
+ }
583
+ // Config and handoff outrank env: an ignored env value cannot invalidate the
584
+ // selected backend's resume target or claim that this boot fell back to DSH.
585
+ if (handoffBackend === undefined && configuredBackend === undefined
586
+ && rawBackendGiven !== '' && !isRegisteredBackend(rawBackendGiven.toLowerCase())) {
587
+ // Two cases, two sentences: a typo is not an uninstalled plugin (P0 D1).
588
+ const installed = listBackends().map(entry => entry.manifest.id).join(', ');
589
+ // The plain fallback (no resume request) keeps P0 D1's shape: dsh plus a
590
+ // warning, exit code 0. With a resume target in hand it must not: dsh would
591
+ // be asked to open an id that belongs to the backend the user named, which
592
+ // either fails or resumes a session of another backend (roadmap §6 item 11).
593
+ // Nothing is resumed, nothing is created — the boot stops here instead.
594
+ if (effectiveSessionId !== undefined) {
595
+ throw new Error(t('resume-backend-unavailable', {
596
+ backend: rawBackendGiven,
597
+ session: effectiveSessionId,
598
+ installed,
599
+ }));
600
+ }
601
+ ctx.logger.warn(isBackendIdSyntax(rawBackendGiven)
602
+ ? `dsh-tui: DSH_TUI_BACKEND="${rawBackend}" names no installed backend (registered: ${installed}); starting on dsh`
603
+ : `dsh-tui: DSH_TUI_BACKEND="${rawBackend}" is not a valid backend id (lowercase letters, digits and dashes); starting on dsh`);
465
604
  }
466
605
  /**
467
606
  * Whether a Config row or DSH_TUI_BACKEND overrides the selector's
@@ -477,27 +616,74 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
477
616
  // Explicit choices (flag/Config/handoff) and explicit resume targets keep
478
617
  // the hard failure: silently swapping what the user named would be worse.
479
618
  let backendStart;
619
+ /** The startup session still opening (`deferBackendOpen`); the channel adopts it. */
620
+ let backendStartup;
621
+ /** Once set, the channel's binding owns the session. */
622
+ let backendStartupAdopted = false;
480
623
  let backendFallbackNotice;
624
+ const deferBackendOpen = runtimeOptions.deferBackendOpen === true;
625
+ if (deferBackendOpen && backendChoice === 'dsh' && !dshInEntry) {
626
+ throw new Error('dsh-tui: the standalone entry cannot host the DSH kernel; start it through the profile launcher');
627
+ }
628
+ /** The in-process DSH kernel's startup session: settled by `attachDsh`
629
+ * (the profile's dsh-tui row) or by a failed composition. */
630
+ let settleDshStartup;
631
+ let dshStartupSettled = false;
632
+ const dshStartup = dshInEntry
633
+ ? new Promise((resolve, reject) => {
634
+ settleDshStartup = {
635
+ resolve: startup => { dshStartupSettled = true; resolve(startup); },
636
+ reject: error => { dshStartupSettled = true; reject(error); },
637
+ };
638
+ })
639
+ : undefined;
640
+ // A boot that fails before the channel holds the startup must not leak an
641
+ // unhandled rejection.
642
+ dshStartup?.catch(() => undefined);
481
643
  if (backendChoice !== 'dsh') {
482
644
  try {
483
- backendStart = await openBackendStartup(ctx, await BACKEND_LOADERS[backendChoice](), {
645
+ const startupInput = {
484
646
  cwd: sessionCwd,
485
- stderr: line => {
647
+ stderr: (line) => {
486
648
  logForDebugging(`[${backendChoice}-stderr] ${line}`);
487
649
  stderrReporter.push(line);
488
650
  },
489
- ...(config.sessionId === undefined ? {} : { configuredSessionId: config.sessionId }),
490
- argv: cmdlineArgs ?? process.argv.slice(2),
491
- });
651
+ ...(configuredSessionId === undefined ? {} : { configuredSessionId }),
652
+ argv: startupArgv,
653
+ };
654
+ // One load per launch, shared by the deferred path (the entry mounts
655
+ // between `prepare` and `start`) and the eager one.
656
+ const backendModule = await loadBackend(backendChoice);
657
+ if (deferBackendOpen) {
658
+ const prepared = await prepareBackendStartup(ctx, backendModule, startupInput);
659
+ const startup = prepared.start().then(opened => ({ session: opened.session, history: opened.initialHistory }));
660
+ backendStartup = startup;
661
+ // As for dshStartup above; the effect closes an unadopted session.
662
+ startup.catch(() => undefined);
663
+ ctx.effect(() => () => {
664
+ if (backendStartupAdopted)
665
+ return;
666
+ void startup.then(opened => opened.session.dispose(), () => undefined).catch(() => undefined);
667
+ }, 'dsh-tui deferred backend startup');
668
+ backendStart = { ...prepared, session: createStartingSession(prepared.backendId, sessionCwd), initialHistory: [] };
669
+ }
670
+ else {
671
+ backendStart = await openBackendStartup(ctx, backendModule, startupInput);
672
+ }
492
673
  }
493
674
  catch (error) {
494
- if (backendPinned || handoffBackend !== undefined || launchSessionId !== undefined)
675
+ if (deferBackendOpen || backendPinned || handoffBackend !== undefined || effectiveSessionId !== undefined)
495
676
  throw error;
496
677
  const reason = error instanceof Error ? error.message : String(error);
497
678
  logForDebugging(`dsh-tui: remembered backend "${backendChoice}" failed to open (${reason}); falling back to dsh`);
498
- backendFallbackNotice = t('kernel-memory-fallback', { name: kernelDisplayName(backendChoice), reason });
679
+ backendFallbackNotice = t('kernel-memory-fallback', { name: backendLabel(backendChoice), reason });
499
680
  }
500
681
  }
682
+ // Resolve a surviving bare request on the backend that actually opened.
683
+ // This also covers a remembered backend whose startup fell back to DSH.
684
+ const bootSessionId = backendStart === undefined
685
+ ? effectiveSessionId ?? resumeTargetFromArgv(startupArgv)
686
+ : backendStart.resumedSessionId;
501
687
  // The backend session (and its child process) belongs to this fiber until the
502
688
  // channel adopts it: a boot that throws before then disposes the fiber's
503
689
  // effects, and this one stops the child instead of leaking it. Dispose is
@@ -515,11 +701,15 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
515
701
  const backendPermissions = backendStart === undefined ? undefined : new PermissionStore();
516
702
  if (backendPermissions !== undefined)
517
703
  ctx.effect(() => () => backendPermissions.settleAll());
518
- const { agent, handle, agentPreset, route: createdRoute } = backendStart !== undefined
704
+ const resolved = backendStart !== undefined || dshInEntry
519
705
  ? { agent: undefined, handle: undefined, agentPreset: undefined, route: undefined }
520
- : await resolveAgent(ctx, launchSessionId, configuredRoute, startupRoute, meta, config.preset);
706
+ : await resolveAgent(ctx, bootSessionId, configuredRoute, startupRoute, deploymentRoute, meta, config.preset);
707
+ let agent = resolved.agent;
708
+ const { handle, agentPreset, route: createdRoute } = resolved;
521
709
  // Workspace ownership is a DSH session-store fact (skipped off DSH).
522
- if (agent !== undefined)
710
+ // `ctx` here is the context that owns the session: this plugin's, or the
711
+ // dsh-tui row's for the in-process DSH kernel (`attachDsh`).
712
+ const attachWorkspaceOwnership = async (ctx, agent) => {
523
713
  try {
524
714
  // Opening a persisted TUI session is an explicit ownership action too.
525
715
  // Older TUI versions only wrote the Session log, so attaching on every
@@ -556,6 +746,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
556
746
  // loud instead of silently leaving the conversation Ungrouped.
557
747
  ctx.logger.warn(`dsh-tui: session "${agent.session.id}" workspace attachment failed: ${error instanceof Error ? error.message : String(error)}`);
558
748
  }
749
+ };
750
+ if (agent !== undefined)
751
+ await attachWorkspaceOwnership(ctx, agent);
559
752
  // Status-line route: the exact route the agent runs with — on create the
560
753
  // validated startup resolution, on resume the route the target session's
561
754
  // own records carry (a complete cordis.yml pin wins over them).
@@ -586,16 +779,36 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
586
779
  startupSession = backendStart.session;
587
780
  else if (agent !== undefined)
588
781
  startupSession = createDshSession(ctx, { agent, handle });
782
+ else if (dshInEntry)
783
+ startupSession = createStartingSession('dsh', sessionCwd);
589
784
  else
590
785
  throw new Error('dsh-tui: no startup session was opened');
786
+ // The in-process DSH kernel's route and Config pins from its dsh-tui row,
787
+ // set in `attachDsh` before adoption; the getters below serve them.
788
+ const rowFacts = {};
789
+ /**
790
+ * The in-process DSH kernel's `/new` while no DSH session was adopted (its
791
+ * startup open failed). Installed by `attachDsh`.
792
+ */
793
+ let openDshSession;
794
+ let compositionFailed = false;
795
+ /** How the in-process DSH kernel's composition ended: a failing startup open
796
+ * waits for it, so a failed composition (the root cause) is reported. */
797
+ let settleComposition = () => undefined;
798
+ const compositionSettled = new Promise(resolve => { settleComposition = resolve; });
591
799
  const rawChannel = createChannel(ctx, startupSession, {
592
800
  // The namespace this boot actually registered the settings section under
593
801
  // (the Config owner's Loader id; custom ids are supported). Chat and the
594
802
  // channel's own settings reads look the section up by it.
595
803
  settingsNs: tuiSettingsNs,
804
+ onTaskComplete: preview => {
805
+ if (!shadow)
806
+ taskNotifier.notify(t('task-complete-notification'), preview);
807
+ },
808
+ settingsService: tuiSettings,
596
809
  // The backend reports its model with its first turn (`system/init`);
597
810
  // until then the status line names the backend.
598
- model: backendStart !== undefined ? backendStart.label : displayRoute.model,
811
+ get model() { return rowFacts.route?.model ?? (backendStart !== undefined ? backendStart.label : displayRoute.model); },
599
812
  ...(backendStart === undefined || backendPermissions === undefined ? {} : {
600
813
  backendLabel: backendStart.label,
601
814
  openSession: backendStart.open,
@@ -605,6 +818,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
605
818
  sessionPrefs: backendStart.sessionPrefs,
606
819
  initialHistory: backendStart.initialHistory,
607
820
  resumeCommand: backendStart.resumeCommand,
821
+ ...(backendStartup === undefined ? {} : { startup: backendStartup }),
822
+ }),
823
+ // The in-process DSH kernel: a DSH placeholder until the row's session.
824
+ ...(dshStartup === undefined ? {} : {
825
+ backendLabel: backendLabel('dsh'),
826
+ startup: dshStartup,
827
+ openSession: async (target) => {
828
+ if (target.kind !== 'create' || openDshSession === undefined)
829
+ throw new Error(t('new-session-unavailable'));
830
+ return await openDshSession(target.cwd);
831
+ },
608
832
  }),
609
833
  // The activity projection only pushes on change; read the current value as
610
834
  // soon as this session binds so a resumed or reattached session renders its
@@ -626,17 +850,19 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
626
850
  // sessions record sessionCwd at creation, so both agree there.
627
851
  // A resumed backend session runs where it was recorded.
628
852
  cwd: backendStart?.session.cwd ?? agent?.session.header.cwd ?? sessionCwd,
629
- provider: backendStart !== undefined ? backendStart.backendId : displayRoute.provider,
853
+ get provider() { return rowFacts.route?.provider ?? (backendStart !== undefined ? backendStart.backendId : displayRoute.provider); },
630
854
  // Raw cordis.yml route (undefined when unset): the channel's
631
855
  // new-session path re-resolves prefs against these, and resume passes
632
856
  // only explicit values so the target session's own record wins.
633
- configuredModel: config.model,
634
- configuredProvider: config.provider,
857
+ get configuredModel() { return rowFacts.config === undefined ? config.model : rowFacts.config.model; },
858
+ get configuredProvider() { return rowFacts.config === undefined ? config.provider : rowFacts.config.provider; },
635
859
  // Raw cordis.yml `lang` / `activityFrames`: /reload must not override a
636
860
  // static deployment choice with the persisted preference.
637
861
  configuredLang: config.lang,
638
862
  configuredActivityFrames: config.activityFrames,
639
- effort: config.effort,
863
+ // Read when the DSH extensions attach (the adoption), after the row's
864
+ // facts are in; the display fields below are fixed at the first frame.
865
+ get effort() { return rowFacts.config === undefined ? config.effort : rowFacts.config.effort; },
640
866
  activity: config.activity,
641
867
  // Explicit cordis.yml value (static deployment choice) wins over the
642
868
  // runtime `/activity` preference, which wins over the default.
@@ -645,14 +871,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
645
871
  contextBar: config.contextBar,
646
872
  // Same precedence for the agent preset: cordis.yml `preset` over the
647
873
  // persisted `/preset` choice; undefined adopts the roster default.
648
- configuredPreset: config.preset,
874
+ get configuredPreset() { return rowFacts.config === undefined ? config.preset : rowFacts.config.preset; },
649
875
  agentPreset,
650
876
  // Shift+Tab session-mode cycle (undefined → the built-in default/
651
877
  // plan/full cycle in sessionModes.ts).
652
- modes: config.modes,
878
+ get modes() { return rowFacts.config === undefined ? config.modes : rowFacts.config.modes; },
653
879
  // Edit/Write diff presentation (schema default 'auto'); the /settings
654
880
  // screen edits this key live through the dsh-tui namespace.
655
881
  diffLayout: config.diffLayout,
882
+ // Upstream auto-retry seeding (cordis.yml upstreamRetry, default on):
883
+ // only an explicit opt-out disables it.
884
+ get upstreamRetry() { return rowFacts.config === undefined ? config.upstreamRetry : rowFacts.config.upstreamRetry; },
656
885
  thinkingFold: config.thinkingFold,
657
886
  jobGroupFold: config.jobGroupFold,
658
887
  toolBackground: config.toolBackground,
@@ -674,6 +903,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
674
903
  // 的改动由 applyBrand 实时接上(branding.ts 负责解析)。
675
904
  brand: config.brand,
676
905
  });
906
+ backendStartupAdopted = backendStartup !== undefined;
677
907
  // Register the live Channel for the adapter Kernel. The Channel driver
678
908
  // resolves it lazily from the composition root, so this can be called after
679
909
  // the plugin-host Kernel started without requiring a re-mount.
@@ -682,9 +912,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
682
912
  // child activation context.
683
913
  const unregisterTuiChannel = registerTuiChannel(compositionRoot(ctx), rawChannel);
684
914
  ctx.effect(() => () => { unregisterTuiChannel(); });
685
- const pluginHost = ctx.get('tuiPluginHost');
686
915
  const adapterRuntime = adapterRuntimeFor(ctx);
687
- const uiMount = mountChannelUi(ctx, rawChannel, pluginHost, adapterRuntime.mode);
916
+ // Looked up per probe: the plugin-host row can compose after the mount.
917
+ const uiMount = mountChannelUi(ctx, rawChannel, ctx.get('tuiPluginHost'), adapterRuntime.mode, () => ctx.get('tuiPluginHost'));
688
918
  const channel = uiMount.channel;
689
919
  bindChannelCommands(rawChannel, channel);
690
920
  // last-run.json for the launcher's crash retry (see writeLastRunRecord):
@@ -692,6 +922,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
692
922
  // at that point. The kernel-switch branch skips the refresh because the
693
923
  // replacement writes its own record when it boots.
694
924
  const bootAttemptId = `${process.pid.toString(36)}-${Date.now().toString(36)}`;
925
+ /** None before DSH has composed (the in-process DSH kernel) or off DSH. */
926
+ const liveDshAgent = () => dshAgentOn(ctx, channel.agentId);
695
927
  const refreshLastRunRecord = () => {
696
928
  // An observational composition (replay/embedding) is not "the instance the
697
929
  // user ran last" — it must not overwrite the interactive record.
@@ -701,7 +933,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
701
933
  ? backendStart.persisted(channel.agentId, channel.rows)
702
934
  : isExitResumable({
703
935
  pendingCount: channel.pending.length,
704
- liveAgent: ctx.agents.get(SessionId(channel.agentId)),
936
+ liveAgent: liveDshAgent(),
705
937
  startupAgent: agent,
706
938
  });
707
939
  writeLastRunRecord({
@@ -725,6 +957,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
725
957
  // lands once the channel can actually show it.
726
958
  if (backendFallbackNotice !== undefined)
727
959
  notifyChannel(backendFallbackNotice, { color: 'warning' });
960
+ // The entry could not use the installed dsh and fell back (this process,
961
+ // or the `dsh --profile` it handed the launch to): say why, once.
962
+ const hostNotice = process.env[HOST_NOTICE_ENV];
963
+ delete process.env[HOST_NOTICE_ENV];
964
+ if (hostNotice !== undefined && hostNotice !== '') {
965
+ notifyChannel(t('host-dsh-unavailable', { reason: hostNotice }), { color: 'warning', timeoutMs: 10000 });
966
+ }
728
967
  const submitChannel = text => {
729
968
  if (!shadow)
730
969
  channel.submit(text);
@@ -744,7 +983,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
744
983
  applySidePanelSplitEnabled(config.sidePanel?.splitEnabled);
745
984
  applySidePanelOpen(config.sidePanel?.open);
746
985
  applySidePanelRatio(config.sidePanel?.ratio);
747
- applySidePanelPanels(config.sidePanel?.panels);
986
+ // Registered ids survive: on the profile path a third-party row may have
987
+ // registered its panel before this row applied (./panels.ts).
988
+ applyConfiguredSidePanelPanels(config.sidePanel?.panels);
748
989
  applyCompanionSkin(config.companion?.skin);
749
990
  applyBtwContextTurns(config.btw?.contextTurns);
750
991
  applyBtwContextBudget(config.btw?.contextBudget);
@@ -759,14 +1000,16 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
759
1000
  // Without the extensions row (tuiToast absent) plugin toasts are dropped
760
1001
  // by the runtime itself — same soft-degrade contract as the other seams.
761
1002
  // Delivery uses the same owner-bound UI capability as all renderer actions.
762
- const toastStore = getHostToastStore(ctx.get('tuiToast'));
763
- toastStore?.setSink(delivery => {
764
- notifyChannel(delivery.text, { color: delivery.color, timeoutMs: delivery.timeoutMs });
765
- });
766
- if (questionAnswererRegistration.kind === 'waterfall') {
1003
+ const bindToastSink = () => {
1004
+ getHostToastStore(ctx.get('tuiToast'))?.setSink(delivery => {
1005
+ notifyChannel(delivery.text, { color: delivery.color, timeoutMs: delivery.timeoutMs });
1006
+ });
1007
+ };
1008
+ bindToastSink();
1009
+ if (questionSeams?.registration.kind === 'waterfall') {
767
1010
  // Ownership follows the mutable channel; registration cleanup belongs to
768
1011
  // this Cordis fiber.
769
- questionAnswererRegistration.register(channel);
1012
+ questionSeams.registration.register(channel);
770
1013
  }
771
1014
  // Fullscreen layout decision: the settings user layer (edited through the
772
1015
  // /settings screen) overrides cordis.yml when set. The settings injection
@@ -777,6 +1020,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
777
1020
  // (swapping layouts requires re-mounting the whole tree).
778
1021
  let bootedFullscreen = config.fullscreen === true;
779
1022
  let bootedTerminalImages = lastBootedTerminalImages ?? config.terminalImages ?? true;
1023
+ // The launchpad boot decision (settings `launchpad`): same
1024
+ // restart-to-apply pattern as fullscreen — resolved before the tree mounts
1025
+ // from the settings user layer over cordis.yml; a mid-session edit is for
1026
+ // the next boot, the running session keeps what it booted with.
1027
+ let bootedLaunchpad = config.launchpad !== false;
780
1028
  let rendererSettingsFrozen = false;
781
1029
  const terminalImagesDisabledByEnv = isEnvTruthy(process.env.DSH_TUI_DISABLE_TERMINAL_IMAGES);
782
1030
  // The settings service may come up AFTER this plugin's apply: the cordis
@@ -791,10 +1039,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
791
1039
  resolveSettingsReady = () => resolve();
792
1040
  setTimeout(resolve, 300);
793
1041
  });
794
- // Old hosts register a settings.yaml scope. 0.1.7 projects the plugin's
795
- // volatile Config fields instead; both paths apply edits without remounting.
796
- ctx.inject(['settings'], (settingsCtx) => {
797
- // Loader targets the Config owner's fiber, not the injected child fiber.
1042
+ ((settingsCtx) => {
798
1043
  const scope = createSettingsScope(configOwner, settingsCtx.settings, tuiSettingsNs, Schema.object({
799
1044
  diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
800
1045
  thinkingFold: Schema.union(['preview', 'full']).default('preview'),
@@ -822,6 +1067,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
822
1067
  expandEditor: Schema.boolean(),
823
1068
  // Same no-default rule: applyDisplay resolves `?? config.smoothStreaming ?? true`.
824
1069
  smoothStreaming: Schema.boolean(),
1070
+ taskNotifications: Schema.union(['off', 'unfocused', 'always']),
1071
+ notificationMethod: Schema.union(['auto', 'osc9', 'bel']),
825
1072
  // Same no-default rule: applyDisplay resolves `?? config.mermaidDiagrams ?? true`.
826
1073
  mermaidDiagrams: Schema.boolean(),
827
1074
  // Code-frame shape; unset keeps the light rail frame.
@@ -913,6 +1160,10 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
913
1160
  // Same no-default rule: unset keeps cordis.yml's `fullscreen`
914
1161
  // decisive; set overrides it from the next boot on.
915
1162
  fullscreen: Schema.boolean(),
1163
+ // Same no-default rule as fullscreen: the launchpad boot gate reads
1164
+ // `value.launchpad ?? config.launchpad ?? true`, so cordis.yml stays
1165
+ // decisive while the user layer is unset.
1166
+ launchpad: Schema.boolean(),
916
1167
  // Unset inherits cordis.yml; a saved choice takes effect after restart.
917
1168
  terminalImages: Schema.boolean(),
918
1169
  // Built-in action-shortcut overrides, one optional combo string per
@@ -969,8 +1220,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
969
1220
  if (typeof value.fullscreen === 'boolean') {
970
1221
  bootedFullscreen = value.fullscreen;
971
1222
  }
1223
+ if (typeof value.launchpad === 'boolean') {
1224
+ bootedLaunchpad = value.launchpad;
1225
+ }
972
1226
  bootedTerminalImages = lastBootedTerminalImages ?? value.terminalImages ?? config.terminalImages ?? true;
973
1227
  };
1228
+ /** False only during the boot application below: a registered panel absent
1229
+ * from its CSV is not a user removal (./panels.ts). */
1230
+ let settingsEditsLive = true;
974
1231
  // The /settings language field writes `lang` through the settings
975
1232
  // service (user layer): apply it live and mirror it to lang.json so
976
1233
  // the /lang command and next-boot resolution agree. DSH_TUI_LANG
@@ -1002,6 +1259,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1002
1259
  channel.setPromptSessionLabel(value.promptSessionLabel ?? config.promptSessionLabel ?? false);
1003
1260
  channel.setExpandEditor(value.expandEditor ?? config.expandEditor ?? true);
1004
1261
  channel.setSmoothStreaming(value.smoothStreaming ?? config.smoothStreaming ?? true);
1262
+ taskNotifier.configure(value.taskNotifications ?? config.taskNotifications, value.notificationMethod ?? config.notificationMethod);
1005
1263
  applyMermaidDiagrams(value.mermaidDiagrams ?? config.mermaidDiagrams);
1006
1264
  applyCodeFrameStyle(value.codeFrameStyle ?? config.codeFrameStyle);
1007
1265
  applyMathRendering(resolveMathRendering(value, config));
@@ -1016,7 +1274,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1016
1274
  applySidePanelSplitEnabled(value.sidePanel?.splitEnabled ?? config.sidePanel?.splitEnabled);
1017
1275
  applySidePanelOpen(value.sidePanel?.open ?? config.sidePanel?.open);
1018
1276
  applySidePanelRatio(value.sidePanel?.ratio ?? config.sidePanel?.ratio);
1019
- applySidePanelPanels(value.sidePanel?.panels ?? config.sidePanel?.panels);
1277
+ // A registered panel missing from a live edit was unticked in /settings.
1278
+ const panelsCsv = value.sidePanel?.panels ?? config.sidePanel?.panels;
1279
+ if (settingsEditsLive)
1280
+ applySidePanelPanelsFromSettings(panelsCsv);
1281
+ else
1282
+ applyConfiguredSidePanelPanels(panelsCsv);
1020
1283
  applyCompanionSkin(value.companion?.skin ?? config.companion?.skin);
1021
1284
  applyBtwContextTurns(value.btw?.contextTurns ?? config.btw?.contextTurns);
1022
1285
  applyBtwContextBudget(value.btw?.contextBudget ?? config.btw?.contextBudget);
@@ -1090,8 +1353,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1090
1353
  notifyChannel(t('settings-fullscreen-migrated'), { color: 'warning' });
1091
1354
  }
1092
1355
  const { fullscreen: staleFullscreen, ...migratedSettings } = bootSettings;
1356
+ settingsEditsLive = false;
1093
1357
  apply(fullscreenMigration === 'unset' ? migratedSettings : bootSettings);
1358
+ settingsEditsLive = true;
1094
1359
  let lastTerminalImages = bootSettings.terminalImages ?? config.terminalImages ?? true;
1360
+ let lastLaunchpad = bootSettings.launchpad ?? config.launchpad ?? true;
1095
1361
  settingsCtx.effect(() => scope.watch(next => {
1096
1362
  apply(next);
1097
1363
  if (typeof next.fullscreen === 'boolean' && next.fullscreen !== bootedFullscreen) {
@@ -1102,9 +1368,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1102
1368
  channel.notify(t('settings-terminal-images-restart'), { color: 'warning' });
1103
1369
  }
1104
1370
  lastTerminalImages = terminalImages;
1371
+ const launchpad = next.launchpad ?? config.launchpad ?? true;
1372
+ if (launchpad !== lastLaunchpad && launchpad !== bootedLaunchpad) {
1373
+ notifyChannel(t('settings-launchpad-restart'), { color: 'warning' });
1374
+ }
1375
+ lastLaunchpad = launchpad;
1105
1376
  }));
1106
1377
  resolveSettingsReady?.();
1107
- });
1378
+ })({ settings: tuiSettings, effect: ctx.effect.bind(ctx) });
1108
1379
  // The /settings screen's own section: the dsh-tui namespace comes from
1109
1380
  // the settings registration above, and the declared selects write `lang`
1110
1381
  // and `diffLayout` back through the settings service's revision-fenced
@@ -1147,9 +1418,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1147
1418
  // left this registration silently skipped and /settings read-only.
1148
1419
  // channel.ts reads through the same fallback, so both sides meet in the
1149
1420
  // same registry either way.
1421
+ // The in-process DSH kernel registers into the local host first and moves
1422
+ // the section to the composition's service once it exists.
1423
+ let rehomeSettingsSection = () => undefined;
1150
1424
  {
1151
- const settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections')) ?? getLocalSettingsSectionsHost(ctx);
1152
- const unregister = settingsSections.register({
1425
+ let settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections')) ?? getLocalSettingsSectionsHost(ctx);
1426
+ const tuiSection = {
1153
1427
  ns: tuiSettingsNs,
1154
1428
  title: 'dsh-tui',
1155
1429
  groups: [...SETTING_GROUPS],
@@ -1258,6 +1532,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1258
1532
  return String(typeof value === 'boolean' ? value : config.mermaidDiagrams !== false);
1259
1533
  },
1260
1534
  },
1535
+ {
1536
+ ...settingField('taskNotifications'),
1537
+ format: value => String(value ?? config.taskNotifications ?? 'unfocused'),
1538
+ },
1539
+ {
1540
+ ...settingField('notificationMethod'),
1541
+ format: value => String(value ?? config.notificationMethod ?? 'auto'),
1542
+ },
1261
1543
  {
1262
1544
  ...settingField('codeFrameStyle'),
1263
1545
  },
@@ -1284,10 +1566,10 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1284
1566
  ...settingField('effortDefault'),
1285
1567
  format(value) {
1286
1568
  // Unset in settings.yaml: show what a boot would actually start
1287
- // on (the cordis effort pin → the persisted /effort choice)
1569
+ // on (the persisted /effort choice → the cordis effort default)
1288
1570
  // instead of a misleading blank.
1289
1571
  if (value === undefined || value === null || value === 'auto') {
1290
- return config.effort ?? readEffortPref() ?? 'auto';
1572
+ return readEffortPref() ?? config.effort ?? 'auto';
1291
1573
  }
1292
1574
  return String(value);
1293
1575
  },
@@ -1440,6 +1722,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1440
1722
  return { kind: 'set', value: budget };
1441
1723
  },
1442
1724
  },
1725
+ {
1726
+ ...settingField('launchpad'),
1727
+ format(value) {
1728
+ // Unset in settings.yaml: the effective default is on — same
1729
+ // rule as expandEditor's field.
1730
+ return String(typeof value === 'boolean' ? value : config.launchpad !== false);
1731
+ },
1732
+ },
1443
1733
  {
1444
1734
  ...settingField('companion.skin'),
1445
1735
  },
@@ -1471,8 +1761,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1471
1761
  ...settingField('minimal'),
1472
1762
  },
1473
1763
  ],
1474
- });
1475
- ctx.effect(() => unregister);
1764
+ };
1765
+ let unregister = settingsSections.register(tuiSection);
1766
+ ctx.effect(() => () => { unregister(); });
1767
+ rehomeSettingsSection = () => {
1768
+ const service = getHostSettingsSections(ctx.get('tuiSettingsSections'));
1769
+ if (service === undefined || service === settingsSections)
1770
+ return;
1771
+ unregister();
1772
+ settingsSections = service;
1773
+ unregister = service.register(tuiSection);
1774
+ };
1476
1775
  }
1477
1776
  // DSH approval seam: the permission layer asks ApprovalService.request(),
1478
1777
  // which dispatches an `approval/request` waterfall. With no answerer the
@@ -1486,19 +1785,24 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1486
1785
  bindApprovalStore(ctx, approvalStore);
1487
1786
  // A non-DSH backend answers its own permission prompts: the DSH answerer
1488
1787
  // is not registered for it.
1489
- if (ctx.get('approval') !== undefined && backendStart === undefined) {
1490
- ctx.on('approval/request', (req, next) => approvalStore.park(req).catch(() => next()));
1491
- // Badge-flip push (P-4): React does not know the session log appended —
1492
- // a source-badge verdict that only flips inside getSnapshot() surfaces
1493
- // solely when something else re-renders. Feed the session firehose to
1494
- // the store: it reacts only to tool/result (the sole verdict-flipping
1495
- // event type), and its internal log-length memo skips appends from any
1496
- // session other than the active ask's, so no agent filtering is needed
1497
- // here. The firehose fires post-commit, after the event entered
1498
- // session.events, so the recheck sees the settled result.
1499
- ctx.on('session/event', (session, event) => approvalStore.noteSessionEvent(session.id, event));
1500
- ctx.effect(() => () => approvalStore.settleAll('cancelled'));
1501
- }
1788
+ // The in-process DSH kernel installs it in `attachDsh`.
1789
+ const mountDshApprovals = (dshCtx) => {
1790
+ if (dshCtx.get('approval') !== undefined && backendStart === undefined) {
1791
+ dshCtx.on('approval/request', (req, next) => approvalStore.park(req).catch(() => next()));
1792
+ // Badge-flip push (P-4): React does not know the session log appended —
1793
+ // a source-badge verdict that only flips inside getSnapshot() surfaces
1794
+ // solely when something else re-renders. Feed the session firehose to
1795
+ // the store: it reacts only to tool/result (the sole verdict-flipping
1796
+ // event type), and its internal log-length memo skips appends from any
1797
+ // session other than the active ask's, so no agent filtering is needed
1798
+ // here. The firehose fires post-commit, after the event entered
1799
+ // session.events, so the recheck sees the settled result.
1800
+ dshCtx.on('session/event', (session, event) => approvalStore.noteSessionEvent(session.id, event));
1801
+ dshCtx.effect(() => () => approvalStore.settleAll('cancelled'));
1802
+ }
1803
+ };
1804
+ if (!dshInEntry)
1805
+ mountDshApprovals(ctx);
1502
1806
  // The agent view reads parked ask ids for its "needs input" state.
1503
1807
  rawChannel.bindApprovalStore(approvalStore);
1504
1808
  // The panel source Chat renders: the backend's own prompts off DSH.
@@ -1518,9 +1822,25 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1518
1822
  // Submit once the channel exists; delivery goes through the normal pending/inbox
1519
1823
  // chain, so no special timing is needed. The parser separates startup flags
1520
1824
  // from literal prompt text.
1521
- const initialPrompt = initialPromptFromCmdlineArgs(cmdlineArgs);
1522
- if (initialPrompt)
1523
- submitChannel(initialPrompt);
1825
+ // The standalone entry has no `ctx.cmdlineArgs` (its argv is the app's); a
1826
+ // replacement keeps the arguments but the first process sent their prompt.
1827
+ const initialPrompt = launchPromptSent ? '' : initialPromptFromCmdlineArgs(cmdlineArgs ?? process.argv.slice(2));
1828
+ if (initialPrompt) {
1829
+ // A placeholder session refuses sends and nothing replays them after
1830
+ // adoption: hold the prompt until the channel is ready.
1831
+ if (channel.ready) {
1832
+ submitChannel(initialPrompt);
1833
+ }
1834
+ else {
1835
+ const unsubscribe = channel.subscribe(() => {
1836
+ if (!channel.ready)
1837
+ return;
1838
+ unsubscribe();
1839
+ submitChannel(initialPrompt);
1840
+ });
1841
+ ctx.effect(() => unsubscribe, 'dsh-tui initial prompt after startup');
1842
+ }
1843
+ }
1524
1844
  // Attach the stderr reporter to the live channel and flush anything a
1525
1845
  // startup-spawned server produced while the channel didn't exist yet.
1526
1846
  notifyStderr = (text, options) => notifyChannel(text, options);
@@ -1554,10 +1874,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1554
1874
  // accepted; the exit funnel then respawns onto that kernel (a NEW session —
1555
1875
  // no resume markers at all).
1556
1876
  let backendSwitchRequested;
1877
+ // A signal or `ctx.appExit` via the entry's exit seam: the clean-exit branch
1878
+ // runs as for `/quit`, then ends the process by the signal / with the code.
1879
+ let processExitRequest;
1557
1880
  // The profile this process was booted with (`dsh --profile <name>`); dsh
1558
1881
  // exposes it nowhere else, and /update must update the installation the
1559
1882
  // user is actually running, not a hard-coded one.
1560
- const profile = resolveDshProfileName();
1883
+ const profile = resolveDshProfileName() ?? runtimeOptions.profile;
1561
1884
  // Single exit funnel: `/exit` and double Ctrl+C land here, and so does
1562
1885
  // the unmount triggered by a cordis context teardown — but the two must
1563
1886
  // not share a fate (issue #12). Teardown only unmounts the UI; user exit
@@ -1565,7 +1888,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1565
1888
  // update handoff or resume hint).
1566
1889
  /** The session id for a restart or update handoff, or empty when the backend
1567
1890
  * has not persisted it and the replacement must start fresh. */
1568
- const handoffSessionId = () => backendStart === undefined || backendStart.persisted(channel.agentId, channel.rows) ? channel.agentId : '';
1891
+ const handoffSessionId = () => {
1892
+ if (backendStart !== undefined)
1893
+ return backendStart.persisted(channel.agentId, channel.rows) ? channel.agentId : '';
1894
+ return isUnstoredFreshSession(liveDshAgent()?.session) ? '' : channel.agentId;
1895
+ };
1569
1896
  const handoffHint = backendStart === undefined ? undefined : (sessionId) => backendStart.resumeCommand(sessionId);
1570
1897
  const funnel = createExitFunnel({
1571
1898
  onUserExit: error => {
@@ -1589,18 +1916,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1589
1916
  // never CLEARS a marker, so a session the user still has cannot be
1590
1917
  // dropped by a failure that happened before the first message landed.
1591
1918
  writeResumeMarkers: () => {
1592
- if (isExitResumable({
1593
- pendingCount: channel.pending.length,
1594
- liveAgent: ctx.agents.get(SessionId(channel.agentId)),
1919
+ writeCrashResumeMarkers({
1920
+ ctx,
1921
+ backendStart,
1922
+ channel,
1595
1923
  startupAgent: agent,
1596
- })) {
1597
- writeResumeTarget(channel.agentId);
1598
- }
1599
- // Non-DSH sessions use their backend preference as the launcher marker.
1600
- if (backendStart !== undefined && backendStart.persisted(channel.agentId, channel.rows))
1601
- backendStart.sessionPrefs.setLastSession(channel.agentId);
1602
- // So the launcher's retry reopens this session, not the boot-time one.
1603
- refreshLastRunRecord();
1924
+ writeResumeTarget,
1925
+ refreshLastRunRecord,
1926
+ });
1604
1927
  },
1605
1928
  finish: crashLine => {
1606
1929
  void finishExit(ctx, instance, bootedFullscreen, undefined, crashLine, () => disposeRootAndExit(ctx, 1));
@@ -1611,8 +1934,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1611
1934
  if (updateRequested) {
1612
1935
  try {
1613
1936
  // Non-DSH sessions keep their marker in backend prefs, not DSH's `resume.txt`.
1614
- if (backendStart === undefined)
1615
- writeResumeTarget(channel.agentId);
1937
+ if (backendStart === undefined) {
1938
+ const sessionId = handoffSessionId();
1939
+ if (sessionId === '')
1940
+ clearResumeTarget();
1941
+ else
1942
+ writeResumeTarget(sessionId);
1943
+ }
1616
1944
  else if (backendStart.persisted(channel.agentId, channel.rows))
1617
1945
  backendStart.sessionPrefs.setLastSession(channel.agentId);
1618
1946
  }
@@ -1623,7 +1951,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1623
1951
  const hintText = isStandaloneRuntime()
1624
1952
  ? t('update-standalone-starting')
1625
1953
  : t('update-starting');
1626
- void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, handoffSessionId(), updateTargetVersion, backendChoice, handoffHint));
1954
+ void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, handoffSessionId(), updateTargetVersion, backendChoice, handoffHint, exitSeam));
1627
1955
  return;
1628
1956
  }
1629
1957
  // The kernel selector's switch: the same respawn machinery, no resume.
@@ -1633,30 +1961,41 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1633
1961
  // inherited DSH_TUI_RESUME_SESSION marker from the replacement env.
1634
1962
  // kernel.json was already written when the choice was accepted.
1635
1963
  if (backendSwitchRequested !== undefined) {
1636
- logRestartEvent('funnel: backend-switch branch entered', { backend: backendSwitchRequested });
1964
+ // Captured: the closure below runs after this block, where the outer
1965
+ // `let` is no longer narrowed.
1966
+ const switchingTo = backendSwitchRequested;
1967
+ logRestartEvent('funnel: backend-switch branch entered', { backend: switchingTo });
1637
1968
  // Fullscreen keeps the alternate screen and writes the "switching"
1638
1969
  // notice into it until the replacement takes over (see handoffAck.ts);
1639
1970
  // inline restores the main screen and writes the notice there.
1640
- logRestartEvent(handoffEventTag('starting'), { backend: backendSwitchRequested });
1971
+ logRestartEvent(handoffEventTag('starting'), { backend: switchingTo });
1641
1972
  const keepAlt = bootedFullscreen;
1642
- void finishExit(ctx, instance, bootedFullscreen, formatHandoffNotice('starting', { name: kernelDisplayName(backendSwitchRequested), color: process.stdout.isTTY === true }), undefined, () => runRestart(ctx, profile, '', undefined, {
1643
- backend: backendSwitchRequested,
1973
+ void finishExit(ctx, instance, bootedFullscreen, formatHandoffNotice('starting', { name: backendLabel(switchingTo), color: process.stdout.isTTY === true }), undefined, () => runRestart(ctx, profile, '', undefined, {
1974
+ backend: switchingTo,
1975
+ // The replacement's parent prints the same name this process just
1976
+ // put on screen (update.ts has no registry to look it up in).
1977
+ backendName: backendLabel(switchingTo),
1644
1978
  ...(keepAlt ? { handoffScreen: 'alt' } : {}),
1645
- }), { keepAltScreen: keepAlt });
1979
+ }, exitSeam), { keepAltScreen: keepAlt });
1646
1980
  return;
1647
1981
  }
1648
1982
  // `/restart`: same handoff as the update path, no installation step.
1649
- // The resume target is written unconditionally — the user asked to
1650
- // restart THIS session, blank or not (mirrors the update contract).
1983
+ // A fresh initialization-only session has no log to reopen. Restart
1984
+ // it fresh; saved sessions keep their identity across the handoff.
1651
1985
  if (restartRequested) {
1652
1986
  beginRestartAttempt(channel.agentId);
1653
1987
  logRestartEvent('funnel: /restart branch entered');
1654
1988
  try {
1655
- if (backendStart === undefined)
1656
- writeResumeTarget(channel.agentId);
1989
+ if (backendStart === undefined) {
1990
+ const sessionId = handoffSessionId();
1991
+ if (sessionId === '')
1992
+ clearResumeTarget();
1993
+ else
1994
+ writeResumeTarget(sessionId);
1995
+ }
1657
1996
  else if (backendStart.persisted(channel.agentId, channel.rows))
1658
1997
  backendStart.sessionPrefs.setLastSession(channel.agentId);
1659
- logRestartEvent('funnel: resume target written');
1998
+ logRestartEvent('funnel: resume target updated');
1660
1999
  }
1661
2000
  catch (error) {
1662
2001
  // Resume persistence is best effort and must never block a restart.
@@ -1665,7 +2004,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1665
2004
  });
1666
2005
  }
1667
2006
  refreshLastRunRecord();
1668
- void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, handoffSessionId(), handoffHint, { kernel: backendChoice }));
2007
+ void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, handoffSessionId(), handoffHint, { kernel: backendChoice }, exitSeam));
1669
2008
  return;
1670
2009
  }
1671
2010
  // Judge against the live session behind the channel (channel.agentId),
@@ -1689,7 +2028,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1689
2028
  else {
1690
2029
  const resumable = isExitResumable({
1691
2030
  pendingCount: channel.pending.length,
1692
- liveAgent: ctx.agents.get(SessionId(channel.agentId)),
2031
+ liveAgent: liveDshAgent(),
1693
2032
  startupAgent: agent,
1694
2033
  });
1695
2034
  try {
@@ -1707,10 +2046,39 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1707
2046
  }
1708
2047
  // Same resumability as the markers above.
1709
2048
  refreshLastRunRecord();
1710
- void finishExit(ctx, instance, bootedFullscreen, hint, undefined, () => disposeRootAndExit(ctx, 0));
2049
+ const request = processExitRequest;
2050
+ void finishExit(ctx, instance, bootedFullscreen, hint, undefined, () => {
2051
+ if (request === undefined)
2052
+ disposeRootAndExit(ctx, 0);
2053
+ else if (request.kind === 'code')
2054
+ disposeRootAndExit(ctx, request.code);
2055
+ // A signal: the process dies by it once the root is down (bounded
2056
+ // as any dispose; the stalled case dies by it too).
2057
+ else
2058
+ disposeRootAndThen(ctx, () => { dieBySignal(request.signal); }, () => { dieBySignal(request.signal); });
2059
+ });
1711
2060
  },
1712
2061
  });
1713
2062
  const handleExit = funnel.handleExit;
2063
+ const exitSeam = runtimeOptions.exitSeam;
2064
+ const requestProcessExit = (request) => {
2065
+ // A handoff is under way: no backstop, which could kill the supervisor of
2066
+ // a replacement already up; superviseReplacement takes the seam over.
2067
+ if (updateRequested || restartRequested || backendSwitchRequested !== undefined)
2068
+ return 'supervising';
2069
+ // A `/quit` or a crash is already ending this process.
2070
+ if (exited)
2071
+ return 'pending';
2072
+ processExitRequest = request;
2073
+ logRestartEvent('funnel: process exit requested', request.kind === 'signal' ? { signal: request.signal } : { code: request.code });
2074
+ if (handleExit())
2075
+ return 'exiting';
2076
+ // The tree is being torn down: the funnel cannot own the process.
2077
+ processExitRequest = undefined;
2078
+ return 'refused';
2079
+ };
2080
+ if (exitSeam !== undefined)
2081
+ exitSeam.request = requestProcessExit;
1714
2082
  /** The launchpad kernel selector's accept path (the Chat side passes the
1715
2083
  * chosen kernel through onSwitchBackend — the prop wiring lands together
1716
2084
  * with the selector's Chat rendering). Persists the choice BEFORE the
@@ -1724,7 +2092,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1724
2092
  backendSwitchRequested = backend;
1725
2093
  writeKernelPrefs({ backend });
1726
2094
  logRestartEvent('command: backend switch accepted', { backend });
1727
- notifyChannel(t('kernel-switch-restarting', { name: kernelDisplayName(backend) }));
2095
+ notifyChannel(t('kernel-switch-restarting', { name: backendLabel(backend) }));
1728
2096
  handleExit();
1729
2097
  };
1730
2098
  /** /channel changed the active channel's connection. The running CLI child
@@ -1818,18 +2186,21 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1818
2186
  * and opens straight into its conversation.
1819
2187
  */
1820
2188
  const dshBoot = backendStart === undefined;
1821
- const noResume = isLandingLaunch({ launchSessionId, initialPrompt });
2189
+ const noResume = isLandingLaunch({ launchSessionId: bootSessionId, initialPrompt });
1822
2190
  const openHomeOnBoot = dshBoot && !homeSeen && noResume && requestedWorkspace === undefined;
1823
2191
  /**
1824
2192
  * The launchpad is NOT one-shot the way the workspace home is: every
1825
2193
  * ordinary launch starts on it, because it is where the first sentence gets
1826
2194
  * typed rather than a tutorial that retires itself — on every backend: a
1827
2195
  * remembered claude kernel lands here exactly like a dsh one (the `dshBoot`
1828
- * gate below is deliberately absent). `DSH_TUI_NO_LAUNCHPAD=1` is the
1829
- * escape hatch (an automation that wants the old blank conversation and no
1830
- * dialog in front of it).
2196
+ * gate below is deliberately absent). The /settings `launchpad` toggle
2197
+ * (settings user layer over cordis.yml, resolved into `bootedLaunchpad`
2198
+ * above) is the persistent off switch — off boots straight into the chat
2199
+ * page. `DSH_TUI_NO_LAUNCHPAD=1` remains the env escape hatch (an
2200
+ * automation that wants the old blank conversation and no dialog in front
2201
+ * of it).
1831
2202
  */
1832
- const launchpadOnBoot = noResume && process.env.DSH_TUI_NO_LAUNCHPAD !== '1';
2203
+ const launchpadOnBoot = noResume && bootedLaunchpad && process.env.DSH_TUI_NO_LAUNCHPAD !== '1';
1833
2204
  /**
1834
2205
  * The first-run guide. Gated on its own preference (not on `homeSeen`): the
1835
2206
  * two answer different questions, and an install that already knows its
@@ -1839,7 +2210,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1839
2210
  // 品牌镜像初值(branding.ts):首帧渲染前铺好,避免 Claude 后端先画一屏
1840
2211
  // 蓝再变橙。Chat 里的 effect 会在品牌解析变化时跟进更新这个镜像。
1841
2212
  setActiveBrand(resolveBrand(config.brand, backendChoice));
1842
- const chat = React.createElement(Chat, {
2213
+ const liveThemeHost = () => getHostThemes(ctx.get('tuiThemes'));
2214
+ const buildChat = () => React.createElement(Chat, {
1843
2215
  channel,
1844
2216
  renderScene: createChannelSceneOutlet(() => rawChannel.pluginScene),
1845
2217
  questionStore,
@@ -1859,7 +2231,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1859
2231
  // no longer runs a second activity tracker of its own.
1860
2232
  activityStore,
1861
2233
  extensionShortcuts: getHostShortcuts(ctx.get('tuiShortcuts')),
1862
- themeHost,
2234
+ themeHost: liveThemeHost(),
1863
2235
  // Full-screen surfaces inside Chat — the trajectory scene and the session
1864
2236
  // browser — enter the alt screen themselves in inline mode; in fullscreen
1865
2237
  // the tree is already wrapped below, so they must not nest.
@@ -1880,12 +2252,15 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1880
2252
  onSwitchBackend: switchBackend,
1881
2253
  // /channel: restart with a new session after the connection changed.
1882
2254
  onRestartFreshSession: restartFreshSession,
2255
+ // The backend registry as the picker sees it: the UI takes data, not the
2256
+ // registry (P0). dsh first, then the manifest order — picker order. Entries
2257
+ // that registered without an admission are not offered (B-2).
2258
+ kernelEntries: kernelEntriesOf(listOfferedBackends()),
1883
2259
  onProbeKernels: () => probeKernels(ctx, sessionCwd),
1884
- // The kernel picker's SDK install wizard (the dim Claude row, Enter).
1885
- onResolveSdkInstallTarget: sdkInstall.resolveTarget,
1886
- onStartSdkInstall: sdkInstall.start,
1887
- onCheckPnpm: sdkInstall.checkPnpm,
1888
- sdkInstallPinned: sdkInstall.pinned,
2260
+ // The kernel picker's SDK install wizard (a dim row's Enter): the surface of
2261
+ // whichever row the user picked, looked up by id — the manifest says what to
2262
+ // install and with which executor, this host holds the actions.
2263
+ onResolveSdkInstall: installSurfaceFor,
1889
2264
  kernelPinned: backendPinned,
1890
2265
  // Only a `dsh --profile <name>` launch has a profile installation for
1891
2266
  // `/update` to act on; source checkouts and `--config` overlays get the
@@ -1957,17 +2332,32 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1957
2332
  // the screen border). It must sit INSIDE AlternateScreen: the alt-screen
1958
2333
  // box sizes itself to the real terminal rows, while PageMargin reports
1959
2334
  // content-box dimensions to everything below it.
1960
- const marginChildren = bootedFullscreen
1961
- ? React.createElement(AlternateScreen, null, React.createElement(PageMargin, null, chat))
1962
- : React.createElement(PageMargin, null, chat);
1963
- const tree = React.createElement(ThemeProvider, {
1964
- themeHost,
1965
- children: marginChildren,
1966
- });
2335
+ const buildTree = () => {
2336
+ const chat = buildChat();
2337
+ const marginChildren = bootedFullscreen
2338
+ ? React.createElement(AlternateScreen, null, React.createElement(PageMargin, null, chat))
2339
+ : React.createElement(PageMargin, null, chat);
2340
+ return React.createElement(ThemeProvider, {
2341
+ themeHost: liveThemeHost(),
2342
+ children: marginChildren,
2343
+ });
2344
+ };
2345
+ const tree = buildTree();
1967
2346
  // Kernel-switch replacement: send the ready ACK once the first frame after
1968
2347
  // adoption is flushed. Does nothing on an ordinary boot.
1969
2348
  armFirstFrameAck(process.stdout);
1970
- instance = await render(tree, { exitOnCtrlC: false, terminalImages: bootedTerminalImages });
2349
+ markBoot('render-start');
2350
+ // The entry's composition right after the mount freezes the event loop for
2351
+ // a while: it waits for the first frame to reach the terminal.
2352
+ const awaitsFirstFrame = entrySlot !== undefined;
2353
+ let frameFlushed = () => undefined;
2354
+ const firstFrame = awaitsFirstFrame ? new Promise(resolve => { frameFlushed = resolve; }) : undefined;
2355
+ instance = await render(tree, {
2356
+ exitOnCtrlC: false,
2357
+ terminalImages: bootedTerminalImages,
2358
+ ...(awaitsFirstFrame ? { onFrame: () => { frameFlushed(); } } : {}),
2359
+ });
2360
+ markBoot('render-done');
1971
2361
  const isRecompose = lastBootedFullscreen !== undefined;
1972
2362
  lastBootedFullscreen = bootedFullscreen;
1973
2363
  lastBootedTerminalImages = bootedTerminalImages;
@@ -1981,14 +2371,32 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1981
2371
  // External injection channel (dsh.nvim etc.): expose a per-session local
1982
2372
  // socket that appends text into the prompt input and submits it. Optional
1983
2373
  // integration — a bind failure degrades to "no channel" and never fails the
1984
- // session. Closed on teardown so the socket and discovery record do not leak.
1985
- const injectChannel = openInjectChannel(agent?.session.id ?? channel.agentId, channel.cwd, {
1986
- append: (text) => injectControllerRef.current?.append(text),
1987
- submit: () => injectControllerRef.current?.submit(),
1988
- }, (message) => ctx.logger.warn(`dsh-tui: ${message}`));
1989
- if (injectChannel) {
1990
- ctx.effect(() => () => injectChannel.close());
1991
- }
2374
+ // session. It follows the bound session: a placeholder still opening (empty
2375
+ // id) binds nothing, and adoption or a session switch rebinds under the new
2376
+ // id and cwd, closing the previous socket and discovery record (a connected
2377
+ // client reconnects through servers.json). Closed on teardown so neither leaks.
2378
+ let injectChannel = null;
2379
+ let injectSessionId = '';
2380
+ const bindInjectChannel = () => {
2381
+ if (channel.agentId === injectSessionId)
2382
+ return;
2383
+ injectChannel?.close();
2384
+ injectChannel = null;
2385
+ injectSessionId = channel.agentId;
2386
+ if (injectSessionId === '')
2387
+ return;
2388
+ injectChannel = openInjectChannel(injectSessionId, channel.cwd, {
2389
+ append: (text) => injectControllerRef.current?.append(text),
2390
+ submit: () => injectControllerRef.current?.submit(),
2391
+ }, (message) => ctx.logger.warn(`dsh-tui: ${message}`));
2392
+ };
2393
+ bindInjectChannel();
2394
+ const unsubscribeInject = channel.subscribe(bindInjectChannel);
2395
+ ctx.effect(() => () => {
2396
+ unsubscribeInject();
2397
+ injectChannel?.close();
2398
+ injectChannel = null;
2399
+ }, 'dsh-tui inject channel');
1992
2400
  // Cross-process session mounting: publish the sessions this process has
1993
2401
  // mounted so another TUI (a different terminal process on the same machine)
1994
2402
  // can see them as occupied and refuse to mount the same log. Two processes
@@ -2034,9 +2442,138 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
2034
2442
  // the process, so a later fatal error falls back to Node's default crash
2035
2443
  // instead of reaching a disposed ctx.
2036
2444
  registerProcessGuardFatalSink(undefined);
2445
+ if (exitSeam?.request === requestProcessExit)
2446
+ exitSeam.request = undefined;
2037
2447
  rawChannel.releaseContributions();
2038
2448
  instance?.unmount();
2039
2449
  });
2450
+ /** The first frame's write, then stdout drained (bounded: a renderer that
2451
+ * never draws must not hold the composition back). */
2452
+ const flushFirstFrame = async () => {
2453
+ await Promise.race([firstFrame ?? Promise.resolve(), new Promise(resolve => { setTimeout(resolve, 1000).unref(); })]);
2454
+ await new Promise(resolve => { process.stdout.write('', () => { resolve(); }); });
2455
+ };
2456
+ /**
2457
+ * Services composed after the mount: re-read what the screen took as values
2458
+ * at mount (Chat's stores, theme host, toast sink, /settings section).
2459
+ */
2460
+ const refreshHostServices = () => {
2461
+ rehomeSettingsSection();
2462
+ bindToastSink();
2463
+ if (!exited)
2464
+ instance?.rerender(buildTree());
2465
+ };
2466
+ if (entrySlot !== undefined)
2467
+ entrySlot.firstFrameFlushed = flushFirstFrame;
2468
+ // The kernels without a dsh-tui row: no row to refresh the mounted screen,
2469
+ // so the slot does it.
2470
+ if (entrySlot !== undefined && !entrySlot.dsh)
2471
+ entrySlot.composeSucceeded = () => { refreshHostServices(); };
2472
+ if (entrySlot?.dsh === true) {
2473
+ entrySlot.composeSucceeded = () => { settleComposition(true); };
2474
+ entrySlot.composeFailed = (error, logPath) => {
2475
+ settleComposition(false);
2476
+ const reason = error instanceof Error ? error.message : String(error);
2477
+ logForDebugging(`dsh-tui: host composition failed (${reason})${logPath === undefined ? '' : ` — startup report: ${logPath}`}`);
2478
+ compositionFailed = true;
2479
+ openDshSession = undefined;
2480
+ // No DSH to open on: the failure row names the report and the ways out.
2481
+ const hint = logPath === undefined ? t('startup-compose-failed-hint') : t('startup-compose-failed-hint-log', { path: logPath });
2482
+ if (!dshStartupSettled) {
2483
+ settleDshStartup?.reject(new StartupOpenError(reason.split('\n')[0] ?? reason, hint, { cause: error }));
2484
+ return;
2485
+ }
2486
+ if (exited)
2487
+ return;
2488
+ // The session may already be adopted (the audit runs after the tree
2489
+ // settled): a row that stays, not a toast.
2490
+ try {
2491
+ channel.pushLocal(t('startup-open-failed', { backend: backendLabel('dsh'), err: reason.split('\n')[0] ?? reason }), [hint]);
2492
+ }
2493
+ catch {
2494
+ }
2495
+ };
2496
+ entrySlot.attachDsh = async (rowCtx, rowRuntimeConfig) => {
2497
+ const dshCtx = rowCtx;
2498
+ // The composition failed (its row is on screen), or the process is
2499
+ // leaving (the root dispose waited for the Loader, which started this row).
2500
+ if (compositionFailed || exited)
2501
+ return;
2502
+ try {
2503
+ const rowConfig = configValues(rowRuntimeConfig);
2504
+ rowFacts.config = rowConfig;
2505
+ await installPresets(dshCtx);
2506
+ const seams = mountDshQuestionSeams(dshCtx);
2507
+ if (seams.notice !== undefined)
2508
+ notifyChannel(seams.notice, { color: 'error' });
2509
+ if (seams.registration.kind === 'waterfall')
2510
+ seams.registration.register(channel);
2511
+ mountDshApprovals(dshCtx);
2512
+ refreshHostServices();
2513
+ const rowRoute = { provider: rowConfig.provider, model: rowConfig.model };
2514
+ // The same startup/deployment split the plugin's own boot makes (issue #67).
2515
+ const { startupRoute: rowStartupRoute, deploymentRoute: rowDeploymentRoute } = startupRoutes(dshCtx, rowRoute);
2516
+ const openOnRow = (sessionId, openMeta) => resolveAgent(dshCtx, sessionId, rowRoute, rowStartupRoute, rowDeploymentRoute, openMeta, rowConfig.preset);
2517
+ /** The opened agent is this screen's from now on; returns its route. */
2518
+ const takeOpened = async (opened) => {
2519
+ agent = opened.agent;
2520
+ await attachWorkspaceOwnership(dshCtx, opened.agent);
2521
+ rowFacts.route = opened.route ?? rowStartupRoute;
2522
+ return rowFacts.route;
2523
+ };
2524
+ // `/new` after a failed startup open: DSH's create path on this row.
2525
+ openDshSession = async (cwd) => {
2526
+ const created = await openOnRow(undefined, { cwd });
2527
+ await takeOpened(created);
2528
+ refreshLastRunRecord();
2529
+ return createDshSession(dshCtx, { agent: created.agent, handle: created.handle });
2530
+ };
2531
+ // The entry mounted on rebuilt defaults: the row's own `workspace` /
2532
+ // `cwd` decide the session directory, with `apply`'s precedence. A
2533
+ // provider-URI target resolves now that the providers exist.
2534
+ const rowWorkspace = rowConfig.workspace ?? requestedWorkspace;
2535
+ let startupMeta = { cwd: resolveSessionCwd(rowConfig.cwd) };
2536
+ if (rowWorkspace !== undefined) {
2537
+ if (initialWorkspace !== undefined && rowWorkspace === requestedWorkspace) {
2538
+ startupMeta = meta;
2539
+ }
2540
+ else {
2541
+ const workspaces = getHostWorkspaceRuntime(dshCtx.get('tuiWorkspaces'));
2542
+ const target = workspaces === undefined ? undefined : await workspaces.resolve(rowWorkspace);
2543
+ if (target === undefined)
2544
+ throw new Error(unsupportedWorkspaceTarget(rowWorkspace));
2545
+ startupMeta = { cwd: target.cwd };
2546
+ }
2547
+ }
2548
+ // A revoked target (the safe-mode retry's warning path) cold-starts here too.
2549
+ const rowSessionId = resumeTarget.kind === 'revoked'
2550
+ ? undefined
2551
+ : rowConfig.sessionId ?? resumeTargetFromArgv(cmdlineArgsOf(dshCtx) ?? process.argv.slice(2));
2552
+ const opened = await openOnRow(rowSessionId, startupMeta);
2553
+ const session = createDshSession(dshCtx, { agent: opened.agent, handle: opened.handle });
2554
+ // The composition failed while this opened: close the unadoptable session.
2555
+ if (compositionFailed) {
2556
+ await session.dispose().catch(() => undefined);
2557
+ return;
2558
+ }
2559
+ const route = await takeOpened(opened);
2560
+ settleDshStartup?.resolve({
2561
+ session,
2562
+ history: [],
2563
+ route,
2564
+ ...(opened.agentPreset === undefined ? {} : { agentPreset: opened.agentPreset }),
2565
+ });
2566
+ refreshLastRunRecord();
2567
+ }
2568
+ catch (error) {
2569
+ logForDebugging(`dsh-tui: DSH session failed to open (${error instanceof Error ? error.message : String(error)})`);
2570
+ // A composition that fails reports itself (its row names the report).
2571
+ if (!await compositionSettled)
2572
+ return;
2573
+ settleDshStartup?.reject(error);
2574
+ }
2575
+ };
2576
+ }
2040
2577
  // The TUI is the front door: when the user unmounts it (Ctrl+C), dispose
2041
2578
  // the app tree and exit the process. The rejection handler covers
2042
2579
  // error-driven unmounts — without it a rejected exitPromise became an
@@ -2044,6 +2581,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
2044
2581
  // is swallowed by the funnel (issue #12).
2045
2582
  void instance.waitUntilExit().then(handleExit, handleExit);
2046
2583
  }
2584
+ function unsupportedWorkspaceTarget(target) {
2585
+ return `dsh-tui: unsupported or unavailable workspace target: ${target}`;
2586
+ }
2047
2587
  /**
2048
2588
  * Attach to an existing agent, resume a persisted session (`dsh-tui --resume`
2049
2589
  * feeds the id through `config.sessionId`), or create a fresh one. Resume
@@ -2053,26 +2593,45 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
2053
2593
  * without a session id.
2054
2594
  *
2055
2595
  * Preset composition (issue #8): a create resolves the requested preset
2056
- * (cordis.yml `preset` over the persisted `/preset` choice over the roster
2057
- * default) and mounts it in the factory's setup hook; a resume re-mounts the
2058
- * preset the session's own log records. Without the roster both paths behave
2059
- * as before presets existed.
2596
+ * (`DSH_TUI_PRESET` over the persisted `/preset` choice over the cordis.yml
2597
+ * default over the roster default) and mounts it in the factory's setup hook;
2598
+ * a resume re-mounts the preset the session's own log records. Without the
2599
+ * roster both paths behave as before presets existed.
2060
2600
  *
2061
2601
  * Model route (issues #14/#30/#67): a create adopts the caller's atomically
2062
2602
  * resolved route (validated against the adapter catalog below); a resume
2063
- * passes only a COMPLETE cordis.yml route through — a provider-only pin must
2064
- * not half-override the route the target session's own records carry.
2603
+ * prefers the route the target session's own records carry and falls back to
2604
+ * a COMPLETE cordis.yml route — a provider-only pin must not half-override
2605
+ * the session's route, and the static route is only the deployment default.
2065
2606
  */
2066
- async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRoute, meta, configuredPreset) {
2067
- // Resume override (issue #67): cordis.yml overrides the target session's
2068
- // recorded route only when it pins BOTH halves; undefined halves let the
2069
- // session's own request/header records win (issue #30). The recorded route
2070
- // is ALSO fed back into agentOptions (not just the status line): a resume
2607
+ /**
2608
+ * The startup route and the deployment route a rejected preference falls back
2609
+ * to (issue #67): the persisted `/model` pick wins whole, else a complete
2610
+ * cordis.yml route, else Harness's agent-default-model selection — never the
2611
+ * lock the user's own pick replaced, never a half-pinned config.
2612
+ */
2613
+ function startupRoutes(ctx, configuredRoute) {
2614
+ const selected = ctx.get('agentDefaultModel')?.currentSelection?.();
2615
+ const harnessDefault = typeof selected?.provider === 'string' && selected.provider.length > 0
2616
+ && typeof selected.model === 'string' && selected.model.length > 0
2617
+ ? { provider: selected.provider, model: selected.model }
2618
+ : undefined;
2619
+ return {
2620
+ startupRoute: resolveModelRoute(configuredRoute, readModelPref(), harnessDefault),
2621
+ deploymentRoute: resolveModelRoute(configuredRoute, undefined, harnessDefault),
2622
+ };
2623
+ }
2624
+ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRoute, deploymentRoute, meta, configuredPreset) {
2625
+ // Deployment fallback (issue #67): a resumed session continues on the route
2626
+ // its own request/header records carry (issue #30) — the route it actually
2627
+ // ran is more specific than any static config — and only a session that
2628
+ // records none adopts the complete cordis.yml route. The recorded route is
2629
+ // ALSO fed back into agentOptions (not just the status line): a resume
2071
2630
  // whose cordis.yml pins only `provider` would otherwise leave
2072
2631
  // agentOptions.model undefined, which breaks the `{{model}}` persona
2073
2632
  // variable for the resumed agent's own assembly and for every subagent it
2074
2633
  // spawns (dsh-subagent inherits `parent.options.model`).
2075
- const resumeRoute = explicitModelRoute(configuredRoute);
2634
+ const resumeRoute = explicitModelRoute(configuredRoute) ?? deploymentRoute;
2076
2635
  if (requestedSessionId !== undefined) {
2077
2636
  const resumeId = SessionId(requestedSessionId);
2078
2637
  const existing = ctx.agents.get(resumeId);
@@ -2125,8 +2684,8 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2125
2684
  const composed = await composePreset(ctx, persisted);
2126
2685
  const recorded = await resolvePersistedRoute(ctx, resumeId);
2127
2686
  const resumeOptions = {
2128
- provider: resumeRoute?.provider ?? recorded?.provider,
2129
- model: resumeRoute?.model ?? recorded?.model,
2687
+ provider: recorded?.provider ?? resumeRoute?.provider,
2688
+ model: recorded?.model ?? resumeRoute?.model,
2130
2689
  };
2131
2690
  const resumed = await ctx.agents.resume({
2132
2691
  resumeSessionId: resumeId,
@@ -2134,14 +2693,14 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2134
2693
  ...(composed.setup === undefined ? {} : { setup: composed.setup }),
2135
2694
  });
2136
2695
  // Status-line route on resume: the route the session actually
2137
- // continues on — a complete cordis.yml pin, else the route its own
2138
- // request/header records carry (a bare log yields undefined and the
2139
- // caller falls back to the startup resolution, best effort).
2696
+ // continues on — its own request/header record, else the complete
2697
+ // cordis.yml deployment route (a bare log that pins none falls back to
2698
+ // the startup resolution, best effort).
2140
2699
  return {
2141
2700
  agent: resumed.agent,
2142
2701
  handle: resumed,
2143
2702
  agentPreset: composed.agentPreset,
2144
- route: resumeRoute ?? recordedModelRoute(snapshotLiveSessionEvents(resumed.agent.session)),
2703
+ route: recordedModelRoute(snapshotLiveSessionEvents(resumed.agent.session)) ?? resumeRoute,
2145
2704
  };
2146
2705
  }
2147
2706
  catch (error) {
@@ -2165,18 +2724,24 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2165
2724
  }
2166
2725
  }
2167
2726
  const sessionId = SessionId(randomUUID());
2168
- const presetPref = configuredPreset === undefined ? readPresetPref() : undefined;
2169
- const composed = await composePreset(ctx, configuredPreset ?? presetPref);
2170
- if (!migratePresetPref(presetPref, composed.agentPreset)) {
2727
+ // Fresh-session preset precedence (issue #8): the DSH_TUI_PRESET launch
2728
+ // instruction, then the persisted `/preset` choice (the standing user
2729
+ // default), then the cordis.yml value as the deployment default, then the
2730
+ // roster default. The pref is migrated only when it was the effective
2731
+ // source — an override must not rewrite the remembered id.
2732
+ const presetOverride = presetOverrideFromEnv();
2733
+ const presetPref = readPresetPref();
2734
+ const composed = await composePreset(ctx, presetOverride ?? presetPref ?? configuredPreset);
2735
+ if (presetOverride === undefined && !migratePresetPref(presetPref, composed.agentPreset)) {
2171
2736
  ctx.logger.warn(`dsh-tui: resolved preset preference "${presetPref}" as "${composed.agentPreset}" but could not persist the migrated id`);
2172
2737
  }
2173
2738
  // Fresh-session route precedence (issues #14/#30/#67): resolved atomically
2174
- // by the caller (complete cordis.yml route > the persisted `/model` choice
2739
+ // by the caller (the persisted `/model` choice > a complete cordis.yml route
2175
2740
  // > the harness default), then validated against the adapter catalog — a
2176
- // stale persisted choice falls back to the default route wholesale instead
2177
- // of reaching the server as an unknown model name.
2741
+ // stale persisted choice falls back to the deployment route wholesale
2742
+ // instead of reaching the server as an unknown model name.
2178
2743
  const llm = ctx.get('llm');
2179
- const { route, rejected } = await validateModelRoute(llm, startupRoute);
2744
+ const { route, rejected } = await validateModelRoute(llm, startupRoute, deploymentRoute);
2180
2745
  if (rejected !== undefined) {
2181
2746
  ctx.logger.warn(`dsh-tui: model route ${rejected.provider}/${rejected.model} is not advertised by provider "${rejected.provider}"; falling back to ${route.provider}/${route.model}`);
2182
2747
  }
@@ -2193,7 +2758,7 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2193
2758
  const bootReservation = bootReserved.ok ? bootReserved.reservation : undefined;
2194
2759
  let created;
2195
2760
  try {
2196
- created = await ctx.agents.create({
2761
+ created = await createFreshAgent(ctx, ctx.agents, {
2197
2762
  sessionId,
2198
2763
  meta: {
2199
2764
  ...meta,
@@ -2276,6 +2841,31 @@ export function runCrashExit(deps) {
2276
2841
  }
2277
2842
  deps.finish(`dsh-tui crashed: ${detail.message}`);
2278
2843
  }
2844
+ /** None where the root has no `ctx.agents` (before DSH composes, or a non-DSH kernel). */
2845
+ function dshAgentOn(ctx, agentId) {
2846
+ return ctx.get('agents')?.get(SessionId(agentId));
2847
+ }
2848
+ /**
2849
+ * The crash tail's resume markers (exported for scripts/verify-shutdown-fallback).
2850
+ * Leaves the marker a clean exit would, never CLEARS one.
2851
+ */
2852
+ export function writeCrashResumeMarkers(deps) {
2853
+ const { ctx, backendStart, channel } = deps;
2854
+ if (backendStart === undefined) {
2855
+ if (isExitResumable({
2856
+ pendingCount: channel.pending.length,
2857
+ liveAgent: dshAgentOn(ctx, channel.agentId),
2858
+ startupAgent: deps.startupAgent,
2859
+ })) {
2860
+ deps.writeResumeTarget(channel.agentId);
2861
+ }
2862
+ }
2863
+ else if (backendStart.persisted(channel.agentId, channel.rows)) {
2864
+ // Non-DSH sessions use their backend preference as the launcher marker.
2865
+ backendStart.sessionPrefs.setLastSession(channel.agentId);
2866
+ }
2867
+ deps.refreshLastRunRecord();
2868
+ }
2279
2869
  /**
2280
2870
  * Whether a user exit should leave the resume marker (and print the resume
2281
2871
  * hint). Must be judged against the LIVE session behind the channel, not the
@@ -2291,6 +2881,8 @@ export function isExitResumable(deps) {
2291
2881
  const agent = deps.liveAgent ?? deps.startupAgent;
2292
2882
  if (agent === undefined)
2293
2883
  return false;
2884
+ if (isUnstoredFreshSession(agent.session))
2885
+ return false;
2294
2886
  return (deps.pendingCount > 0 ||
2295
2887
  snapshotLiveSessionEvents(agent.session).some(event => event.type === 'user/message' && event.data.source.kind === 'user'));
2296
2888
  }
@@ -2336,7 +2928,7 @@ export async function finishExit(ctx, instance, fullscreen, notice, stderrNotice
2336
2928
  }
2337
2929
  const cursor = fullscreen ? '' : cursorMoveToFrameEnd(runtime);
2338
2930
  try {
2339
- runtime?.detachForShutdown?.();
2931
+ await runtime?.detachForShutdown?.();
2340
2932
  // The /update continuation spawns children that inherit this stdin;
2341
2933
  // strip the readable pump so the parent cannot swallow their input
2342
2934
  // (issues #284/#307). Harmless on plain exits — the process exits
@@ -2466,11 +3058,47 @@ function writeStream(stream, data) {
2466
3058
  function preservedSessionTail(sessionId, hint) {
2467
3059
  return sessionId === '' ? '\n\n' : ` Your session is preserved — resume with:\n${hint(sessionId)}\n\n`;
2468
3060
  }
2469
- function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile, id), options = {}) {
3061
+ /**
3062
+ * The entry's signals while following a replacement: SIGTERM is forwarded;
3063
+ * SIGINT/SIGHUP already reach it through the process group. A replacement
3064
+ * that dies by a termination signal ends this process the same way.
3065
+ */
3066
+ function superviseReplacement(exitSeam, options) {
3067
+ if (exitSeam === undefined)
3068
+ return options;
3069
+ let replacement;
3070
+ exitSeam.request = request => {
3071
+ if (replacement === undefined)
3072
+ return 'refused';
3073
+ const forwarded = request.kind === 'signal' && request.signal === 'SIGTERM';
3074
+ if (forwarded)
3075
+ replacement.kill('SIGTERM');
3076
+ logRestartEvent('supervisor: exit request while following the replacement', request.kind === 'signal' ? { signal: request.signal, forwarded } : { code: request.code });
3077
+ return 'supervising';
3078
+ };
3079
+ return {
3080
+ ...options,
3081
+ onSpawn: child => { replacement = child; },
3082
+ onTerminationSignal: signal => {
3083
+ if (TERMINATION_SIGNALS.includes(signal))
3084
+ dieBySignal(signal);
3085
+ },
3086
+ };
3087
+ }
3088
+ function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile, id), options = {}, exitSeam) {
2470
3089
  logRestartEvent('runRestart: entered, disposing cordis root');
2471
3090
  disposeRootAndThen(ctx, () => {
2472
3091
  logRestartEvent('runRestart: root disposed, starting restartTui');
2473
- void restartTui(sessionId, options).then(restartCode => {
3092
+ const supervision = superviseReplacement(exitSeam, options);
3093
+ // The React tree stays mounted while supervising, so a late UI read of the
3094
+ // ended channel is logged instead of crashing the supervisor.
3095
+ addProcessErrorAbsorber(error => {
3096
+ if (!(error instanceof Error) || error.message !== CHANNEL_UI_LIFETIME_ENDED)
3097
+ return false;
3098
+ logRestartEvent('supervisor: late UI read after dispose ignored', { at: error.stack?.split('\n').slice(1, 4).map(line => line.trim()).join(' | ') });
3099
+ return true;
3100
+ });
3101
+ void restartTui(sessionId, supervision).then(restartCode => {
2474
3102
  logRestartEvent('runRestart: restartTui resolved', { restartCode });
2475
3103
  if (restartCode !== 0) {
2476
3104
  writeHandoffNotice(`\ndsh-tui restart failed to spawn (exit ${restartCode}).${preservedSessionTail(sessionId, hint)}`);
@@ -2484,13 +3112,13 @@ function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile,
2484
3112
  });
2485
3113
  });
2486
3114
  }
2487
- function runUpdate(ctx, profile, sessionId, targetVersion, kernel, hint = id => resumeCommand(profile, id)) {
3115
+ function runUpdate(ctx, profile, sessionId, targetVersion, kernel, hint = id => resumeCommand(profile, id), exitSeam) {
2488
3116
  disposeRootAndThen(ctx, () => {
2489
3117
  if (profile === undefined) {
2490
3118
  process.stderr.write(`\n${t('update-aborted-no-profile')}\n`);
2491
3119
  process.exit(1);
2492
3120
  }
2493
- void updateTuiAndRestart(sessionId, profile, targetVersion, kernel).then(({ updateCode, restartCode }) => {
3121
+ void updateTuiAndRestart(sessionId, profile, targetVersion, kernel, superviseReplacement(exitSeam, {})).then(({ updateCode, restartCode }) => {
2494
3122
  if (updateCode !== 0) {
2495
3123
  process.stderr.write(`\ndsh-tui update failed (exit ${updateCode}).${preservedSessionTail(sessionId, hint)}`);
2496
3124
  }
@@ -2534,16 +3162,25 @@ function resumeCommand(profile, sessionId) {
2534
3162
  * handoff (update/restart) may legitimately take longer than the bound, and
2535
3163
  * reporting failure on a clean exit would mislead wrapper scripts.
2536
3164
  */
2537
- function disposeRootAndThen(ctx, done, fallbackCode = 1) {
3165
+ function disposeRootAndThen(ctx, done, fallback = 1) {
3166
+ const startedAt = Date.now();
2538
3167
  const timer = setTimeout(() => {
2539
3168
  // Diagnosis for a stalled disposal: without this line the fallback exit
2540
3169
  // is indistinguishable from a successful handoff in the field.
2541
- logRestartEvent('dispose: timeout, taking fallback exit', { fallbackCode });
2542
- process.exit(fallbackCode);
3170
+ logRestartEvent('dispose: timeout, taking fallback exit', typeof fallback === 'number' ? { fallbackCode: fallback } : { fallback: 'signal' });
3171
+ if (typeof fallback === 'number')
3172
+ process.exit(fallback);
3173
+ else
3174
+ fallback();
2543
3175
  }, 5000);
2544
3176
  timer.unref();
2545
- void withHostRootCapability(() => ctx.root.fiber.dispose()).finally(() => closeBackendResources()).then(() => {
3177
+ // A composition still running into this root settles first.
3178
+ // The pooled, process-wide resources of every backend this process actually
3179
+ // loaded are closed here — after the fiber, never before (P0 D4-P2/P3).
3180
+ void disposeRootSettled(ctx, () => withHostRootCapability(() => ctx.root.fiber.dispose())).finally(() => unloadBackends()).then(() => {
2546
3181
  clearTimeout(timer);
3182
+ // The teardown's length, to restart.log only (never the terminal).
3183
+ logRestartEvent('dispose: root disposed', { ms: Date.now() - startedAt });
2547
3184
  done();
2548
3185
  }, () => {
2549
3186
  clearTimeout(timer);