@deepseek-harness-tui/dsh-tui 0.14.1 → 0.16.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 (886) hide show
  1. package/README.md +163 -17
  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 +47 -10
  8. package/guide/dsh-tui-guide/codex-backend.md +26 -4
  9. package/guide/dsh-tui-guide/configuration.en.md +168 -18
  10. package/guide/dsh-tui-guide/configuration.md +144 -16
  11. package/guide/dsh-tui-guide/getting-started.en.md +15 -0
  12. package/guide/dsh-tui-guide/getting-started.md +15 -0
  13. package/guide/dsh-tui-guide/interaction.en.md +64 -24
  14. package/guide/dsh-tui-guide/interaction.md +49 -19
  15. package/guide/dsh-tui-guide/plugins.en.md +97 -3
  16. package/guide/dsh-tui-guide/plugins.md +55 -2
  17. package/guide/dsh-tui-guide/themes.en.md +23 -14
  18. package/guide/dsh-tui-guide/themes.md +19 -12
  19. package/guide/dsh-tui-guide/user-guide.en.md +53 -21
  20. package/guide/dsh-tui-guide/user-guide.md +43 -22
  21. package/lib/settings.json +103 -9
  22. package/lib/types/adapter/channel/projection.d.ts.map +1 -1
  23. package/lib/types/adapter/channel/projection.js +1 -0
  24. package/lib/types/adapter/channel/ui-policy.d.ts +1 -1
  25. package/lib/types/adapter/channel/ui-policy.d.ts.map +1 -1
  26. package/lib/types/adapter/channel/ui-policy.js +4 -0
  27. package/lib/types/adapter/channel/ui.d.ts +2 -0
  28. package/lib/types/adapter/channel/ui.d.ts.map +1 -1
  29. package/lib/types/adapter/channel/ui.js +6 -2
  30. package/lib/types/adapter/kernel/runtime.d.ts.map +1 -1
  31. package/lib/types/adapter/kernel/runtime.js +2 -0
  32. package/lib/types/adapter/ports/channel-catalog.d.ts +5 -1
  33. package/lib/types/adapter/ports/channel-catalog.d.ts.map +1 -1
  34. package/lib/types/adapter/ports/channel-settings.d.ts +14 -3
  35. package/lib/types/adapter/ports/channel-settings.d.ts.map +1 -1
  36. package/lib/types/adapter/ports/channel-ui.d.ts +56 -12
  37. package/lib/types/adapter/ports/channel-ui.d.ts.map +1 -1
  38. package/lib/types/adapter/ports/channel-view.d.ts +68 -0
  39. package/lib/types/adapter/ports/channel-view.d.ts.map +1 -1
  40. package/lib/types/adapter/ports/channel.d.ts +2 -0
  41. package/lib/types/adapter/ports/channel.d.ts.map +1 -1
  42. package/lib/types/adapter/spec/backend-capabilities.d.ts +22 -0
  43. package/lib/types/adapter/spec/backend-capabilities.d.ts.map +1 -0
  44. package/lib/types/adapter/spec/backend-capabilities.js +49 -0
  45. package/lib/types/adapter/spec/protocol-constants.d.ts.map +1 -1
  46. package/lib/types/adapter/spec/protocol-constants.js +10 -3
  47. package/lib/types/adapter/spec/tui-contributions.d.ts +3 -0
  48. package/lib/types/adapter/spec/tui-contributions.d.ts.map +1 -1
  49. package/lib/types/adapter/spec/tui-contributions.js +7 -0
  50. package/lib/types/adapter/standard/registry.d.ts +6 -0
  51. package/lib/types/adapter/standard/registry.d.ts.map +1 -1
  52. package/lib/types/adapter/standard/registry.js +65 -15
  53. package/lib/types/adapter/standard/tui-extension.d.ts.map +1 -1
  54. package/lib/types/adapter/standard/tui-extension.js +7 -1
  55. package/lib/types/adapter/standard/types.d.ts +10 -1
  56. package/lib/types/adapter/standard/types.d.ts.map +1 -1
  57. package/lib/types/adapter/upstream/host-descriptor-driver.d.ts.map +1 -1
  58. package/lib/types/adapter/upstream/host-descriptor-driver.js +52 -0
  59. package/lib/types/agent/backend-manifest.d.ts +152 -0
  60. package/lib/types/agent/backend-manifest.d.ts.map +1 -0
  61. package/lib/types/agent/backend-manifest.js +14 -0
  62. package/lib/types/agent/backend.d.ts +71 -5
  63. package/lib/types/agent/backend.d.ts.map +1 -1
  64. package/lib/types/agent/capabilities.d.ts +6 -0
  65. package/lib/types/agent/capabilities.d.ts.map +1 -1
  66. package/lib/types/agent/events.d.ts +3 -2
  67. package/lib/types/agent/events.d.ts.map +1 -1
  68. package/lib/types/agent/index.d.ts +2 -0
  69. package/lib/types/agent/index.d.ts.map +1 -1
  70. package/lib/types/agent/refs.d.ts +11 -1
  71. package/lib/types/agent/refs.d.ts.map +1 -1
  72. package/lib/types/agent/starting-session.d.ts +4 -0
  73. package/lib/types/agent/starting-session.d.ts.map +1 -0
  74. package/lib/types/agent/starting-session.js +19 -0
  75. package/lib/types/backends/claude/activity.d.ts +1 -1
  76. package/lib/types/backends/claude/activity.d.ts.map +1 -1
  77. package/lib/types/backends/claude/activity.js +5 -5
  78. package/lib/types/backends/claude/auth.d.ts.map +1 -1
  79. package/lib/types/backends/claude/auth.js +10 -3
  80. package/lib/types/backends/claude/backend.d.ts.map +1 -1
  81. package/lib/types/backends/claude/backend.js +103 -29
  82. package/lib/types/backends/claude/catalog.d.ts +2 -1
  83. package/lib/types/backends/claude/catalog.d.ts.map +1 -1
  84. package/lib/types/backends/claude/catalog.js +15 -5
  85. package/lib/types/backends/claude/channels.d.ts +5 -3
  86. package/lib/types/backends/claude/channels.d.ts.map +1 -1
  87. package/lib/types/backends/claude/channels.js +5 -4
  88. package/lib/types/backends/claude/contract.d.ts +4 -0
  89. package/lib/types/backends/claude/contract.d.ts.map +1 -1
  90. package/lib/types/backends/claude/contract.js +4 -0
  91. package/lib/types/backends/claude/controls.d.ts +4 -0
  92. package/lib/types/backends/claude/controls.d.ts.map +1 -1
  93. package/lib/types/backends/claude/controls.js +34 -42
  94. package/lib/types/backends/claude/dialogs.d.ts +8 -2
  95. package/lib/types/backends/claude/dialogs.d.ts.map +1 -1
  96. package/lib/types/backends/claude/dialogs.js +24 -16
  97. package/lib/types/backends/claude/images.d.ts +1 -1
  98. package/lib/types/backends/claude/images.d.ts.map +1 -1
  99. package/lib/types/backends/claude/images.js +7 -7
  100. package/lib/types/backends/claude/manifest.d.ts +21 -0
  101. package/lib/types/backends/claude/manifest.d.ts.map +1 -0
  102. package/lib/types/backends/claude/manifest.js +29 -0
  103. package/lib/types/backends/claude/older-history.js +2 -2
  104. package/lib/types/backends/claude/permissions.js +11 -11
  105. package/lib/types/backends/claude/prefs.d.ts +4 -2
  106. package/lib/types/backends/claude/prefs.d.ts.map +1 -1
  107. package/lib/types/backends/claude/prefs.js +4 -3
  108. package/lib/types/backends/claude/process.d.ts +16 -4
  109. package/lib/types/backends/claude/process.d.ts.map +1 -1
  110. package/lib/types/backends/claude/process.js +100 -37
  111. package/lib/types/backends/claude/send-message.d.ts +1 -1
  112. package/lib/types/backends/claude/send-message.d.ts.map +1 -1
  113. package/lib/types/backends/claude/session/auth.js +22 -22
  114. package/lib/types/backends/claude/session/channels.d.ts +4 -0
  115. package/lib/types/backends/claude/session/channels.d.ts.map +1 -1
  116. package/lib/types/backends/claude/session/channels.js +5 -3
  117. package/lib/types/backends/claude/session/lifecycle.d.ts.map +1 -1
  118. package/lib/types/backends/claude/session/lifecycle.js +31 -23
  119. package/lib/types/backends/claude/session/store.js +8 -8
  120. package/lib/types/backends/claude/session/subagents.js +2 -2
  121. package/lib/types/backends/claude/session/types.d.ts +7 -0
  122. package/lib/types/backends/claude/session/types.d.ts.map +1 -1
  123. package/lib/types/backends/claude/side-query.js +3 -3
  124. package/lib/types/backends/claude/task-output.js +5 -5
  125. package/lib/types/backends/claude/text.d.ts +778 -0
  126. package/lib/types/backends/claude/text.d.ts.map +1 -0
  127. package/lib/types/backends/claude/text.js +298 -0
  128. package/lib/types/backends/claude/transcript-file.d.ts +1 -1
  129. package/lib/types/backends/claude/transcript-file.d.ts.map +1 -1
  130. package/lib/types/backends/claude/transcript-file.js +4 -4
  131. package/lib/types/backends/claude/translate/attempts.js +2 -2
  132. package/lib/types/backends/claude/translate/core.js +3 -3
  133. package/lib/types/backends/claude/translate/messages.js +4 -4
  134. package/lib/types/backends/claude/translate/system.js +14 -14
  135. package/lib/types/backends/claude/translate/tasks.js +2 -2
  136. package/lib/types/backends/claude/translate/types.d.ts +1 -1
  137. package/lib/types/backends/claude/translate/types.d.ts.map +1 -1
  138. package/lib/types/backends/codex/auth/external-tokens.js +16 -16
  139. package/lib/types/backends/codex/auth/status.js +14 -14
  140. package/lib/types/backends/codex/backend.d.ts +4 -19
  141. package/lib/types/backends/codex/backend.d.ts.map +1 -1
  142. package/lib/types/backends/codex/backend.js +118 -25
  143. package/lib/types/backends/codex/catalog.d.ts +2 -1
  144. package/lib/types/backends/codex/catalog.d.ts.map +1 -1
  145. package/lib/types/backends/codex/catalog.js +63 -18
  146. package/lib/types/backends/codex/channels.d.ts +6 -3
  147. package/lib/types/backends/codex/channels.d.ts.map +1 -1
  148. package/lib/types/backends/codex/channels.js +15 -13
  149. package/lib/types/backends/codex/contract.d.ts +3 -0
  150. package/lib/types/backends/codex/contract.d.ts.map +1 -1
  151. package/lib/types/backends/codex/contract.js +3 -0
  152. package/lib/types/backends/codex/detect.d.ts.map +1 -1
  153. package/lib/types/backends/codex/detect.js +9 -6
  154. package/lib/types/backends/codex/manifest.d.ts +30 -0
  155. package/lib/types/backends/codex/manifest.d.ts.map +1 -0
  156. package/lib/types/backends/codex/manifest.js +23 -0
  157. package/lib/types/backends/codex/prefs.d.ts +4 -2
  158. package/lib/types/backends/codex/prefs.d.ts.map +1 -1
  159. package/lib/types/backends/codex/prefs.js +4 -3
  160. package/lib/types/backends/codex/rpc/hub.js +2 -2
  161. package/lib/types/backends/codex/session/activity.d.ts +1 -1
  162. package/lib/types/backends/codex/session/activity.d.ts.map +1 -1
  163. package/lib/types/backends/codex/session/activity.js +13 -13
  164. package/lib/types/backends/codex/session/approvals.js +30 -30
  165. package/lib/types/backends/codex/session/async-questions.d.ts +17 -0
  166. package/lib/types/backends/codex/session/async-questions.d.ts.map +1 -0
  167. package/lib/types/backends/codex/session/async-questions.js +89 -0
  168. package/lib/types/backends/codex/session/commands.d.ts +8 -2
  169. package/lib/types/backends/codex/session/commands.d.ts.map +1 -1
  170. package/lib/types/backends/codex/session/commands.js +71 -23
  171. package/lib/types/backends/codex/session/controls.d.ts +1 -1
  172. package/lib/types/backends/codex/session/controls.d.ts.map +1 -1
  173. package/lib/types/backends/codex/session/controls.js +21 -16
  174. package/lib/types/backends/codex/session/goals.d.ts.map +1 -1
  175. package/lib/types/backends/codex/session/goals.js +3 -3
  176. package/lib/types/backends/codex/session/history.js +2 -2
  177. package/lib/types/backends/codex/session/images.js +8 -8
  178. package/lib/types/backends/codex/session/input.d.ts +6 -2
  179. package/lib/types/backends/codex/session/input.d.ts.map +1 -1
  180. package/lib/types/backends/codex/session/input.js +55 -13
  181. package/lib/types/backends/codex/session/plan-review.js +6 -6
  182. package/lib/types/backends/codex/session/session.d.ts.map +1 -1
  183. package/lib/types/backends/codex/session/session.js +100 -42
  184. package/lib/types/backends/codex/session/side-query.d.ts.map +1 -1
  185. package/lib/types/backends/codex/session/side-query.js +27 -9
  186. package/lib/types/backends/codex/session/state.d.ts +6 -1
  187. package/lib/types/backends/codex/session/state.d.ts.map +1 -1
  188. package/lib/types/backends/codex/session/subagents.js +5 -5
  189. package/lib/types/backends/codex/session/tasks.js +4 -4
  190. package/lib/types/backends/codex/text.d.ts +933 -0
  191. package/lib/types/backends/codex/text.d.ts.map +1 -0
  192. package/lib/types/backends/codex/text.js +313 -0
  193. package/lib/types/backends/codex/translate/items.d.ts.map +1 -1
  194. package/lib/types/backends/codex/translate/items.js +7 -5
  195. package/lib/types/backends/codex/translate/live.d.ts.map +1 -1
  196. package/lib/types/backends/codex/translate/live.js +11 -8
  197. package/lib/types/backends/codex/translate/notices.js +10 -10
  198. package/lib/types/backends/codex/translate/question-reply.d.ts +11 -0
  199. package/lib/types/backends/codex/translate/question-reply.d.ts.map +1 -0
  200. package/lib/types/backends/codex/translate/question-reply.js +53 -0
  201. package/lib/types/backends/shared/channel-slug.d.ts +4 -0
  202. package/lib/types/backends/shared/channel-slug.d.ts.map +1 -0
  203. package/lib/types/backends/shared/channel-slug.js +25 -0
  204. package/lib/types/backends/shared/channel-tokens.d.ts.map +1 -1
  205. package/lib/types/backends/shared/channel-tokens.js +30 -23
  206. package/lib/types/{channel → backends/shared}/elicitation.d.ts +78 -21
  207. package/lib/types/backends/shared/elicitation.d.ts.map +1 -0
  208. package/lib/types/{channel → backends/shared}/elicitation.js +64 -66
  209. package/lib/types/backends/shared/image-size.d.ts +33 -0
  210. package/lib/types/backends/shared/image-size.d.ts.map +1 -0
  211. package/lib/types/backends/shared/image-size.js +134 -0
  212. package/lib/types/{utils → backends/shared}/jsonl.d.ts +5 -1
  213. package/lib/types/backends/shared/jsonl.d.ts.map +1 -0
  214. package/lib/types/{utils → backends/shared}/jsonl.js +5 -1
  215. package/lib/types/backends/shared/localized-text.d.ts +30 -0
  216. package/lib/types/backends/shared/localized-text.d.ts.map +1 -0
  217. package/lib/types/backends/shared/localized-text.js +24 -0
  218. package/lib/types/backends/shared/platform-text.d.ts +88 -0
  219. package/lib/types/backends/shared/platform-text.d.ts.map +1 -0
  220. package/lib/types/backends/shared/platform-text.js +33 -0
  221. package/lib/types/branding.d.ts +13 -0
  222. package/lib/types/branding.d.ts.map +1 -1
  223. package/lib/types/branding.js +18 -0
  224. package/lib/types/channel/channel-slug.d.ts +3 -1
  225. package/lib/types/channel/channel-slug.d.ts.map +1 -1
  226. package/lib/types/channel/channel-slug.js +3 -1
  227. package/lib/types/channel/projection.d.ts.map +1 -1
  228. package/lib/types/channel/projection.js +10 -0
  229. package/lib/types/channel/sanitize.d.ts +7 -0
  230. package/lib/types/channel/sanitize.d.ts.map +1 -1
  231. package/lib/types/channel/sanitize.js +9 -0
  232. package/lib/types/channel/usage.d.ts.map +1 -1
  233. package/lib/types/channel/usage.js +5 -0
  234. package/lib/types/commands.d.ts +15 -0
  235. package/lib/types/commands.d.ts.map +1 -1
  236. package/lib/types/commands.js +58 -10
  237. package/lib/types/components/AgentMessageComposer.d.ts.map +1 -1
  238. package/lib/types/components/AgentMessageComposer.js +3 -2
  239. package/lib/types/components/BtwPanel.d.ts.map +1 -1
  240. package/lib/types/components/BtwPanel.js +4 -2
  241. package/lib/types/components/Chat/JobCard.js +3 -3
  242. package/lib/types/components/CommandSuggestions.d.ts.map +1 -1
  243. package/lib/types/components/CommandSuggestions.js +24 -7
  244. package/lib/types/components/ContextBarView.d.ts +5 -1
  245. package/lib/types/components/ContextBarView.d.ts.map +1 -1
  246. package/lib/types/components/ContextBarView.js +6 -3
  247. package/lib/types/components/ExtensionDialog.d.ts.map +1 -1
  248. package/lib/types/components/ExtensionDialog.js +2 -3
  249. package/lib/types/components/GoalTodoPanel.d.ts.map +1 -1
  250. package/lib/types/components/GoalTodoPanel.js +14 -3
  251. package/lib/types/components/InlineMathParagraph.js +1 -1
  252. package/lib/types/components/KernelPicker.d.ts.map +1 -1
  253. package/lib/types/components/KernelPicker.js +1 -1
  254. package/lib/types/components/LogoV2.d.ts +4 -1
  255. package/lib/types/components/LogoV2.d.ts.map +1 -1
  256. package/lib/types/components/LogoV2.js +14 -9
  257. package/lib/types/components/MarkdownTable.d.ts.map +1 -1
  258. package/lib/types/components/MarkdownTable.js +4 -3
  259. package/lib/types/components/MessageList.d.ts.map +1 -1
  260. package/lib/types/components/MessageList.js +109 -15
  261. package/lib/types/components/ModelPicker.d.ts +24 -31
  262. package/lib/types/components/ModelPicker.d.ts.map +1 -1
  263. package/lib/types/components/ModelPicker.js +115 -40
  264. package/lib/types/components/PermissionsPicker.d.ts +10 -1
  265. package/lib/types/components/PermissionsPicker.d.ts.map +1 -1
  266. package/lib/types/components/PermissionsPicker.js +24 -16
  267. package/lib/types/components/PluginInstallPane.d.ts +21 -0
  268. package/lib/types/components/PluginInstallPane.d.ts.map +1 -0
  269. package/lib/types/components/PluginInstallPane.js +84 -0
  270. package/lib/types/components/PluginManagePane.d.ts +21 -0
  271. package/lib/types/components/PluginManagePane.d.ts.map +1 -0
  272. package/lib/types/components/PluginManagePane.js +69 -0
  273. package/lib/types/components/PromptEditor.d.ts +3 -3
  274. package/lib/types/components/PromptInput.d.ts +21 -3
  275. package/lib/types/components/PromptInput.d.ts.map +1 -1
  276. package/lib/types/components/PromptInput.js +65 -121
  277. package/lib/types/components/SdkInstallWizard.d.ts +3 -1
  278. package/lib/types/components/SdkInstallWizard.d.ts.map +1 -1
  279. package/lib/types/components/SdkInstallWizard.js +8 -1
  280. package/lib/types/components/SearchBox.d.ts +13 -15
  281. package/lib/types/components/SearchBox.d.ts.map +1 -1
  282. package/lib/types/components/SearchBox.js +58 -27
  283. package/lib/types/components/SubagentCard.d.ts +13 -1
  284. package/lib/types/components/SubagentCard.d.ts.map +1 -1
  285. package/lib/types/components/SubagentCard.js +27 -12
  286. package/lib/types/components/SubagentDashboard.d.ts +8 -1
  287. package/lib/types/components/SubagentDashboard.d.ts.map +1 -1
  288. package/lib/types/components/SubagentDashboard.js +58 -16
  289. package/lib/types/components/ThemePicker.d.ts +1 -1
  290. package/lib/types/components/ThemePicker.d.ts.map +1 -1
  291. package/lib/types/components/ThemePicker.js +14 -11
  292. package/lib/types/components/Tooltip.js +1 -1
  293. package/lib/types/components/WorkingSpinner.js +1 -1
  294. package/lib/types/components/WorkspaceFlowPicker.d.ts.map +1 -1
  295. package/lib/types/components/WorkspaceFlowPicker.js +4 -3
  296. package/lib/types/components/composerImageTokens.d.ts +24 -0
  297. package/lib/types/components/composerImageTokens.d.ts.map +1 -0
  298. package/lib/types/components/composerImageTokens.js +37 -0
  299. package/lib/types/components/design-system/InputCaret.d.ts +7 -0
  300. package/lib/types/components/design-system/InputCaret.d.ts.map +1 -0
  301. package/lib/types/components/design-system/InputCaret.js +9 -0
  302. package/lib/types/components/design-system/ListItem.d.ts +13 -2
  303. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  304. package/lib/types/components/design-system/ListItem.js +11 -5
  305. package/lib/types/components/design-system/ThemeProvider.d.ts.map +1 -1
  306. package/lib/types/components/design-system/ThemeProvider.js +6 -8
  307. package/lib/types/components/kernelCatalog.d.ts +55 -11
  308. package/lib/types/components/kernelCatalog.d.ts.map +1 -1
  309. package/lib/types/components/kernelCatalog.js +42 -23
  310. package/lib/types/components/launchpadActions.d.ts +4 -0
  311. package/lib/types/components/launchpadActions.d.ts.map +1 -1
  312. package/lib/types/components/launchpadActions.js +5 -4
  313. package/lib/types/components/launchpadLayout.d.ts +14 -1
  314. package/lib/types/components/launchpadLayout.d.ts.map +1 -1
  315. package/lib/types/components/launchpadLayout.js +38 -19
  316. package/lib/types/components/messages/TeamMessageBubble.d.ts +20 -0
  317. package/lib/types/components/messages/TeamMessageBubble.d.ts.map +1 -0
  318. package/lib/types/components/messages/TeamMessageBubble.js +15 -0
  319. package/lib/types/components/messages/UserPromptMessage.d.ts +4 -3
  320. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  321. package/lib/types/components/messages/UserPromptMessage.js +4 -3
  322. package/lib/types/components/questions/AskUserQuestionPanel.d.ts.map +1 -1
  323. package/lib/types/components/questions/AskUserQuestionPanel.js +8 -3
  324. package/lib/types/components/questions/PlanReviewPanel.d.ts.map +1 -1
  325. package/lib/types/components/questions/PlanReviewPanel.js +4 -3
  326. package/lib/types/components/questions/ProviderModelEditor.d.ts.map +1 -1
  327. package/lib/types/components/questions/ProviderModelEditor.js +3 -13
  328. package/lib/types/components/sessions/ForeignSessionRow.d.ts +3 -1
  329. package/lib/types/components/sessions/ForeignSessionRow.d.ts.map +1 -1
  330. package/lib/types/components/sessions/ForeignSessionRow.js +5 -2
  331. package/lib/types/components/sessions/SessionListRow.d.ts +3 -1
  332. package/lib/types/components/sessions/SessionListRow.d.ts.map +1 -1
  333. package/lib/types/components/sessions/SessionListRow.js +5 -2
  334. package/lib/types/components/sidePanel/PanelBar.d.ts +1 -0
  335. package/lib/types/components/sidePanel/PanelBar.d.ts.map +1 -1
  336. package/lib/types/components/sidePanel/PanelBar.js +21 -17
  337. package/lib/types/components/sidePanel/PanelHost.d.ts.map +1 -1
  338. package/lib/types/components/sidePanel/PanelHost.js +5 -1
  339. package/lib/types/components/sidePanel/PanelStore.d.ts +8 -0
  340. package/lib/types/components/sidePanel/PanelStore.d.ts.map +1 -1
  341. package/lib/types/components/sidePanel/PanelStore.js +11 -0
  342. package/lib/types/components/sidePanel/SidePanelColumn.d.ts +1 -1
  343. package/lib/types/components/sidePanel/SidePanelColumn.d.ts.map +1 -1
  344. package/lib/types/components/sidePanel/SidePanelColumn.js +15 -2
  345. package/lib/types/components/sidePanel/SidePanelLayout.d.ts +6 -4
  346. package/lib/types/components/sidePanel/SidePanelLayout.d.ts.map +1 -1
  347. package/lib/types/components/sidePanel/SidePanelLayout.js +33 -7
  348. package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts +7 -0
  349. package/lib/types/components/sidePanel/SidePanelRuntimeContext.d.ts.map +1 -1
  350. package/lib/types/components/sidePanel/SidePanelRuntimeContext.js +4 -0
  351. package/lib/types/components/sidePanel/btw/BtwComposer.d.ts +7 -2
  352. package/lib/types/components/sidePanel/btw/BtwComposer.d.ts.map +1 -1
  353. package/lib/types/components/sidePanel/btw/BtwComposer.js +28 -8
  354. package/lib/types/components/sidePanel/btw/BtwPanelAdapter.d.ts +2 -1
  355. package/lib/types/components/sidePanel/btw/BtwPanelAdapter.d.ts.map +1 -1
  356. package/lib/types/components/sidePanel/btw/BtwPanelAdapter.js +156 -21
  357. package/lib/types/components/sidePanel/btw/BtwThreadScene.d.ts +6 -2
  358. package/lib/types/components/sidePanel/btw/BtwThreadScene.d.ts.map +1 -1
  359. package/lib/types/components/sidePanel/btw/BtwThreadScene.js +64 -16
  360. package/lib/types/components/sidePanel/btw/BtwThreadView.d.ts +15 -5
  361. package/lib/types/components/sidePanel/btw/BtwThreadView.d.ts.map +1 -1
  362. package/lib/types/components/sidePanel/btw/BtwThreadView.js +101 -10
  363. package/lib/types/components/sidePanel/btw/threads.d.ts +18 -0
  364. package/lib/types/components/sidePanel/btw/threads.d.ts.map +1 -1
  365. package/lib/types/components/sidePanel/btw/threads.js +51 -0
  366. package/lib/types/components/sidePanel/builtinPanels.d.ts.map +1 -1
  367. package/lib/types/components/sidePanel/builtinPanels.js +5 -1
  368. package/lib/types/components/sidePanel/companion/CompanionPanel.js +2 -2
  369. package/lib/types/components/sidePanel/companion/SplashMascot.d.ts +5 -1
  370. package/lib/types/components/sidePanel/companion/SplashMascot.d.ts.map +1 -1
  371. package/lib/types/components/sidePanel/companion/SplashMascot.js +19 -4
  372. package/lib/types/components/sidePanel/companion/skins.d.ts +8 -0
  373. package/lib/types/components/sidePanel/companion/skins.d.ts.map +1 -1
  374. package/lib/types/components/sidePanel/companion/skins.js +57 -12
  375. package/lib/types/components/sidePanel/types.d.ts +7 -1
  376. package/lib/types/components/sidePanel/types.d.ts.map +1 -1
  377. package/lib/types/components/sidePanel/useSidePanel.d.ts +2 -0
  378. package/lib/types/components/sidePanel/useSidePanel.d.ts.map +1 -1
  379. package/lib/types/components/sidePanel/useSidePanel.js +11 -1
  380. package/lib/types/components/splashArtRows.d.ts +29 -0
  381. package/lib/types/components/splashArtRows.d.ts.map +1 -0
  382. package/lib/types/components/splashArtRows.js +31 -0
  383. package/lib/types/components/splashFonts.d.ts +24 -1
  384. package/lib/types/components/splashFonts.d.ts.map +1 -1
  385. package/lib/types/components/splashFonts.js +40 -5
  386. package/lib/types/components/teamRoster.d.ts +70 -0
  387. package/lib/types/components/teamRoster.d.ts.map +1 -0
  388. package/lib/types/components/teamRoster.js +78 -0
  389. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts +3 -1
  390. package/lib/types/components/workspaces/HomeWorkspaceRow.d.ts.map +1 -1
  391. package/lib/types/components/workspaces/HomeWorkspaceRow.js +5 -2
  392. package/lib/types/dsh-adapter/admission-loader.d.ts +102 -0
  393. package/lib/types/dsh-adapter/admission-loader.d.ts.map +1 -0
  394. package/lib/types/dsh-adapter/admission-loader.js +301 -0
  395. package/lib/types/dsh-adapter/agent-view.d.ts.map +1 -1
  396. package/lib/types/dsh-adapter/agent-view.js +3 -1
  397. package/lib/types/dsh-adapter/approvals.d.ts +7 -0
  398. package/lib/types/dsh-adapter/approvals.d.ts.map +1 -1
  399. package/lib/types/dsh-adapter/approvals.js +45 -1
  400. package/lib/types/dsh-adapter/backend/translate.d.ts.map +1 -1
  401. package/lib/types/dsh-adapter/backend/translate.js +71 -8
  402. package/lib/types/dsh-adapter/backend-admission.d.ts +64 -0
  403. package/lib/types/dsh-adapter/backend-admission.d.ts.map +1 -0
  404. package/lib/types/dsh-adapter/backend-admission.js +34 -0
  405. package/lib/types/dsh-adapter/backend-assembly.d.ts +31 -0
  406. package/lib/types/dsh-adapter/backend-assembly.d.ts.map +1 -0
  407. package/lib/types/dsh-adapter/backend-assembly.js +225 -0
  408. package/lib/types/dsh-adapter/backend-contribution.d.ts +62 -0
  409. package/lib/types/dsh-adapter/backend-contribution.d.ts.map +1 -0
  410. package/lib/types/dsh-adapter/backend-contribution.js +78 -0
  411. package/lib/types/dsh-adapter/backend-registry.d.ts +136 -0
  412. package/lib/types/dsh-adapter/backend-registry.d.ts.map +1 -0
  413. package/lib/types/dsh-adapter/backend-registry.js +304 -0
  414. package/lib/types/dsh-adapter/backends.d.ts +67 -24
  415. package/lib/types/dsh-adapter/backends.d.ts.map +1 -1
  416. package/lib/types/dsh-adapter/backends.generated.d.ts +12 -0
  417. package/lib/types/dsh-adapter/backends.generated.d.ts.map +1 -0
  418. package/lib/types/dsh-adapter/backends.generated.js +6 -0
  419. package/lib/types/dsh-adapter/backends.js +91 -46
  420. package/lib/types/dsh-adapter/bundled-presets.d.ts.map +1 -1
  421. package/lib/types/dsh-adapter/bundled-presets.js +60 -3
  422. package/lib/types/dsh-adapter/channel/action-readiness.d.ts +2 -0
  423. package/lib/types/dsh-adapter/channel/action-readiness.d.ts.map +1 -1
  424. package/lib/types/dsh-adapter/channel/action-readiness.js +9 -3
  425. package/lib/types/dsh-adapter/channel/agent-view-projection.js +2 -2
  426. package/lib/types/dsh-adapter/channel/background-action.d.ts.map +1 -1
  427. package/lib/types/dsh-adapter/channel/background-action.js +8 -3
  428. package/lib/types/dsh-adapter/channel/binding-events.d.ts +17 -3
  429. package/lib/types/dsh-adapter/channel/binding-events.d.ts.map +1 -1
  430. package/lib/types/dsh-adapter/channel/binding-events.js +19 -46
  431. package/lib/types/dsh-adapter/channel/channel-host.d.ts +45 -0
  432. package/lib/types/dsh-adapter/channel/channel-host.d.ts.map +1 -0
  433. package/lib/types/dsh-adapter/channel/channel-host.js +1 -0
  434. package/lib/types/dsh-adapter/channel/command-completions.d.ts +5 -0
  435. package/lib/types/dsh-adapter/channel/command-completions.d.ts.map +1 -1
  436. package/lib/types/dsh-adapter/channel/command-completions.js +79 -6
  437. package/lib/types/dsh-adapter/channel/compaction.d.ts +7 -2
  438. package/lib/types/dsh-adapter/channel/compaction.d.ts.map +1 -1
  439. package/lib/types/dsh-adapter/channel/compaction.js +100 -26
  440. package/lib/types/dsh-adapter/channel/composer-images.d.ts +4 -2
  441. package/lib/types/dsh-adapter/channel/composer-images.d.ts.map +1 -1
  442. package/lib/types/dsh-adapter/channel/composer-images.js +36 -5
  443. package/lib/types/dsh-adapter/channel/cordis-host.d.ts +14 -0
  444. package/lib/types/dsh-adapter/channel/cordis-host.d.ts.map +1 -0
  445. package/lib/types/dsh-adapter/channel/cordis-host.js +35 -0
  446. package/lib/types/dsh-adapter/channel/core/actions.d.ts +3 -3
  447. package/lib/types/dsh-adapter/channel/core/actions.d.ts.map +1 -1
  448. package/lib/types/dsh-adapter/channel/core/actions.js +15 -6
  449. package/lib/types/dsh-adapter/channel/core/binding-feed.d.ts +10 -3
  450. package/lib/types/dsh-adapter/channel/core/binding-feed.d.ts.map +1 -1
  451. package/lib/types/dsh-adapter/channel/core/binding-feed.js +5 -1
  452. package/lib/types/dsh-adapter/channel/core/compose.d.ts +12 -28
  453. package/lib/types/dsh-adapter/channel/core/compose.d.ts.map +1 -1
  454. package/lib/types/dsh-adapter/channel/core/compose.js +330 -117
  455. package/lib/types/dsh-adapter/channel/core/files.d.ts +7 -2
  456. package/lib/types/dsh-adapter/channel/core/files.d.ts.map +1 -1
  457. package/lib/types/dsh-adapter/channel/core/files.js +1 -0
  458. package/lib/types/dsh-adapter/channel/core/host.d.ts +12 -6
  459. package/lib/types/dsh-adapter/channel/core/host.d.ts.map +1 -1
  460. package/lib/types/dsh-adapter/channel/core/host.js +69 -35
  461. package/lib/types/dsh-adapter/channel/core/local-actions.d.ts +5 -3
  462. package/lib/types/dsh-adapter/channel/core/local-actions.d.ts.map +1 -1
  463. package/lib/types/dsh-adapter/channel/core/local-actions.js +5 -2
  464. package/lib/types/dsh-adapter/channel/core/session-controls.js +2 -2
  465. package/lib/types/dsh-adapter/channel/core/session-switch.d.ts +7 -4
  466. package/lib/types/dsh-adapter/channel/core/session-switch.d.ts.map +1 -1
  467. package/lib/types/dsh-adapter/channel/core/session-switch.js +8 -4
  468. package/lib/types/dsh-adapter/channel/core/sessions.d.ts +1 -1
  469. package/lib/types/dsh-adapter/channel/core/sessions.d.ts.map +1 -1
  470. package/lib/types/dsh-adapter/channel/core/sessions.js +75 -16
  471. package/lib/types/dsh-adapter/channel/extensions.d.ts +4 -3
  472. package/lib/types/dsh-adapter/channel/extensions.d.ts.map +1 -1
  473. package/lib/types/dsh-adapter/channel/extensions.js +109 -30
  474. package/lib/types/dsh-adapter/channel/input-delivery.d.ts +2 -2
  475. package/lib/types/dsh-adapter/channel/input-delivery.d.ts.map +1 -1
  476. package/lib/types/dsh-adapter/channel/input-delivery.js +14 -5
  477. package/lib/types/dsh-adapter/channel/job-projection.d.ts +4 -0
  478. package/lib/types/dsh-adapter/channel/job-projection.d.ts.map +1 -1
  479. package/lib/types/dsh-adapter/channel/job-projection.js +205 -24
  480. package/lib/types/dsh-adapter/channel/mentions.d.ts +3 -3
  481. package/lib/types/dsh-adapter/channel/mentions.d.ts.map +1 -1
  482. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts +7 -0
  483. package/lib/types/dsh-adapter/channel/mode-permission-actions.d.ts.map +1 -1
  484. package/lib/types/dsh-adapter/channel/mode-permission-actions.js +235 -13
  485. package/lib/types/dsh-adapter/channel/model-actions.d.ts +5 -1
  486. package/lib/types/dsh-adapter/channel/model-actions.d.ts.map +1 -1
  487. package/lib/types/dsh-adapter/channel/model-actions.js +48 -29
  488. package/lib/types/dsh-adapter/channel/model-selection.d.ts +17 -0
  489. package/lib/types/dsh-adapter/channel/model-selection.d.ts.map +1 -0
  490. package/lib/types/dsh-adapter/channel/model-selection.js +109 -0
  491. package/lib/types/dsh-adapter/channel/model-switch.d.ts +12 -21
  492. package/lib/types/dsh-adapter/channel/model-switch.d.ts.map +1 -1
  493. package/lib/types/dsh-adapter/channel/model-switch.js +39 -102
  494. package/lib/types/dsh-adapter/channel/permissions.d.ts.map +1 -1
  495. package/lib/types/dsh-adapter/channel/permissions.js +4 -1
  496. package/lib/types/dsh-adapter/channel/projection-helpers.d.ts.map +1 -1
  497. package/lib/types/dsh-adapter/channel/projection-helpers.js +2 -0
  498. package/lib/types/dsh-adapter/channel/projection.js +1 -1
  499. package/lib/types/dsh-adapter/channel/reports.d.ts +1 -1
  500. package/lib/types/dsh-adapter/channel/reports.d.ts.map +1 -1
  501. package/lib/types/dsh-adapter/channel/reports.js +11 -3
  502. package/lib/types/dsh-adapter/channel/session-fork.d.ts.map +1 -1
  503. package/lib/types/dsh-adapter/channel/session-fork.js +33 -20
  504. package/lib/types/dsh-adapter/channel/session-listing.d.ts +20 -0
  505. package/lib/types/dsh-adapter/channel/session-listing.d.ts.map +1 -0
  506. package/lib/types/dsh-adapter/channel/session-listing.js +51 -0
  507. package/lib/types/dsh-adapter/channel/session-metadata.d.ts +3 -1
  508. package/lib/types/dsh-adapter/channel/session-metadata.d.ts.map +1 -1
  509. package/lib/types/dsh-adapter/channel/session-metadata.js +60 -30
  510. package/lib/types/dsh-adapter/channel/session-reset.js +2 -2
  511. package/lib/types/dsh-adapter/channel/session-resume.d.ts.map +1 -1
  512. package/lib/types/dsh-adapter/channel/session-resume.js +22 -8
  513. package/lib/types/dsh-adapter/channel/session-rewind.d.ts.map +1 -1
  514. package/lib/types/dsh-adapter/channel/session-rewind.js +38 -24
  515. package/lib/types/dsh-adapter/channel/session-tree-actions.d.ts.map +1 -1
  516. package/lib/types/dsh-adapter/channel/session-tree-actions.js +38 -24
  517. package/lib/types/dsh-adapter/channel/settings-host.d.ts +5 -2
  518. package/lib/types/dsh-adapter/channel/settings-host.d.ts.map +1 -1
  519. package/lib/types/dsh-adapter/channel/settings-host.js +136 -5
  520. package/lib/types/dsh-adapter/channel/state.d.ts +43 -1
  521. package/lib/types/dsh-adapter/channel/state.d.ts.map +1 -1
  522. package/lib/types/dsh-adapter/channel/state.js +14 -1
  523. package/lib/types/dsh-adapter/channel/subagent-projection.d.ts.map +1 -1
  524. package/lib/types/dsh-adapter/channel/subagent-projection.js +84 -12
  525. package/lib/types/dsh-adapter/channel/task-completion.d.ts +6 -0
  526. package/lib/types/dsh-adapter/channel/task-completion.d.ts.map +1 -0
  527. package/lib/types/dsh-adapter/channel/task-completion.js +114 -0
  528. package/lib/types/dsh-adapter/channel/transcript.d.ts +8 -1
  529. package/lib/types/dsh-adapter/channel/transcript.d.ts.map +1 -1
  530. package/lib/types/dsh-adapter/channel/transcript.js +20 -3
  531. package/lib/types/dsh-adapter/channel/upstream-retry.d.ts +81 -0
  532. package/lib/types/dsh-adapter/channel/upstream-retry.d.ts.map +1 -0
  533. package/lib/types/dsh-adapter/channel/upstream-retry.js +138 -0
  534. package/lib/types/dsh-adapter/channel/workspace-actions.d.ts.map +1 -1
  535. package/lib/types/dsh-adapter/channel/workspace-actions.js +9 -8
  536. package/lib/types/dsh-adapter/channel-ui.d.ts +3 -1
  537. package/lib/types/dsh-adapter/channel-ui.d.ts.map +1 -1
  538. package/lib/types/dsh-adapter/channel-ui.js +4 -2
  539. package/lib/types/dsh-adapter/channel.d.ts +2 -2
  540. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  541. package/lib/types/dsh-adapter/channel.js +19 -5
  542. package/lib/types/dsh-adapter/compat/agent-team-0.2.0.d.ts +55 -0
  543. package/lib/types/dsh-adapter/compat/agent-team-0.2.0.d.ts.map +1 -0
  544. package/lib/types/dsh-adapter/compat/agent-team-0.2.0.js +96 -0
  545. package/lib/types/dsh-adapter/context-occupancy.d.ts +81 -4
  546. package/lib/types/dsh-adapter/context-occupancy.d.ts.map +1 -1
  547. package/lib/types/dsh-adapter/context-occupancy.js +108 -17
  548. package/lib/types/dsh-adapter/contract.d.ts +4 -4
  549. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  550. package/lib/types/dsh-adapter/contract.js +20 -5
  551. package/lib/types/dsh-adapter/effect-ledger.d.ts +7 -0
  552. package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
  553. package/lib/types/dsh-adapter/effect-ledger.js +8 -1
  554. package/lib/types/dsh-adapter/entry-slot.d.ts +38 -0
  555. package/lib/types/dsh-adapter/entry-slot.d.ts.map +1 -0
  556. package/lib/types/dsh-adapter/entry-slot.js +28 -0
  557. package/lib/types/dsh-adapter/fresh-agent.d.ts +22 -0
  558. package/lib/types/dsh-adapter/fresh-agent.d.ts.map +1 -0
  559. package/lib/types/dsh-adapter/fresh-agent.js +226 -0
  560. package/lib/types/dsh-adapter/host-access.d.ts +16 -1
  561. package/lib/types/dsh-adapter/host-access.d.ts.map +1 -1
  562. package/lib/types/dsh-adapter/host-access.js +148 -2
  563. package/lib/types/dsh-adapter/host-contract.d.ts +103 -0
  564. package/lib/types/dsh-adapter/host-contract.d.ts.map +1 -0
  565. package/lib/types/dsh-adapter/host-contract.js +77 -0
  566. package/lib/types/dsh-adapter/host-dsh.d.ts +112 -0
  567. package/lib/types/dsh-adapter/host-dsh.d.ts.map +1 -0
  568. package/lib/types/dsh-adapter/host-dsh.js +481 -0
  569. package/lib/types/dsh-adapter/host-entry.d.ts +9 -0
  570. package/lib/types/dsh-adapter/host-entry.d.ts.map +1 -0
  571. package/lib/types/dsh-adapter/host-entry.js +253 -0
  572. package/lib/types/dsh-adapter/index.d.ts +53 -17
  573. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  574. package/lib/types/dsh-adapter/index.js +63 -9
  575. package/lib/types/dsh-adapter/install/executors.d.ts +44 -0
  576. package/lib/types/dsh-adapter/install/executors.d.ts.map +1 -0
  577. package/lib/types/dsh-adapter/install/executors.js +15 -0
  578. package/lib/types/dsh-adapter/install/pnpm-profile-add.d.ts +3 -0
  579. package/lib/types/dsh-adapter/install/pnpm-profile-add.d.ts.map +1 -0
  580. package/lib/types/dsh-adapter/install/pnpm-profile-add.js +150 -0
  581. package/lib/types/dsh-adapter/invariant.d.ts +7 -2
  582. package/lib/types/dsh-adapter/invariant.d.ts.map +1 -1
  583. package/lib/types/dsh-adapter/invariant.js +6 -3
  584. package/lib/types/dsh-adapter/jobs.d.ts +7 -0
  585. package/lib/types/dsh-adapter/jobs.d.ts.map +1 -1
  586. package/lib/types/dsh-adapter/jobs.js +13 -0
  587. package/lib/types/dsh-adapter/lite-profile.d.ts +50 -0
  588. package/lib/types/dsh-adapter/lite-profile.d.ts.map +1 -0
  589. package/lib/types/dsh-adapter/lite-profile.js +49 -0
  590. package/lib/types/dsh-adapter/migrate/index.js +2 -2
  591. package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts +1 -1
  592. package/lib/types/dsh-adapter/migrate/parse/jsonl.d.ts.map +1 -1
  593. package/lib/types/dsh-adapter/migrate/parse/jsonl.js +4 -3
  594. package/lib/types/dsh-adapter/oauth/profiles.d.ts.map +1 -1
  595. package/lib/types/dsh-adapter/oauth/profiles.js +5 -1
  596. package/lib/types/dsh-adapter/panels.d.ts +7 -0
  597. package/lib/types/dsh-adapter/panels.d.ts.map +1 -1
  598. package/lib/types/dsh-adapter/panels.js +41 -2
  599. package/lib/types/dsh-adapter/plugin-completions.d.ts +32 -0
  600. package/lib/types/dsh-adapter/plugin-completions.d.ts.map +1 -0
  601. package/lib/types/dsh-adapter/plugin-completions.js +80 -0
  602. package/lib/types/dsh-adapter/plugin-declaration.d.ts +119 -0
  603. package/lib/types/dsh-adapter/plugin-declaration.d.ts.map +1 -0
  604. package/lib/types/dsh-adapter/plugin-declaration.js +183 -0
  605. package/lib/types/dsh-adapter/plugin-feed.d.ts +94 -0
  606. package/lib/types/dsh-adapter/plugin-feed.d.ts.map +1 -0
  607. package/lib/types/dsh-adapter/plugin-feed.js +135 -0
  608. package/lib/types/dsh-adapter/plugin-guard.d.ts +115 -0
  609. package/lib/types/dsh-adapter/plugin-guard.d.ts.map +1 -0
  610. package/lib/types/dsh-adapter/plugin-guard.js +108 -0
  611. package/lib/types/dsh-adapter/plugin-host.d.ts +13 -2
  612. package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
  613. package/lib/types/dsh-adapter/plugin-host.js +34 -8
  614. package/lib/types/dsh-adapter/plugin-install.d.ts +160 -0
  615. package/lib/types/dsh-adapter/plugin-install.d.ts.map +1 -0
  616. package/lib/types/dsh-adapter/plugin-install.js +545 -0
  617. package/lib/types/dsh-adapter/plugin-manage.d.ts +178 -0
  618. package/lib/types/dsh-adapter/plugin-manage.d.ts.map +1 -0
  619. package/lib/types/dsh-adapter/plugin-manage.js +468 -0
  620. package/lib/types/dsh-adapter/plugin-manager.d.ts +144 -0
  621. package/lib/types/dsh-adapter/plugin-manager.d.ts.map +1 -0
  622. package/lib/types/dsh-adapter/plugin-manager.js +83 -0
  623. package/lib/types/dsh-adapter/plugin.d.ts +50 -1
  624. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  625. package/lib/types/dsh-adapter/plugin.js +918 -234
  626. package/lib/types/dsh-adapter/plugins-info.d.ts +130 -8
  627. package/lib/types/dsh-adapter/plugins-info.d.ts.map +1 -1
  628. package/lib/types/dsh-adapter/plugins-info.js +469 -44
  629. package/lib/types/dsh-adapter/process-exit.d.ts +43 -0
  630. package/lib/types/dsh-adapter/process-exit.d.ts.map +1 -0
  631. package/lib/types/dsh-adapter/process-exit.js +60 -0
  632. package/lib/types/dsh-adapter/root-dispose.d.ts +18 -0
  633. package/lib/types/dsh-adapter/root-dispose.d.ts.map +1 -0
  634. package/lib/types/dsh-adapter/root-dispose.js +28 -0
  635. package/lib/types/dsh-adapter/session-list-metadata.d.ts +20 -0
  636. package/lib/types/dsh-adapter/session-list-metadata.d.ts.map +1 -0
  637. package/lib/types/dsh-adapter/session-list-metadata.js +56 -0
  638. package/lib/types/dsh-adapter/session-projection-store.d.ts +66 -0
  639. package/lib/types/dsh-adapter/session-projection-store.d.ts.map +1 -0
  640. package/lib/types/dsh-adapter/session-projection-store.js +149 -0
  641. package/lib/types/dsh-adapter/session-seed.d.ts +11 -0
  642. package/lib/types/dsh-adapter/session-seed.d.ts.map +1 -0
  643. package/lib/types/dsh-adapter/session-seed.js +47 -0
  644. package/lib/types/dsh-adapter/sessions/list.d.ts +3 -1
  645. package/lib/types/dsh-adapter/sessions/list.d.ts.map +1 -1
  646. package/lib/types/dsh-adapter/sessions/list.js +11 -5
  647. package/lib/types/dsh-adapter/sessions/snapshot.d.ts +0 -2
  648. package/lib/types/dsh-adapter/sessions/snapshot.d.ts.map +1 -1
  649. package/lib/types/dsh-adapter/sessions/snapshot.js +7 -70
  650. package/lib/types/dsh-adapter/settingsEditor.js +2 -2
  651. package/lib/types/dsh-adapter/sideQuestion.d.ts +21 -0
  652. package/lib/types/dsh-adapter/sideQuestion.d.ts.map +1 -1
  653. package/lib/types/dsh-adapter/sideQuestion.js +53 -0
  654. package/lib/types/dsh-adapter/startup-args.d.ts +2 -0
  655. package/lib/types/dsh-adapter/startup-args.d.ts.map +1 -1
  656. package/lib/types/dsh-adapter/startup-args.js +5 -0
  657. package/lib/types/dsh-adapter/subagents.d.ts +33 -1
  658. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -1
  659. package/lib/types/dsh-adapter/subagents.js +112 -9
  660. package/lib/types/dsh-adapter/team-mode.d.ts +74 -0
  661. package/lib/types/dsh-adapter/team-mode.d.ts.map +1 -0
  662. package/lib/types/dsh-adapter/team-mode.js +188 -0
  663. package/lib/types/dsh-adapter/team-roster.d.ts +67 -0
  664. package/lib/types/dsh-adapter/team-roster.d.ts.map +1 -0
  665. package/lib/types/dsh-adapter/team-roster.js +136 -0
  666. package/lib/types/dsh-adapter/team-tools.d.ts +58 -0
  667. package/lib/types/dsh-adapter/team-tools.d.ts.map +1 -0
  668. package/lib/types/dsh-adapter/team-tools.js +208 -0
  669. package/lib/types/dsh-adapter/tui-settings.d.ts +57 -0
  670. package/lib/types/dsh-adapter/tui-settings.d.ts.map +1 -0
  671. package/lib/types/dsh-adapter/tui-settings.js +199 -0
  672. package/lib/types/dsh-adapter/types.d.ts +1 -1
  673. package/lib/types/dsh-adapter/types.d.ts.map +1 -1
  674. package/lib/types/effortPrefs.d.ts +6 -3
  675. package/lib/types/effortPrefs.d.ts.map +1 -1
  676. package/lib/types/effortPrefs.js +7 -4
  677. package/lib/types/hostEntryRoute.d.ts +18 -0
  678. package/lib/types/hostEntryRoute.d.ts.map +1 -0
  679. package/lib/types/hostEntryRoute.js +57 -0
  680. package/lib/types/i18n.d.ts +961 -1439
  681. package/lib/types/i18n.d.ts.map +1 -1
  682. package/lib/types/i18n.js +443 -387
  683. package/lib/types/ink/components/AlternateScreen.js +2 -2
  684. package/lib/types/ink/components/App.d.ts +1 -0
  685. package/lib/types/ink/components/App.d.ts.map +1 -1
  686. package/lib/types/ink/components/App.js +16 -4
  687. package/lib/types/ink/components/CursorDeclarationContext.d.ts +9 -0
  688. package/lib/types/ink/components/CursorDeclarationContext.d.ts.map +1 -1
  689. package/lib/types/ink/components/CursorDeclarationContext.js +2 -0
  690. package/lib/types/ink/components/ScrollBox.d.ts +9 -0
  691. package/lib/types/ink/components/ScrollBox.d.ts.map +1 -1
  692. package/lib/types/ink/components/ScrollBox.js +23 -0
  693. package/lib/types/ink/dom.d.ts.map +1 -1
  694. package/lib/types/ink/hooks/use-copy-on-select.d.ts +2 -2
  695. package/lib/types/ink/hooks/use-copy-on-select.d.ts.map +1 -1
  696. package/lib/types/ink/hooks/use-copy-on-select.js +13 -7
  697. package/lib/types/ink/hooks/use-declared-cursor.d.ts +11 -3
  698. package/lib/types/ink/hooks/use-declared-cursor.d.ts.map +1 -1
  699. package/lib/types/ink/hooks/use-declared-cursor.js +30 -10
  700. package/lib/types/ink/ink.d.ts +22 -17
  701. package/lib/types/ink/ink.d.ts.map +1 -1
  702. package/lib/types/ink/ink.js +151 -40
  703. package/lib/types/ink/kitty-graphics.d.ts +2 -0
  704. package/lib/types/ink/kitty-graphics.d.ts.map +1 -1
  705. package/lib/types/ink/kitty-graphics.js +6 -1
  706. package/lib/types/ink/node-cache.d.ts +2 -1
  707. package/lib/types/ink/node-cache.d.ts.map +1 -1
  708. package/lib/types/ink/output.d.ts +10 -4
  709. package/lib/types/ink/output.d.ts.map +1 -1
  710. package/lib/types/ink/output.js +14 -10
  711. package/lib/types/ink/render-node-to-output.d.ts +5 -2
  712. package/lib/types/ink/render-node-to-output.d.ts.map +1 -1
  713. package/lib/types/ink/render-node-to-output.js +27 -9
  714. package/lib/types/ink/renderer.d.ts +1 -0
  715. package/lib/types/ink/renderer.d.ts.map +1 -1
  716. package/lib/types/ink/renderer.js +4 -3
  717. package/lib/types/ink/root.d.ts +4 -1
  718. package/lib/types/ink/root.d.ts.map +1 -1
  719. package/lib/types/ink/screen.d.ts +4 -2
  720. package/lib/types/ink/screen.d.ts.map +1 -1
  721. package/lib/types/ink/selection.d.ts +10 -1
  722. package/lib/types/ink/selection.d.ts.map +1 -1
  723. package/lib/types/ink/selection.js +94 -25
  724. package/lib/types/ink/stdin-handoff.d.ts +11 -0
  725. package/lib/types/ink/stdin-handoff.d.ts.map +1 -0
  726. package/lib/types/ink/stdin-handoff.js +20 -0
  727. package/lib/types/ink/styles.d.ts +2 -1
  728. package/lib/types/ink/styles.d.ts.map +1 -1
  729. package/lib/types/ink/terminal-image.d.ts +6 -4
  730. package/lib/types/ink/terminal-image.d.ts.map +1 -1
  731. package/lib/types/ink/terminal-image.js +16 -39
  732. package/lib/types/ink/terminal-notification.d.ts +8 -0
  733. package/lib/types/ink/terminal-notification.d.ts.map +1 -0
  734. package/lib/types/ink/terminal-notification.js +83 -0
  735. package/lib/types/ink/terminal.d.ts +9 -0
  736. package/lib/types/ink/terminal.d.ts.map +1 -1
  737. package/lib/types/ink/terminal.js +34 -1
  738. package/lib/types/ink/termio/csi.d.ts +11 -0
  739. package/lib/types/ink/termio/csi.d.ts.map +1 -1
  740. package/lib/types/ink/termio/csi.js +13 -0
  741. package/lib/types/ink/update-overflow-guard.d.ts +6 -0
  742. package/lib/types/ink/update-overflow-guard.d.ts.map +1 -1
  743. package/lib/types/ink/update-overflow-guard.js +17 -0
  744. package/lib/types/ink/wrap-text.d.ts +6 -0
  745. package/lib/types/ink/wrap-text.d.ts.map +1 -1
  746. package/lib/types/ink/wrap-text.js +17 -0
  747. package/lib/types/kernelPrefs.d.ts +130 -17
  748. package/lib/types/kernelPrefs.d.ts.map +1 -1
  749. package/lib/types/kernelPrefs.js +98 -35
  750. package/lib/types/math/inline-layout.d.ts +2 -0
  751. package/lib/types/math/inline-layout.d.ts.map +1 -1
  752. package/lib/types/math/inline-layout.js +5 -27
  753. package/lib/types/modelGroups.d.ts +15 -27
  754. package/lib/types/modelGroups.d.ts.map +1 -1
  755. package/lib/types/modelGroups.js +17 -31
  756. package/lib/types/modelPrefs.d.ts +5 -4
  757. package/lib/types/modelPrefs.d.ts.map +1 -1
  758. package/lib/types/modelPrefs.js +5 -4
  759. package/lib/types/modelRoute.d.ts +23 -9
  760. package/lib/types/modelRoute.d.ts.map +1 -1
  761. package/lib/types/modelRoute.js +25 -11
  762. package/lib/types/native-ts/yoga-layout/index.d.ts.map +1 -1
  763. package/lib/types/native-ts/yoga-layout/index.js +8 -1
  764. package/lib/types/permissionPrefs.d.ts +34 -0
  765. package/lib/types/permissionPrefs.d.ts.map +1 -0
  766. package/lib/types/permissionPrefs.js +61 -0
  767. package/lib/types/permissionPresetLabels.d.ts +25 -0
  768. package/lib/types/permissionPresetLabels.d.ts.map +1 -0
  769. package/lib/types/permissionPresetLabels.js +33 -0
  770. package/lib/types/plugin-host.d.ts +1 -0
  771. package/lib/types/plugin-host.d.ts.map +1 -1
  772. package/lib/types/preset-patch.d.ts +22 -0
  773. package/lib/types/preset-patch.d.ts.map +1 -0
  774. package/lib/types/preset-patch.js +14 -0
  775. package/lib/types/presetPrefs.d.ts +17 -3
  776. package/lib/types/presetPrefs.d.ts.map +1 -1
  777. package/lib/types/presetPrefs.js +20 -3
  778. package/lib/types/reload.d.ts +6 -10
  779. package/lib/types/reload.d.ts.map +1 -1
  780. package/lib/types/reload.js +14 -14
  781. package/lib/types/screens/Chat.d.ts +55 -16
  782. package/lib/types/screens/Chat.d.ts.map +1 -1
  783. package/lib/types/screens/Chat.js +742 -280
  784. package/lib/types/screens/Launchpad.d.ts +33 -17
  785. package/lib/types/screens/Launchpad.d.ts.map +1 -1
  786. package/lib/types/screens/Launchpad.js +256 -78
  787. package/lib/types/screens/SessionSupervisor.d.ts.map +1 -1
  788. package/lib/types/screens/SessionSupervisor.js +53 -12
  789. package/lib/types/screens/Settings.d.ts.map +1 -1
  790. package/lib/types/screens/Settings.js +7 -0
  791. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  792. package/lib/types/screens/StatusLine.js +24 -7
  793. package/lib/types/screens/StatusMetrics.d.ts +46 -1
  794. package/lib/types/screens/StatusMetrics.d.ts.map +1 -1
  795. package/lib/types/screens/StatusMetrics.js +72 -0
  796. package/lib/types/screens/chat/useKernelPicker.d.ts +8 -4
  797. package/lib/types/screens/chat/useKernelPicker.d.ts.map +1 -1
  798. package/lib/types/screens/chat/useKernelPicker.js +61 -21
  799. package/lib/types/screens/chat/useModelPicker.d.ts +42 -0
  800. package/lib/types/screens/chat/useModelPicker.d.ts.map +1 -0
  801. package/lib/types/screens/chat/useModelPicker.js +182 -0
  802. package/lib/types/screens/chat/usePluginWriteFlow.d.ts +68 -0
  803. package/lib/types/screens/chat/usePluginWriteFlow.d.ts.map +1 -0
  804. package/lib/types/screens/chat/usePluginWriteFlow.js +92 -0
  805. package/lib/types/screens/chatOverlay.d.ts +30 -5
  806. package/lib/types/screens/chatOverlay.d.ts.map +1 -1
  807. package/lib/types/screens/chatOverlay.js +0 -1
  808. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts +4 -1
  809. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.d.ts.map +1 -1
  810. package/lib/types/screens/sessionSupervisor/ForeignSessionPanes.js +26 -5
  811. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts +1 -1
  812. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.d.ts.map +1 -1
  813. package/lib/types/screens/sessionSupervisor/useSessionSupervisor.js +51 -29
  814. package/lib/types/sessionHistory.d.ts +5 -0
  815. package/lib/types/sessionHistory.d.ts.map +1 -1
  816. package/lib/types/sessionHistory.js +5 -0
  817. package/lib/types/sessionModes.d.ts +5 -3
  818. package/lib/types/sessionModes.d.ts.map +1 -1
  819. package/lib/types/sessionModes.js +9 -3
  820. package/lib/types/sessions/listSnapshot.d.ts +6 -0
  821. package/lib/types/sessions/listSnapshot.d.ts.map +1 -0
  822. package/lib/types/sessions/listSnapshot.js +67 -0
  823. package/lib/types/settings/definitions.d.ts +50 -0
  824. package/lib/types/settings/definitions.d.ts.map +1 -1
  825. package/lib/types/settings/definitions.js +42 -8
  826. package/lib/types/terminal-utils/cjk-emphasis.d.ts +28 -0
  827. package/lib/types/terminal-utils/cjk-emphasis.d.ts.map +1 -0
  828. package/lib/types/terminal-utils/cjk-emphasis.js +31 -0
  829. package/lib/types/terminal-utils/markdown.d.ts +3 -1
  830. package/lib/types/terminal-utils/markdown.d.ts.map +1 -1
  831. package/lib/types/terminal-utils/markdown.js +7 -2
  832. package/lib/types/theme.d.ts +29 -12
  833. package/lib/types/theme.d.ts.map +1 -1
  834. package/lib/types/theme.js +310 -23
  835. package/lib/types/themeCatalog.d.ts.map +1 -1
  836. package/lib/types/themeCatalog.js +21 -2
  837. package/lib/types/tips.js +4 -4
  838. package/lib/types/tuiSettingsFile.d.ts +56 -0
  839. package/lib/types/tuiSettingsFile.d.ts.map +1 -0
  840. package/lib/types/tuiSettingsFile.js +206 -0
  841. package/lib/types/ui.d.ts +2 -0
  842. package/lib/types/ui.d.ts.map +1 -1
  843. package/lib/types/ui.js +2 -0
  844. package/lib/types/update.d.ts +72 -26
  845. package/lib/types/update.d.ts.map +1 -1
  846. package/lib/types/update.js +131 -43
  847. package/lib/types/utils/bmp.d.ts +7 -0
  848. package/lib/types/utils/bmp.d.ts.map +1 -1
  849. package/lib/types/utils/bmp.js +27 -0
  850. package/lib/types/utils/bootTrace.d.ts +2 -0
  851. package/lib/types/utils/bootTrace.d.ts.map +1 -0
  852. package/lib/types/utils/bootTrace.js +20 -0
  853. package/lib/types/utils/clipboard.d.ts.map +1 -1
  854. package/lib/types/utils/clipboard.js +11 -3
  855. package/lib/types/utils/credentials.d.ts +14 -2
  856. package/lib/types/utils/credentials.d.ts.map +1 -1
  857. package/lib/types/utils/credentials.js +34 -11
  858. package/lib/types/utils/imageResize.d.ts +10 -20
  859. package/lib/types/utils/imageResize.d.ts.map +1 -1
  860. package/lib/types/utils/imageResize.js +61 -134
  861. package/lib/types/utils/inputPaste.d.ts +2 -2
  862. package/lib/types/utils/inputPaste.js +2 -2
  863. package/lib/types/utils/pastedImagePath.d.ts +5 -0
  864. package/lib/types/utils/pastedImagePath.d.ts.map +1 -1
  865. package/lib/types/utils/pastedImagePath.js +33 -0
  866. package/lib/types/utils/paths.d.ts +11 -0
  867. package/lib/types/utils/paths.d.ts.map +1 -1
  868. package/lib/types/utils/paths.js +13 -0
  869. package/package.json +150 -114
  870. package/presets/winbash.mjs +716 -0
  871. package/tui-profile/feed/README.md +63 -0
  872. package/tui-profile/feed/first-party.json +10 -0
  873. package/tui-profile/protocols/profile-definitions.d.ts +1 -0
  874. package/tui-profile/protocols/profile-definitions.js +14 -0
  875. package/tui-profile/protocols/tui-contributions.d.ts +55 -0
  876. package/tui-profile/protocols/tui-contributions.js +141 -0
  877. package/tui-profile/registry/contracts/backend-v1alpha1.json +13 -0
  878. package/tui-profile/registry/registry-0.15.json +12 -0
  879. package/lib/types/backends/claude/install.d.ts +0 -15
  880. package/lib/types/backends/claude/install.d.ts.map +0 -1
  881. package/lib/types/backends/claude/install.js +0 -91
  882. package/lib/types/channel/elicitation.d.ts.map +0 -1
  883. package/lib/types/dsh-adapter/channel/session-lineage.d.ts +0 -42
  884. package/lib/types/dsh-adapter/channel/session-lineage.d.ts.map +0 -1
  885. package/lib/types/dsh-adapter/channel/session-lineage.js +0 -51
  886. package/lib/types/utils/jsonl.d.ts.map +0 -1
@@ -4,12 +4,19 @@ 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';
16
+ import { createPluginInstallEntry } from './plugin-install.js';
17
+ import { createPluginManageEntry } from './plugin-manage.js';
12
18
  import { formatSessionRef } from '../agent/refs.js';
19
+ import { createStartingSession } from '../agent/starting-session.js';
13
20
  import { createChannelSceneOutlet } from './channel-scene-outlet.js';
14
21
  import { mountChannelUi } from './channel-ui.js';
15
22
  import { bindChannelCommands } from './channel/commands.js';
@@ -18,6 +25,7 @@ import { createChildStderrReporter, installChildStderrGuard } from './childStder
18
25
  import { removeClipboardImageDir } from '../utils/clipboard.js';
19
26
  import { appendCrashLog, serializeCrashDetail, unserializableCrashDetail } from '../utils/crashDetail.js';
20
27
  import { logForDebugging } from '../utils/debug.js';
28
+ import { createTerminalNotifier } from '../ink/terminal-notification.js';
21
29
  import { isEnvTruthy } from '../utils/envUtils.js';
22
30
  import { QuestionStore, bindQuestionStore } from './questions.js';
23
31
  import { prepareQuestionAnswerer } from './questions-answerer.js';
@@ -29,23 +37,24 @@ import { readActivityFrames } from '../activityPrefs.js';
29
37
  import { commitFullscreenFactoryMigration, planFullscreenFactoryMigration, readAppliedMigrations } from '../migrationPrefs.js';
30
38
  import { readModelPref } from '../modelPrefs.js';
31
39
  import { explicitModelRoute, recordedModelRoute, resolveModelRoute, validateModelRoute } from '../modelRoute.js';
32
- import { migratePresetPref, readPresetPref } from '../presetPrefs.js';
40
+ import { migratePresetPref, presetOverrideFromEnv, readPresetPref } from '../presetPrefs.js';
33
41
  import { readEffortPref } from '../effortPrefs.js';
34
42
  import { composePreset, filterMinimalPresetTools, resolvePersistedPreset, resolvePersistedRoute, runningPresetOf } from './presets.js';
43
+ import { createFreshAgent, isUnstoredFreshSession } from './fresh-agent.js';
35
44
  import { ensurePackagedPresets } from './packaged-presets.js';
36
45
  import { registerBundledPresets } from './bundled-presets.js';
37
46
  import { ensureLegacySessionEventTypes, snapshotLiveSessionEvents } from './compat/index.js';
38
- import { clearResumeTarget, resumeTargetFromArgv, writeResumeTarget } from '../sessionHistory.js';
39
- import { initialPromptFromCmdlineArgs } from './startup-args.js';
47
+ import { clearResumeTarget, resumeTargetFromArgv, stripResumeArgs, writeResumeTarget } from '../sessionHistory.js';
48
+ import { cmdlineArgsOf, initialPromptFromCmdlineArgs } from './startup-args.js';
40
49
  import { readHomePrefs } from '../homePrefs.js';
41
50
  import { handoffEventTag, formatHandoffNotice } from '../handoffEvents.js';
42
51
  import { armFirstFrameAck, beginHandoffAck, handoffAttemptId, ownsAltScreenExit } from '../handoffAck.js';
43
- import { KERNEL_IDS, KERNEL_SWITCH_HANDOFF_ENV, kernelDisplayName, readKernelPrefs, resolveRememberedBackend, writeKernelPrefs } from '../kernelPrefs.js';
52
+ 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
53
  import { shouldOfferOnboarding } from '../onboardingPrefs.js';
45
54
  import { resolveSessionCwd } from '../utils/workspaceRoot.js';
46
- import { beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice, writeLastRunRecord } from '../update.js';
55
+ import { LAUNCH_PROMPT_SENT_ENV, beginRestartAttempt, checkForTuiUpdate, installedTuiVersion, isBootDeadlockTarget, isStandaloneRuntime, isVersionNewer, logRestartEvent, resolveDshProfileName, resolveTuiUpdateTarget, restartTui, updateTuiAndRestart, writeHandoffNotice, writeLastRunRecord } from '../update.js';
47
56
  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';
57
+ 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
58
  import { draftComboConflicts, effectiveComboString, parseComboDraft, setKeymapOverrides, SHORTCUT_ACTIONS, } from '../utils/keymap.js';
50
59
  import { attachHerdrIntegration } from '../herdr.js';
51
60
  import { logMouseDebug } from '../utils/debug.js';
@@ -55,8 +64,11 @@ import { startSessionMountHeartbeat } from './session-mount-heartbeat.js';
55
64
  import { reserveMount } from '../sessionMounts.js';
56
65
  import { getHostDialogStore } from './dialogs.js';
57
66
  import { getHostStatusStore } from './status.js';
67
+ import { attachSessionListMetadata } from './session-list-metadata.js';
58
68
  import { createActivityStore } from './activity-store.js';
59
69
  import { createContextOccupancyStore } from './context-occupancy.js';
70
+ import { createTeamRosterStore } from './team-roster.js';
71
+ import { createTeamCommandEntry, filterTeamReplacedTools } from './team-mode.js';
60
72
  import { getHostToastStore } from './toast.js';
61
73
  import { getHostShortcuts } from './shortcuts.js';
62
74
  import { getHostThemes } from './themes.js';
@@ -64,6 +76,10 @@ import { attachSessionToWorkspace } from './workspace.js';
64
76
  import { createLocalWorkspaceRuntime, getHostWorkspaceRuntime } from './workspaces.js';
65
77
  import { getHostSettingsSections, getLocalSettingsSectionsHost } from './settings-sections.js';
66
78
  import { compositionRoot, withHostRootCapability } from './host-access.js';
79
+ import { armAdmissionLoader } from './admission-loader.js';
80
+ import { assembleBackendsFromProfile } from './backend-assembly.js';
81
+ import { applyConfiguredSidePanelPanels, applySidePanelPanelsFromSettings } from './panels.js';
82
+ import { disposeRootSettled } from './root-dispose.js';
67
83
  import { render, ThemeProvider, AlternateScreen } from '../ui.js';
68
84
  import { PageMargin } from '../components/PageMargin.js';
69
85
  import { normalizeSplashFont } from '../components/splashFonts.js';
@@ -73,7 +89,11 @@ import instances from '../ink/instances.js';
73
89
  import { cursorMove, DISABLE_KITTY_KEYBOARD, DISABLE_MODIFY_OTHER_KEYS, DISABLE_WIN32_INPUT_MODE } from '../ink/termio/csi.js';
74
90
  import { DBP, DFE, DISABLE_MOUSE_TRACKING, EXIT_ALT_SCREEN, SHOW_CURSOR } from '../ink/termio/dec.js';
75
91
  import { CLEAR_ITERM2_PROGRESS, CLEAR_TAB_STATUS, supportsTabStatus, wrapForMultiplexer } from '../ink/termio/osc.js';
76
- import { fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update-overflow-guard.js';
92
+ import { addProcessErrorAbsorber, fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update-overflow-guard.js';
93
+ import { CHANNEL_UI_LIFETIME_ENDED } from '../adapter/channel/ui.js';
94
+ import { markBoot } from '../utils/bootTrace.js';
95
+ import { TERMINATION_SIGNALS, dieBySignal } from './process-exit.js';
96
+ import { StartupOpenError } from './channel/state.js';
77
97
  /**
78
98
  * Interactive TUI front door for DeepSeek Harness agents.
79
99
  *
@@ -98,6 +118,11 @@ import { fatalReasonForExit, registerProcessGuardFatalSink } from '../ink/update
98
118
  let lastBootedFullscreen;
99
119
  // Image preferences also stay fixed across host recomposes until /restart.
100
120
  let lastBootedTerminalImages;
121
+ // A replacement's first process already sent the launch prompt. Read once per
122
+ // process (a recompose must not resend it) and dropped from the environment,
123
+ // so a `dsh-tui "prompt"` run from inside the session keeps its own prompt.
124
+ const launchPromptSent = process.env[LAUNCH_PROMPT_SENT_ENV] === '1';
125
+ delete process.env[LAUNCH_PROMPT_SENT_ENV];
101
126
  // Kept importable from here: the startup parser moved to its own dependency-free
102
127
  // module so argv probes can load it without the whole plugin graph.
103
128
  export { initialPromptFromCmdlineArgs };
@@ -128,8 +153,10 @@ export function resolveTuiHostMode(stdoutIsTTY = process.stdout.isTTY === true,
128
153
  const explicitTuiLaunch = env.DSH_TUI_LAUNCHER_VERSION !== undefined || isStandaloneRuntime();
129
154
  return explicitTuiLaunch ? 'invalid-explicit-launch' : 'headless-host';
130
155
  }
131
- export async function apply(ctx, runtimeConfig, configOwner = ctx) {
156
+ export async function apply(ctx, runtimeConfig, configOwner = ctx, runtimeOptions = {}) {
132
157
  const config = configValues(runtimeConfig);
158
+ const taskNotifier = createTerminalNotifier(process.stdout);
159
+ taskNotifier.configure(config.taskNotifications, config.notificationMethod);
133
160
  // /restart handoff diagnosis: the replacement process is marked by env and
134
161
  // logs its boot progress to ~/.dsh-tui/restart.log (ordinary launches stay
135
162
  // silent). First line lands before anything in this function can throw.
@@ -203,21 +230,40 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
203
230
  }
204
231
  // Validate settings before creating an agent or taking over the terminal.
205
232
  const tuiSettingsNs = resolveSettingsNamespace(configOwner, Config);
233
+ // Register before agent creation so its first checkpoint includes web list metadata.
234
+ attachSessionListMetadata(ctx);
235
+ // The TUI's own settings layer (~/.dsh-tui/settings.json): the same
236
+ // document on every kernel and entry. Other namespaces still go
237
+ // to the host's settings service (absent in the standalone entry).
238
+ const tuiSettings = createTuiSettingsService({
239
+ ns: tuiSettingsNs,
240
+ profile: resolveDshProfileName() ?? runtimeOptions.profile ?? 'dsh-tui',
241
+ keys: EDITABLE_CONFIG_KEYS,
242
+ delegate: () => ctx.get('settings'),
243
+ });
244
+ // The in-process DSH kernel's screen mounts before DSH has composed: what
245
+ // needs DSH's services runs later, in `attachDsh`.
246
+ const entrySlot = runtimeOptions.entrySlot;
247
+ const dshInEntry = entrySlot?.dsh === true;
206
248
  // Modern hosts own a declarative registry; old hosts discover directories.
207
249
  // 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`);
250
+ const installPresets = async (presetCtx) => {
251
+ if (!await registerBundledPresets(presetCtx))
252
+ try {
253
+ for (const result of ensurePackagedPresets()) {
254
+ if (result.status === 'conflict') {
255
+ presetCtx.logger.warn(`dsh-tui: packaged preset "${result.id}" was not installed because an unmanaged preset already uses that id`);
256
+ }
213
257
  }
214
258
  }
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
- }
259
+ catch (error) {
260
+ // A read-only home must not make the whole terminal unusable; the other
261
+ // official and user presets remain available.
262
+ presetCtx.logger.warn(`dsh-tui: unable to install packaged presets (${error instanceof Error ? error.message : String(error)})`);
263
+ }
264
+ };
265
+ if (!dshInEntry)
266
+ await installPresets(ctx);
221
267
  // UI language resolution: DSH_TUI_LANG env var wins, then the
222
268
  // settings.yaml `dsh-tui.lang` user layer (applied once the settings
223
269
  // namespace registers below), then cordis.yml `lang`, then the
@@ -263,6 +309,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
263
309
  }
264
310
  }
265
311
  }
312
+ // The questionnaire store every backend's questions park in (a non-DSH
313
+ // session's `question.request` events too); Chat renders it.
314
+ const questionStore = new QuestionStore(adapterRuntimeFor(ctx));
315
+ bindQuestionStore(ctx, questionStore);
316
+ // One store, one teardown effect on both API lines. The compatibility
317
+ // adapter binds either registration to this Cordis fiber; this separate
318
+ // effect rejects asks still parked in the UI during teardown.
319
+ ctx.effect(() => () => questionStore.rejectAll());
266
320
  // DSH user-interaction seam: the model's ask_user_question tool parks on
267
321
  // the userQuestions service until a UI answerer responds. Mount the
268
322
  // service when the composition doesn't (the official dsh-base
@@ -272,46 +326,49 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
272
326
  // seat is registered below; the 0.1.2 line's agent-aware waterfall needs the
273
327
  // channel owner and is therefore registered immediately after the channel
274
328
  // 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
- }
329
+ // inject proxy. The in-process DSH kernel mounts these from its dsh-tui row
330
+ // once DSH has composed: mounting them on the entry's root first would
331
+ // take the `userQuestions` seat the profile's own row registers.
332
+ const mountDshQuestionSeams = (dshCtx) => {
333
+ const userQuestions = dshCtx.get('userQuestions') ?? new UserQuestionService(dshCtx);
334
+ dshCtx.plugin(toolAskUser);
335
+ // The host-level tool mount above is intentional for the TUI and for user
336
+ // presets, but the official Minimal preset is a single-tool trajectory (one
337
+ // persistent shell: bash on POSIX, pwsh on Windows). Filter only that preset
338
+ // at the final assembly boundary. Reading the session on every assembly also
339
+ // makes blank-session /preset switches and resumed sessions behave correctly.
340
+ // Agent Teams is decided at the same boundary: presets keep their legacy
341
+ // delegation rows, and a request that carries `spawn_teammate` drops
342
+ // `subagent` / `subagent_fork` (team-mode.ts), so a team bundle switched
343
+ // at run time reaches the sessions already running.
344
+ dshCtx.on('system-prompt/assemble', async (_assembly, context, next) => {
345
+ const assembled = filterTeamReplacedTools(await next());
346
+ const presetId = context.agent === undefined ? undefined : runningPresetOf(context.agent.session);
347
+ return filterMinimalPresetTools(assembled, presetId);
348
+ });
349
+ // `/debug-prompt` snapshots the final provider-neutral request at the
350
+ // llm/stream boundary, after every prompt and tool contributor has run.
351
+ registerPromptDebug(dshCtx);
352
+ // API selection and registration live behind one adapter boundary. The UI
353
+ // bootstrap only translates a legacy seat conflict into its visible notice.
354
+ const registration = prepareQuestionAnswerer(dshCtx, userQuestions, questionStore);
355
+ const seatDecision = registration.kind === 'legacy' ? registration.yieldDecision : undefined;
356
+ let notice;
357
+ if (seatDecision?.action === 'alert-unverified') {
358
+ dshCtx.logger.error(`dsh-tui: user-questions provider seat is held by a component self-reporting as ${seatDecision.incumbentId} ` +
359
+ '(identity not host-verified); this TUI will not register its questionnaire and model questions may be answered by it');
360
+ notice = t('question-provider-occupied-unverified', { id: seatDecision.incumbentId ?? '' });
361
+ }
362
+ else if (seatDecision?.action === 'alert') {
363
+ const displayId = seatDecision.incumbentId ?? t('question-provider-occupied-unknown');
364
+ dshCtx.logger.error(`dsh-tui: user-questions provider seat is held by a non-host component (${displayId}); ` +
365
+ 'this TUI will not register its questionnaire and model questions may be answered by it');
366
+ notice = t('question-provider-occupied', { id: displayId });
367
+ }
368
+ return { registration, notice };
369
+ };
370
+ const questionSeams = dshInEntry ? undefined : mountDshQuestionSeams(ctx);
371
+ let questionSeatNotice = questionSeams?.notice;
315
372
  // Child-process stderr guard (issue #17): MCP servers spawned with an
316
373
  // inherited stderr (the MCP SDK's stdio default) write straight to the
317
374
  // terminal device from the child process, bypassing the renderer's own
@@ -344,20 +401,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
344
401
  provider: config.provider,
345
402
  model: config.model,
346
403
  };
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);
404
+ // Atomic route resolution (issue #67): the persisted `/model` choice — the
405
+ // route the last session ran — wins whole; else a complete cordis.yml route
406
+ // wins whole as the deployment DEFAULT (it decides the first run, never a
407
+ // later one); else Harness's provider-neutral agent-default-model
408
+ // selection. The local DeepSeek pair remains the final fallback for bare
409
+ // embedders without that service. This lets optional provider bundles supply
410
+ // the same default to Web and TUI without patching this front door by name.
411
+ const { startupRoute, deploymentRoute } = startupRoutes(ctx, configuredRoute);
361
412
  // Session cwd (issue #96): explicit cordis.yml `cwd` wins; otherwise the
362
413
  // git worktree root containing the launch directory (the launch directory
363
414
  // itself outside any worktree), so `@` completion and mention expansion
@@ -413,11 +464,23 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
413
464
  ctx.logger.warn('dsh-tui: tuiPluginHost service is not mounted; plugin grant store, runtime generation and Host Descriptor are unavailable. ' +
414
465
  '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
466
  }
467
+ // Admit third-party activations from their package-root `dsh-plugin.json`.
468
+ armAdmissionLoader(ctx);
469
+ // Stage D-1: the backends installed bundles declare join the registry *here*,
470
+ // before anything below parses a backend choice (`parseBackendChoice` reads the
471
+ // registry for membership, so an id registered later reads as unknown: dsh plus a
472
+ // warning). Synchronous on purpose — a bundle's own rows are composed after this
473
+ // one, so waiting for them would be waiting past the parse. This is the caller
474
+ // notes 0009's "Wired (W-2)" section was still missing.
475
+ assembleBackendsFromProfile(ctx);
476
+ // The in-process DSH kernel mounts before the profile's workspace providers
477
+ // exist: a provider URI resolves in `attachDsh`, and an unresolvable one
478
+ // fails the startup session there instead of the mount.
416
479
  const initialWorkspace = requestedWorkspace === undefined
417
480
  ? undefined
418
481
  : await workspaceService.resolve(requestedWorkspace);
419
- if (requestedWorkspace !== undefined && initialWorkspace === undefined) {
420
- throw new Error(`dsh-tui: unsupported or unavailable workspace target: ${requestedWorkspace}`);
482
+ if (requestedWorkspace !== undefined && initialWorkspace === undefined && !dshInEntry) {
483
+ throw new Error(unsupportedWorkspaceTarget(requestedWorkspace));
421
484
  }
422
485
  const sessionCwd = initialWorkspace?.cwd ?? resolveSessionCwd(config.cwd);
423
486
  const meta = { cwd: sessionCwd };
@@ -425,8 +488,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
425
488
  // `dsh --profile tui` forwards `--resume` verbatim instead, so fall back to
426
489
  // the same app-argv snapshot as the initial prompt. Raw process.argv also
427
490
  // 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;
491
+ const cmdlineArgs = cmdlineArgsOf(ctx);
492
+ // The raw request, deliberately self-contained: `scripts/verify-startup-argv.mjs`
493
+ // replays this very statement (picked out of the compiled `apply` by name) to check
494
+ // the launcher's argv interception end to end, so it has to stay computable from
495
+ // `config` and `cmdlineArgs` alone. Whether the target may actually be used is
496
+ // decided further down, once the backend this boot lands on is known.
430
497
  const launchSessionId = config.sessionId ?? resumeTargetFromArgv(cmdlineArgs ?? process.argv.slice(2));
431
498
  // The session's backend (Config `backend`, `dsh-tui --backend`). A non-DSH
432
499
  // backend opens its own session here and skips everything DSH-specific
@@ -447,28 +514,118 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
447
514
  const handoffBackendRaw = process.env[KERNEL_SWITCH_HANDOFF_ENV];
448
515
  if (handoffBackendRaw !== undefined)
449
516
  delete process.env[KERNEL_SWITCH_HANDOFF_ENV];
450
- const handoffBackend = normalizeBackendChoice(handoffBackendRaw);
517
+ const handoffBackend = parseBackendChoice(handoffBackendRaw);
451
518
  // Fullscreen kernel switch: the old process spawned this one with an ACK
452
519
  // pipe on fd 3 and the alternate screen still open (see handoffAck.ts).
453
520
  const handoffAck = beginHandoffAck();
454
521
  if (handoffAck !== undefined) {
455
522
  logRestartEvent('handoff/boot: ack armed', { attemptId: handoffAttemptId() ?? '' });
456
523
  }
457
- const backendChoice = resolveRememberedBackend({
524
+ // Every source is parsed in two stages (P0 D1): syntax first, then the
525
+ // registry. An id that is syntactically valid but not installed must behave
526
+ // exactly like an unknown value did before the set was open — dsh plus a
527
+ // warning, never a crashed boot. `envKnown` carries that into the resolver for
528
+ // the one source that arrives raw; the other three are parsed above/beside.
529
+ const configuredBackend = parseBackendChoice(config.backend);
530
+ const rememberedBackend = readKernelPrefs().backend;
531
+ const entryKernel = runtimeOptions.entryKernel;
532
+ const backendChoice = entryKernel ?? (dshInEntry ? DSH_BACKEND_ID : resolveRememberedBackend({
458
533
  ...(handoffBackend === undefined ? {} : { handoff: handoffBackend }),
459
- configured: config.backend,
534
+ configured: configuredBackend,
460
535
  envRaw: rawBackend,
461
- memory: readKernelPrefs().backend,
536
+ envKnown: isRegisteredBackend,
537
+ memory: isRegisteredBackend(rememberedBackend) ? rememberedBackend : undefined,
538
+ }));
539
+ const rawBackendGiven = rawBackend === undefined ? '' : rawBackend.trim();
540
+ // The launcher marks a *derived* resume target with the backend it read it from
541
+ // (RESUME_BACKEND_ENV); a target the user placed carries no mark. Read and
542
+ // deleted right away, like the kernel handoff above, so no child inherits it.
543
+ // The mark is parsed with the syntax gate alone: the source is typically a
544
+ // backend that is NOT registered here, which is the whole reason to check.
545
+ // A marked target belongs to that one backend, and this boot may have landed
546
+ // elsewhere — an uninstalled plugin id falls back to dsh (P0 D1), an unset
547
+ // DSH_TUI_BACKEND follows the remembered kernel instead. Handing it over either
548
+ // fails or resumes an unrelated session that happens to share the id, so it is
549
+ // revoked rather than carried across (PR #1380 review R2) — and a revocation is
550
+ // a refusal, not a cold start (roadmap §6 item 11, B-2a).
551
+ const resumeBackendRaw = process.env[RESUME_BACKEND_ENV];
552
+ if (resumeBackendRaw !== undefined)
553
+ delete process.env[RESUME_BACKEND_ENV];
554
+ // A safe-mode retry carries a licence to survive a revoked target (the launcher
555
+ // sets RESUME_RETRY_ENV; see resolveResumeTarget). Read and deleted right away,
556
+ // like the mark above: no child may inherit it.
557
+ const resumeRetry = process.env[RESUME_RETRY_ENV] !== undefined;
558
+ delete process.env[RESUME_RETRY_ENV];
559
+ const resumeTarget = resolveResumeTarget({
560
+ sessionId: launchSessionId,
561
+ sourceBackend: parseBackendId(resumeBackendRaw),
562
+ backendChoice,
563
+ retry: resumeRetry,
462
564
  });
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`);
565
+ const effectiveSessionId = resumeTarget.kind === 'usable' ? resumeTarget.sessionId : undefined;
566
+ // The non-DSH path below opens `backendChoice`'s own session — a revoked target
567
+ // must not reach it either.
568
+ const configuredSessionId = resumeTarget.kind === 'revoked' ? undefined : config.sessionId;
569
+ // Safe-mode retries replay the original argv. Revocation must also remove its
570
+ // resume flags, or either startup path can reopen the replacement backend's
571
+ // own last session after promising a cold start. Prompt parsing keeps raw argv.
572
+ const startupArgv = resumeTarget.kind === 'revoked'
573
+ ? stripResumeArgs(cmdlineArgs ?? process.argv.slice(2))
574
+ : cmdlineArgs ?? process.argv.slice(2);
575
+ if (resumeTarget.kind === 'revoked') {
576
+ // Fail closed (roadmap §6 item 11): the target belongs to another backend, so
577
+ // this boot neither carries it over nor quietly starts a session nobody asked
578
+ // for. A fatal refusal is thrown into the startup funnel, which reports it on
579
+ // stderr and exits non-zero after restoring the terminal; the only tolerated
580
+ // case is the safe-mode retry, whose target comes from last-run.json and whose
581
+ // failure would close the user's last way back (RESUME_RETRY_ENV).
582
+ // The advice must be executable (PR #1449 review R2): when the source backend
583
+ // is not registered here, `--backend <from> --resume` would hit this same
584
+ // refusal, so that branch says the backend is unavailable instead. The other
585
+ // branch names the id (`kernelId`), never the display label — `--backend`
586
+ // takes an id.
587
+ const refusalKey = !resumeTarget.fatal
588
+ ? 'resume-target-revoked-retry'
589
+ : isRegisteredBackend(resumeTarget.from) ? 'resume-target-revoked' : 'resume-target-revoked-uninstalled';
590
+ const refusal = t(refusalKey, {
591
+ from: resumeTarget.from,
592
+ kernel: backendLabel(backendChoice),
593
+ kernelId: backendChoice,
594
+ });
595
+ if (resumeTarget.fatal)
596
+ throw new Error(refusal);
597
+ ctx.logger.warn(refusal);
598
+ }
599
+ // Config and handoff outrank env: an ignored env value cannot invalidate the
600
+ // selected backend's resume target or claim that this boot fell back to DSH.
601
+ if (handoffBackend === undefined && configuredBackend === undefined
602
+ && rawBackendGiven !== '' && !isRegisteredBackend(rawBackendGiven.toLowerCase())) {
603
+ // Two cases, two sentences: a typo is not an uninstalled plugin (P0 D1).
604
+ const installed = listBackends().map(entry => entry.manifest.id).join(', ');
605
+ // The plain fallback (no resume request) keeps P0 D1's shape: dsh plus a
606
+ // warning, exit code 0. With a resume target in hand it must not: dsh would
607
+ // be asked to open an id that belongs to the backend the user named, which
608
+ // either fails or resumes a session of another backend (roadmap §6 item 11).
609
+ // Nothing is resumed, nothing is created — the boot stops here instead.
610
+ if (effectiveSessionId !== undefined) {
611
+ throw new Error(t('resume-backend-unavailable', {
612
+ backend: rawBackendGiven,
613
+ session: effectiveSessionId,
614
+ installed,
615
+ }));
616
+ }
617
+ ctx.logger.warn(isBackendIdSyntax(rawBackendGiven)
618
+ ? `dsh-tui: DSH_TUI_BACKEND="${rawBackend}" names no installed backend (registered: ${installed}); starting on dsh`
619
+ : `dsh-tui: DSH_TUI_BACKEND="${rawBackend}" is not a valid backend id (lowercase letters, digits and dashes); starting on dsh`);
465
620
  }
466
621
  /**
467
622
  * Whether a Config row or DSH_TUI_BACKEND overrides the selector's
468
623
  * remembered kernel. The selector then says so: a switch still restarts
469
624
  * onto the chosen kernel, but the next plain launch follows the override.
625
+ * Only a registered Config choice pins: the schema keeps syntactically
626
+ * valid but uninstalled ids too, which must not prevent a memory fallback.
470
627
  */
471
- const backendPinned = config.backend !== undefined
628
+ const backendPinned = configuredBackend !== undefined
472
629
  || (rawBackend !== undefined && rawBackend.trim() !== '');
473
630
  // A remembered kernel whose backend cannot open (its SDK uninstalled, its
474
631
  // session store unreadable, …) must not kill the boot: without an explicit
@@ -477,27 +634,74 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
477
634
  // Explicit choices (flag/Config/handoff) and explicit resume targets keep
478
635
  // the hard failure: silently swapping what the user named would be worse.
479
636
  let backendStart;
637
+ /** The startup session still opening (`deferBackendOpen`); the channel adopts it. */
638
+ let backendStartup;
639
+ /** Once set, the channel's binding owns the session. */
640
+ let backendStartupAdopted = false;
480
641
  let backendFallbackNotice;
642
+ const deferBackendOpen = runtimeOptions.deferBackendOpen === true;
643
+ if (deferBackendOpen && backendChoice === 'dsh' && !dshInEntry) {
644
+ throw new Error('dsh-tui: the standalone entry cannot host the DSH kernel; start it through the profile launcher');
645
+ }
646
+ /** The in-process DSH kernel's startup session: settled by `attachDsh`
647
+ * (the profile's dsh-tui row) or by a failed composition. */
648
+ let settleDshStartup;
649
+ let dshStartupSettled = false;
650
+ const dshStartup = dshInEntry
651
+ ? new Promise((resolve, reject) => {
652
+ settleDshStartup = {
653
+ resolve: startup => { dshStartupSettled = true; resolve(startup); },
654
+ reject: error => { dshStartupSettled = true; reject(error); },
655
+ };
656
+ })
657
+ : undefined;
658
+ // A boot that fails before the channel holds the startup must not leak an
659
+ // unhandled rejection.
660
+ dshStartup?.catch(() => undefined);
481
661
  if (backendChoice !== 'dsh') {
482
662
  try {
483
- backendStart = await openBackendStartup(ctx, await BACKEND_LOADERS[backendChoice](), {
663
+ const startupInput = {
484
664
  cwd: sessionCwd,
485
- stderr: line => {
665
+ stderr: (line) => {
486
666
  logForDebugging(`[${backendChoice}-stderr] ${line}`);
487
667
  stderrReporter.push(line);
488
668
  },
489
- ...(config.sessionId === undefined ? {} : { configuredSessionId: config.sessionId }),
490
- argv: cmdlineArgs ?? process.argv.slice(2),
491
- });
669
+ ...(configuredSessionId === undefined ? {} : { configuredSessionId }),
670
+ argv: startupArgv,
671
+ };
672
+ // One load per launch, shared by the deferred path (the entry mounts
673
+ // between `prepare` and `start`) and the eager one.
674
+ const backendModule = await loadBackend(backendChoice);
675
+ if (deferBackendOpen) {
676
+ const prepared = await prepareBackendStartup(ctx, backendModule, startupInput);
677
+ const startup = prepared.start().then(opened => ({ session: opened.session, history: opened.initialHistory }));
678
+ backendStartup = startup;
679
+ // As for dshStartup above; the effect closes an unadopted session.
680
+ startup.catch(() => undefined);
681
+ ctx.effect(() => () => {
682
+ if (backendStartupAdopted)
683
+ return;
684
+ void startup.then(opened => opened.session.dispose(), () => undefined).catch(() => undefined);
685
+ }, 'dsh-tui deferred backend startup');
686
+ backendStart = { ...prepared, session: createStartingSession(prepared.backendId, sessionCwd), initialHistory: [] };
687
+ }
688
+ else {
689
+ backendStart = await openBackendStartup(ctx, backendModule, startupInput);
690
+ }
492
691
  }
493
692
  catch (error) {
494
- if (backendPinned || handoffBackend !== undefined || launchSessionId !== undefined)
693
+ if (deferBackendOpen || backendPinned || handoffBackend !== undefined || effectiveSessionId !== undefined)
495
694
  throw error;
496
695
  const reason = error instanceof Error ? error.message : String(error);
497
696
  logForDebugging(`dsh-tui: remembered backend "${backendChoice}" failed to open (${reason}); falling back to dsh`);
498
- backendFallbackNotice = t('kernel-memory-fallback', { name: kernelDisplayName(backendChoice), reason });
697
+ backendFallbackNotice = t('kernel-memory-fallback', { name: backendLabel(backendChoice), reason });
499
698
  }
500
699
  }
700
+ // Resolve a surviving bare request on the backend that actually opened.
701
+ // This also covers a remembered backend whose startup fell back to DSH.
702
+ const bootSessionId = backendStart === undefined
703
+ ? effectiveSessionId ?? resumeTargetFromArgv(startupArgv)
704
+ : backendStart.resumedSessionId;
501
705
  // The backend session (and its child process) belongs to this fiber until the
502
706
  // channel adopts it: a boot that throws before then disposes the fiber's
503
707
  // effects, and this one stops the child instead of leaking it. Dispose is
@@ -515,11 +719,15 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
515
719
  const backendPermissions = backendStart === undefined ? undefined : new PermissionStore();
516
720
  if (backendPermissions !== undefined)
517
721
  ctx.effect(() => () => backendPermissions.settleAll());
518
- const { agent, handle, agentPreset, route: createdRoute } = backendStart !== undefined
722
+ const resolved = backendStart !== undefined || dshInEntry
519
723
  ? { agent: undefined, handle: undefined, agentPreset: undefined, route: undefined }
520
- : await resolveAgent(ctx, launchSessionId, configuredRoute, startupRoute, meta, config.preset);
724
+ : await resolveAgent(ctx, bootSessionId, configuredRoute, startupRoute, deploymentRoute, meta, config.preset);
725
+ let agent = resolved.agent;
726
+ const { handle, agentPreset, route: createdRoute } = resolved;
521
727
  // Workspace ownership is a DSH session-store fact (skipped off DSH).
522
- if (agent !== undefined)
728
+ // `ctx` here is the context that owns the session: this plugin's, or the
729
+ // dsh-tui row's for the in-process DSH kernel (`attachDsh`).
730
+ const attachWorkspaceOwnership = async (ctx, agent) => {
523
731
  try {
524
732
  // Opening a persisted TUI session is an explicit ownership action too.
525
733
  // Older TUI versions only wrote the Session log, so attaching on every
@@ -556,6 +764,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
556
764
  // loud instead of silently leaving the conversation Ungrouped.
557
765
  ctx.logger.warn(`dsh-tui: session "${agent.session.id}" workspace attachment failed: ${error instanceof Error ? error.message : String(error)}`);
558
766
  }
767
+ };
768
+ if (agent !== undefined)
769
+ await attachWorkspaceOwnership(ctx, agent);
559
770
  // Status-line route: the exact route the agent runs with — on create the
560
771
  // validated startup resolution, on resume the route the target session's
561
772
  // own records carry (a complete cordis.yml pin wins over them).
@@ -579,6 +790,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
579
790
  // empty and the channel falls back to the last-request sample — as does a
580
791
  // non-DSH session, whose id the DSH meter never projects.
581
792
  const contextOccupancyStore = createContextOccupancyStore(ctx);
793
+ // Read side of the Agent Teams `agentTeam` projection, for the same TDZ
794
+ // reason; empty unless the profile mounts the team service.
795
+ const teamRosterStore = createTeamRosterStore(ctx);
582
796
  // The channel holds a backend session; this DSH one owns the resolved
583
797
  // handle (disposed by the binding when a later adoption replaces it).
584
798
  let startupSession;
@@ -586,16 +800,36 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
586
800
  startupSession = backendStart.session;
587
801
  else if (agent !== undefined)
588
802
  startupSession = createDshSession(ctx, { agent, handle });
803
+ else if (dshInEntry)
804
+ startupSession = createStartingSession('dsh', sessionCwd);
589
805
  else
590
806
  throw new Error('dsh-tui: no startup session was opened');
807
+ // The in-process DSH kernel's route and Config pins from its dsh-tui row,
808
+ // set in `attachDsh` before adoption; the getters below serve them.
809
+ const rowFacts = {};
810
+ /**
811
+ * The in-process DSH kernel's `/new` while no DSH session was adopted (its
812
+ * startup open failed). Installed by `attachDsh`.
813
+ */
814
+ let openDshSession;
815
+ let compositionFailed = false;
816
+ /** How the in-process DSH kernel's composition ended: a failing startup open
817
+ * waits for it, so a failed composition (the root cause) is reported. */
818
+ let settleComposition = () => undefined;
819
+ const compositionSettled = new Promise(resolve => { settleComposition = resolve; });
591
820
  const rawChannel = createChannel(ctx, startupSession, {
592
821
  // The namespace this boot actually registered the settings section under
593
822
  // (the Config owner's Loader id; custom ids are supported). Chat and the
594
823
  // channel's own settings reads look the section up by it.
595
824
  settingsNs: tuiSettingsNs,
825
+ onTaskComplete: preview => {
826
+ if (!shadow)
827
+ taskNotifier.notify(t('task-complete-notification'), preview);
828
+ },
829
+ settingsService: tuiSettings,
596
830
  // The backend reports its model with its first turn (`system/init`);
597
831
  // until then the status line names the backend.
598
- model: backendStart !== undefined ? backendStart.label : displayRoute.model,
832
+ get model() { return rowFacts.route?.model ?? (backendStart !== undefined ? backendStart.label : displayRoute.model); },
599
833
  ...(backendStart === undefined || backendPermissions === undefined ? {} : {
600
834
  backendLabel: backendStart.label,
601
835
  openSession: backendStart.open,
@@ -605,6 +839,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
605
839
  sessionPrefs: backendStart.sessionPrefs,
606
840
  initialHistory: backendStart.initialHistory,
607
841
  resumeCommand: backendStart.resumeCommand,
842
+ ...(backendStartup === undefined ? {} : { startup: backendStartup }),
843
+ }),
844
+ // The in-process DSH kernel: a DSH placeholder until the row's session.
845
+ ...(dshStartup === undefined ? {} : {
846
+ backendLabel: backendLabel('dsh'),
847
+ startup: dshStartup,
848
+ openSession: async (target) => {
849
+ if (target.kind !== 'create' || openDshSession === undefined)
850
+ throw new Error(t('new-session-unavailable'));
851
+ return await openDshSession(target.cwd);
852
+ },
608
853
  }),
609
854
  // The activity projection only pushes on change; read the current value as
610
855
  // soon as this session binds so a resumed or reattached session renders its
@@ -619,6 +864,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
619
864
  // change, so a resumed session reads one baseline at bind time.
620
865
  contextPressure: contextOccupancyStore,
621
866
  seedContextOccupancy: session => contextOccupancyStore.seed(session),
867
+ team: teamRosterStore,
868
+ seedTeam: session => teamRosterStore.seed(session),
622
869
  // A RESUMED session keeps its persisted header cwd (issue #96 review):
623
870
  // pre-upgrade sessions recorded the launch directory, and re-resolving
624
871
  // from the current launch directory would split @ expansion / file
@@ -626,17 +873,19 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
626
873
  // sessions record sessionCwd at creation, so both agree there.
627
874
  // A resumed backend session runs where it was recorded.
628
875
  cwd: backendStart?.session.cwd ?? agent?.session.header.cwd ?? sessionCwd,
629
- provider: backendStart !== undefined ? backendStart.backendId : displayRoute.provider,
876
+ get provider() { return rowFacts.route?.provider ?? (backendStart !== undefined ? backendStart.backendId : displayRoute.provider); },
630
877
  // Raw cordis.yml route (undefined when unset): the channel's
631
878
  // new-session path re-resolves prefs against these, and resume passes
632
879
  // only explicit values so the target session's own record wins.
633
- configuredModel: config.model,
634
- configuredProvider: config.provider,
880
+ get configuredModel() { return rowFacts.config === undefined ? config.model : rowFacts.config.model; },
881
+ get configuredProvider() { return rowFacts.config === undefined ? config.provider : rowFacts.config.provider; },
635
882
  // Raw cordis.yml `lang` / `activityFrames`: /reload must not override a
636
883
  // static deployment choice with the persisted preference.
637
884
  configuredLang: config.lang,
638
885
  configuredActivityFrames: config.activityFrames,
639
- effort: config.effort,
886
+ // Read when the DSH extensions attach (the adoption), after the row's
887
+ // facts are in; the display fields below are fixed at the first frame.
888
+ get effort() { return rowFacts.config === undefined ? config.effort : rowFacts.config.effort; },
640
889
  activity: config.activity,
641
890
  // Explicit cordis.yml value (static deployment choice) wins over the
642
891
  // runtime `/activity` preference, which wins over the default.
@@ -645,14 +894,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
645
894
  contextBar: config.contextBar,
646
895
  // Same precedence for the agent preset: cordis.yml `preset` over the
647
896
  // persisted `/preset` choice; undefined adopts the roster default.
648
- configuredPreset: config.preset,
897
+ get configuredPreset() { return rowFacts.config === undefined ? config.preset : rowFacts.config.preset; },
649
898
  agentPreset,
650
899
  // Shift+Tab session-mode cycle (undefined → the built-in default/
651
900
  // plan/full cycle in sessionModes.ts).
652
- modes: config.modes,
901
+ get modes() { return rowFacts.config === undefined ? config.modes : rowFacts.config.modes; },
653
902
  // Edit/Write diff presentation (schema default 'auto'); the /settings
654
903
  // screen edits this key live through the dsh-tui namespace.
655
904
  diffLayout: config.diffLayout,
905
+ // Upstream auto-retry seeding (cordis.yml upstreamRetry, default on):
906
+ // only an explicit opt-out disables it.
907
+ get upstreamRetry() { return rowFacts.config === undefined ? config.upstreamRetry : rowFacts.config.upstreamRetry; },
656
908
  thinkingFold: config.thinkingFold,
657
909
  jobGroupFold: config.jobGroupFold,
658
910
  toolBackground: config.toolBackground,
@@ -674,6 +926,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
674
926
  // 的改动由 applyBrand 实时接上(branding.ts 负责解析)。
675
927
  brand: config.brand,
676
928
  });
929
+ backendStartupAdopted = backendStartup !== undefined;
677
930
  // Register the live Channel for the adapter Kernel. The Channel driver
678
931
  // resolves it lazily from the composition root, so this can be called after
679
932
  // the plugin-host Kernel started without requiring a re-mount.
@@ -682,9 +935,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
682
935
  // child activation context.
683
936
  const unregisterTuiChannel = registerTuiChannel(compositionRoot(ctx), rawChannel);
684
937
  ctx.effect(() => () => { unregisterTuiChannel(); });
685
- const pluginHost = ctx.get('tuiPluginHost');
686
938
  const adapterRuntime = adapterRuntimeFor(ctx);
687
- const uiMount = mountChannelUi(ctx, rawChannel, pluginHost, adapterRuntime.mode);
939
+ // Looked up per probe: the plugin-host row can compose after the mount.
940
+ const uiMount = mountChannelUi(ctx, rawChannel, ctx.get('tuiPluginHost'), adapterRuntime.mode, () => ctx.get('tuiPluginHost'));
688
941
  const channel = uiMount.channel;
689
942
  bindChannelCommands(rawChannel, channel);
690
943
  // last-run.json for the launcher's crash retry (see writeLastRunRecord):
@@ -692,6 +945,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
692
945
  // at that point. The kernel-switch branch skips the refresh because the
693
946
  // replacement writes its own record when it boots.
694
947
  const bootAttemptId = `${process.pid.toString(36)}-${Date.now().toString(36)}`;
948
+ /** None before DSH has composed (the in-process DSH kernel) or off DSH. */
949
+ const liveDshAgent = () => dshAgentOn(ctx, channel.agentId);
695
950
  const refreshLastRunRecord = () => {
696
951
  // An observational composition (replay/embedding) is not "the instance the
697
952
  // user ran last" — it must not overwrite the interactive record.
@@ -701,7 +956,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
701
956
  ? backendStart.persisted(channel.agentId, channel.rows)
702
957
  : isExitResumable({
703
958
  pendingCount: channel.pending.length,
704
- liveAgent: ctx.agents.get(SessionId(channel.agentId)),
959
+ liveAgent: liveDshAgent(),
705
960
  startupAgent: agent,
706
961
  });
707
962
  writeLastRunRecord({
@@ -725,6 +980,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
725
980
  // lands once the channel can actually show it.
726
981
  if (backendFallbackNotice !== undefined)
727
982
  notifyChannel(backendFallbackNotice, { color: 'warning' });
983
+ // The entry could not use the installed dsh and fell back (this process,
984
+ // or the `dsh --profile` it handed the launch to): say why, once.
985
+ const hostNotice = process.env[HOST_NOTICE_ENV];
986
+ delete process.env[HOST_NOTICE_ENV];
987
+ if (hostNotice !== undefined && hostNotice !== '') {
988
+ notifyChannel(t('host-dsh-unavailable', { reason: hostNotice }), { color: 'warning', timeoutMs: 10000 });
989
+ }
728
990
  const submitChannel = text => {
729
991
  if (!shadow)
730
992
  channel.submit(text);
@@ -744,7 +1006,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
744
1006
  applySidePanelSplitEnabled(config.sidePanel?.splitEnabled);
745
1007
  applySidePanelOpen(config.sidePanel?.open);
746
1008
  applySidePanelRatio(config.sidePanel?.ratio);
747
- applySidePanelPanels(config.sidePanel?.panels);
1009
+ // Registered ids survive: on the profile path a third-party row may have
1010
+ // registered its panel before this row applied (./panels.ts).
1011
+ applyConfiguredSidePanelPanels(config.sidePanel?.panels);
748
1012
  applyCompanionSkin(config.companion?.skin);
749
1013
  applyBtwContextTurns(config.btw?.contextTurns);
750
1014
  applyBtwContextBudget(config.btw?.contextBudget);
@@ -759,14 +1023,16 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
759
1023
  // Without the extensions row (tuiToast absent) plugin toasts are dropped
760
1024
  // by the runtime itself — same soft-degrade contract as the other seams.
761
1025
  // 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') {
1026
+ const bindToastSink = () => {
1027
+ getHostToastStore(ctx.get('tuiToast'))?.setSink(delivery => {
1028
+ notifyChannel(delivery.text, { color: delivery.color, timeoutMs: delivery.timeoutMs });
1029
+ });
1030
+ };
1031
+ bindToastSink();
1032
+ if (questionSeams?.registration.kind === 'waterfall') {
767
1033
  // Ownership follows the mutable channel; registration cleanup belongs to
768
1034
  // this Cordis fiber.
769
- questionAnswererRegistration.register(channel);
1035
+ questionSeams.registration.register(channel);
770
1036
  }
771
1037
  // Fullscreen layout decision: the settings user layer (edited through the
772
1038
  // /settings screen) overrides cordis.yml when set. The settings injection
@@ -777,6 +1043,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
777
1043
  // (swapping layouts requires re-mounting the whole tree).
778
1044
  let bootedFullscreen = config.fullscreen === true;
779
1045
  let bootedTerminalImages = lastBootedTerminalImages ?? config.terminalImages ?? true;
1046
+ // The launchpad boot decision (settings `launchpad`): same
1047
+ // restart-to-apply pattern as fullscreen — resolved before the tree mounts
1048
+ // from the settings user layer over cordis.yml; a mid-session edit is for
1049
+ // the next boot, the running session keeps what it booted with.
1050
+ let bootedLaunchpad = config.launchpad !== false;
780
1051
  let rendererSettingsFrozen = false;
781
1052
  const terminalImagesDisabledByEnv = isEnvTruthy(process.env.DSH_TUI_DISABLE_TERMINAL_IMAGES);
782
1053
  // The settings service may come up AFTER this plugin's apply: the cordis
@@ -791,10 +1062,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
791
1062
  resolveSettingsReady = () => resolve();
792
1063
  setTimeout(resolve, 300);
793
1064
  });
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.
1065
+ ((settingsCtx) => {
798
1066
  const scope = createSettingsScope(configOwner, settingsCtx.settings, tuiSettingsNs, Schema.object({
799
1067
  diffLayout: Schema.union(['auto', 'split', 'unified']).default('auto'),
800
1068
  thinkingFold: Schema.union(['preview', 'full']).default('preview'),
@@ -822,6 +1090,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
822
1090
  expandEditor: Schema.boolean(),
823
1091
  // Same no-default rule: applyDisplay resolves `?? config.smoothStreaming ?? true`.
824
1092
  smoothStreaming: Schema.boolean(),
1093
+ taskNotifications: Schema.union(['off', 'unfocused', 'always']),
1094
+ notificationMethod: Schema.union(['auto', 'osc9', 'bel']),
825
1095
  // Same no-default rule: applyDisplay resolves `?? config.mermaidDiagrams ?? true`.
826
1096
  mermaidDiagrams: Schema.boolean(),
827
1097
  // Code-frame shape; unset keeps the light rail frame.
@@ -913,6 +1183,10 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
913
1183
  // Same no-default rule: unset keeps cordis.yml's `fullscreen`
914
1184
  // decisive; set overrides it from the next boot on.
915
1185
  fullscreen: Schema.boolean(),
1186
+ // Same no-default rule as fullscreen: the launchpad boot gate reads
1187
+ // `value.launchpad ?? config.launchpad ?? true`, so cordis.yml stays
1188
+ // decisive while the user layer is unset.
1189
+ launchpad: Schema.boolean(),
916
1190
  // Unset inherits cordis.yml; a saved choice takes effect after restart.
917
1191
  terminalImages: Schema.boolean(),
918
1192
  // Built-in action-shortcut overrides, one optional combo string per
@@ -969,8 +1243,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
969
1243
  if (typeof value.fullscreen === 'boolean') {
970
1244
  bootedFullscreen = value.fullscreen;
971
1245
  }
1246
+ if (typeof value.launchpad === 'boolean') {
1247
+ bootedLaunchpad = value.launchpad;
1248
+ }
972
1249
  bootedTerminalImages = lastBootedTerminalImages ?? value.terminalImages ?? config.terminalImages ?? true;
973
1250
  };
1251
+ /** False only during the boot application below: a registered panel absent
1252
+ * from its CSV is not a user removal (./panels.ts). */
1253
+ let settingsEditsLive = true;
974
1254
  // The /settings language field writes `lang` through the settings
975
1255
  // service (user layer): apply it live and mirror it to lang.json so
976
1256
  // the /lang command and next-boot resolution agree. DSH_TUI_LANG
@@ -1002,6 +1282,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1002
1282
  channel.setPromptSessionLabel(value.promptSessionLabel ?? config.promptSessionLabel ?? false);
1003
1283
  channel.setExpandEditor(value.expandEditor ?? config.expandEditor ?? true);
1004
1284
  channel.setSmoothStreaming(value.smoothStreaming ?? config.smoothStreaming ?? true);
1285
+ taskNotifier.configure(value.taskNotifications ?? config.taskNotifications, value.notificationMethod ?? config.notificationMethod);
1005
1286
  applyMermaidDiagrams(value.mermaidDiagrams ?? config.mermaidDiagrams);
1006
1287
  applyCodeFrameStyle(value.codeFrameStyle ?? config.codeFrameStyle);
1007
1288
  applyMathRendering(resolveMathRendering(value, config));
@@ -1016,7 +1297,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1016
1297
  applySidePanelSplitEnabled(value.sidePanel?.splitEnabled ?? config.sidePanel?.splitEnabled);
1017
1298
  applySidePanelOpen(value.sidePanel?.open ?? config.sidePanel?.open);
1018
1299
  applySidePanelRatio(value.sidePanel?.ratio ?? config.sidePanel?.ratio);
1019
- applySidePanelPanels(value.sidePanel?.panels ?? config.sidePanel?.panels);
1300
+ // A registered panel missing from a live edit was unticked in /settings.
1301
+ const panelsCsv = value.sidePanel?.panels ?? config.sidePanel?.panels;
1302
+ if (settingsEditsLive)
1303
+ applySidePanelPanelsFromSettings(panelsCsv);
1304
+ else
1305
+ applyConfiguredSidePanelPanels(panelsCsv);
1020
1306
  applyCompanionSkin(value.companion?.skin ?? config.companion?.skin);
1021
1307
  applyBtwContextTurns(value.btw?.contextTurns ?? config.btw?.contextTurns);
1022
1308
  applyBtwContextBudget(value.btw?.contextBudget ?? config.btw?.contextBudget);
@@ -1090,8 +1376,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1090
1376
  notifyChannel(t('settings-fullscreen-migrated'), { color: 'warning' });
1091
1377
  }
1092
1378
  const { fullscreen: staleFullscreen, ...migratedSettings } = bootSettings;
1379
+ settingsEditsLive = false;
1093
1380
  apply(fullscreenMigration === 'unset' ? migratedSettings : bootSettings);
1381
+ settingsEditsLive = true;
1094
1382
  let lastTerminalImages = bootSettings.terminalImages ?? config.terminalImages ?? true;
1383
+ let lastLaunchpad = bootSettings.launchpad ?? config.launchpad ?? true;
1095
1384
  settingsCtx.effect(() => scope.watch(next => {
1096
1385
  apply(next);
1097
1386
  if (typeof next.fullscreen === 'boolean' && next.fullscreen !== bootedFullscreen) {
@@ -1102,9 +1391,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1102
1391
  channel.notify(t('settings-terminal-images-restart'), { color: 'warning' });
1103
1392
  }
1104
1393
  lastTerminalImages = terminalImages;
1394
+ const launchpad = next.launchpad ?? config.launchpad ?? true;
1395
+ if (launchpad !== lastLaunchpad && launchpad !== bootedLaunchpad) {
1396
+ notifyChannel(t('settings-launchpad-restart'), { color: 'warning' });
1397
+ }
1398
+ lastLaunchpad = launchpad;
1105
1399
  }));
1106
1400
  resolveSettingsReady?.();
1107
- });
1401
+ })({ settings: tuiSettings, effect: ctx.effect.bind(ctx) });
1108
1402
  // The /settings screen's own section: the dsh-tui namespace comes from
1109
1403
  // the settings registration above, and the declared selects write `lang`
1110
1404
  // and `diffLayout` back through the settings service's revision-fenced
@@ -1147,9 +1441,12 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1147
1441
  // left this registration silently skipped and /settings read-only.
1148
1442
  // channel.ts reads through the same fallback, so both sides meet in the
1149
1443
  // same registry either way.
1444
+ // The in-process DSH kernel registers into the local host first and moves
1445
+ // the section to the composition's service once it exists.
1446
+ let rehomeSettingsSection = () => undefined;
1150
1447
  {
1151
- const settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections')) ?? getLocalSettingsSectionsHost(ctx);
1152
- const unregister = settingsSections.register({
1448
+ let settingsSections = getHostSettingsSections(ctx.get('tuiSettingsSections')) ?? getLocalSettingsSectionsHost(ctx);
1449
+ const tuiSection = {
1153
1450
  ns: tuiSettingsNs,
1154
1451
  title: 'dsh-tui',
1155
1452
  groups: [...SETTING_GROUPS],
@@ -1258,6 +1555,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1258
1555
  return String(typeof value === 'boolean' ? value : config.mermaidDiagrams !== false);
1259
1556
  },
1260
1557
  },
1558
+ {
1559
+ ...settingField('taskNotifications'),
1560
+ format: value => String(value ?? config.taskNotifications ?? 'unfocused'),
1561
+ },
1562
+ {
1563
+ ...settingField('notificationMethod'),
1564
+ format: value => String(value ?? config.notificationMethod ?? 'auto'),
1565
+ },
1261
1566
  {
1262
1567
  ...settingField('codeFrameStyle'),
1263
1568
  },
@@ -1284,10 +1589,10 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1284
1589
  ...settingField('effortDefault'),
1285
1590
  format(value) {
1286
1591
  // Unset in settings.yaml: show what a boot would actually start
1287
- // on (the cordis effort pin → the persisted /effort choice)
1592
+ // on (the persisted /effort choice → the cordis effort default)
1288
1593
  // instead of a misleading blank.
1289
1594
  if (value === undefined || value === null || value === 'auto') {
1290
- return config.effort ?? readEffortPref() ?? 'auto';
1595
+ return readEffortPref() ?? config.effort ?? 'auto';
1291
1596
  }
1292
1597
  return String(value);
1293
1598
  },
@@ -1440,6 +1745,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1440
1745
  return { kind: 'set', value: budget };
1441
1746
  },
1442
1747
  },
1748
+ {
1749
+ ...settingField('launchpad'),
1750
+ format(value) {
1751
+ // Unset in settings.yaml: the effective default is on — same
1752
+ // rule as expandEditor's field.
1753
+ return String(typeof value === 'boolean' ? value : config.launchpad !== false);
1754
+ },
1755
+ },
1443
1756
  {
1444
1757
  ...settingField('companion.skin'),
1445
1758
  },
@@ -1471,8 +1784,17 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1471
1784
  ...settingField('minimal'),
1472
1785
  },
1473
1786
  ],
1474
- });
1475
- ctx.effect(() => unregister);
1787
+ };
1788
+ let unregister = settingsSections.register(tuiSection);
1789
+ ctx.effect(() => () => { unregister(); });
1790
+ rehomeSettingsSection = () => {
1791
+ const service = getHostSettingsSections(ctx.get('tuiSettingsSections'));
1792
+ if (service === undefined || service === settingsSections)
1793
+ return;
1794
+ unregister();
1795
+ settingsSections = service;
1796
+ unregister = service.register(tuiSection);
1797
+ };
1476
1798
  }
1477
1799
  // DSH approval seam: the permission layer asks ApprovalService.request(),
1478
1800
  // which dispatches an `approval/request` waterfall. With no answerer the
@@ -1486,19 +1808,24 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1486
1808
  bindApprovalStore(ctx, approvalStore);
1487
1809
  // A non-DSH backend answers its own permission prompts: the DSH answerer
1488
1810
  // 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
- }
1811
+ // The in-process DSH kernel installs it in `attachDsh`.
1812
+ const mountDshApprovals = (dshCtx) => {
1813
+ if (dshCtx.get('approval') !== undefined && backendStart === undefined) {
1814
+ dshCtx.on('approval/request', (req, next) => approvalStore.park(req).catch(() => next()));
1815
+ // Badge-flip push (P-4): React does not know the session log appended —
1816
+ // a source-badge verdict that only flips inside getSnapshot() surfaces
1817
+ // solely when something else re-renders. Feed the session firehose to
1818
+ // the store: it reacts only to tool/result (the sole verdict-flipping
1819
+ // event type), and its internal log-length memo skips appends from any
1820
+ // session other than the active ask's, so no agent filtering is needed
1821
+ // here. The firehose fires post-commit, after the event entered
1822
+ // session.events, so the recheck sees the settled result.
1823
+ dshCtx.on('session/event', (session, event) => approvalStore.noteSessionEvent(session.id, event));
1824
+ dshCtx.effect(() => () => approvalStore.settleAll('cancelled'));
1825
+ }
1826
+ };
1827
+ if (!dshInEntry)
1828
+ mountDshApprovals(ctx);
1502
1829
  // The agent view reads parked ask ids for its "needs input" state.
1503
1830
  rawChannel.bindApprovalStore(approvalStore);
1504
1831
  // The panel source Chat renders: the backend's own prompts off DSH.
@@ -1518,9 +1845,25 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1518
1845
  // Submit once the channel exists; delivery goes through the normal pending/inbox
1519
1846
  // chain, so no special timing is needed. The parser separates startup flags
1520
1847
  // from literal prompt text.
1521
- const initialPrompt = initialPromptFromCmdlineArgs(cmdlineArgs);
1522
- if (initialPrompt)
1523
- submitChannel(initialPrompt);
1848
+ // The standalone entry has no `ctx.cmdlineArgs` (its argv is the app's); a
1849
+ // replacement keeps the arguments but the first process sent their prompt.
1850
+ const initialPrompt = launchPromptSent ? '' : initialPromptFromCmdlineArgs(cmdlineArgs ?? process.argv.slice(2));
1851
+ if (initialPrompt) {
1852
+ // A placeholder session refuses sends and nothing replays them after
1853
+ // adoption: hold the prompt until the channel is ready.
1854
+ if (channel.ready) {
1855
+ submitChannel(initialPrompt);
1856
+ }
1857
+ else {
1858
+ const unsubscribe = channel.subscribe(() => {
1859
+ if (!channel.ready)
1860
+ return;
1861
+ unsubscribe();
1862
+ submitChannel(initialPrompt);
1863
+ });
1864
+ ctx.effect(() => unsubscribe, 'dsh-tui initial prompt after startup');
1865
+ }
1866
+ }
1524
1867
  // Attach the stderr reporter to the live channel and flush anything a
1525
1868
  // startup-spawned server produced while the channel didn't exist yet.
1526
1869
  notifyStderr = (text, options) => notifyChannel(text, options);
@@ -1554,10 +1897,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1554
1897
  // accepted; the exit funnel then respawns onto that kernel (a NEW session —
1555
1898
  // no resume markers at all).
1556
1899
  let backendSwitchRequested;
1900
+ // A signal or `ctx.appExit` via the entry's exit seam: the clean-exit branch
1901
+ // runs as for `/quit`, then ends the process by the signal / with the code.
1902
+ let processExitRequest;
1557
1903
  // The profile this process was booted with (`dsh --profile <name>`); dsh
1558
1904
  // exposes it nowhere else, and /update must update the installation the
1559
1905
  // user is actually running, not a hard-coded one.
1560
- const profile = resolveDshProfileName();
1906
+ const profile = resolveDshProfileName() ?? runtimeOptions.profile;
1561
1907
  // Single exit funnel: `/exit` and double Ctrl+C land here, and so does
1562
1908
  // the unmount triggered by a cordis context teardown — but the two must
1563
1909
  // not share a fate (issue #12). Teardown only unmounts the UI; user exit
@@ -1565,7 +1911,11 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1565
1911
  // update handoff or resume hint).
1566
1912
  /** The session id for a restart or update handoff, or empty when the backend
1567
1913
  * has not persisted it and the replacement must start fresh. */
1568
- const handoffSessionId = () => backendStart === undefined || backendStart.persisted(channel.agentId, channel.rows) ? channel.agentId : '';
1914
+ const handoffSessionId = () => {
1915
+ if (backendStart !== undefined)
1916
+ return backendStart.persisted(channel.agentId, channel.rows) ? channel.agentId : '';
1917
+ return isUnstoredFreshSession(liveDshAgent()?.session) ? '' : channel.agentId;
1918
+ };
1569
1919
  const handoffHint = backendStart === undefined ? undefined : (sessionId) => backendStart.resumeCommand(sessionId);
1570
1920
  const funnel = createExitFunnel({
1571
1921
  onUserExit: error => {
@@ -1589,18 +1939,14 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1589
1939
  // never CLEARS a marker, so a session the user still has cannot be
1590
1940
  // dropped by a failure that happened before the first message landed.
1591
1941
  writeResumeMarkers: () => {
1592
- if (isExitResumable({
1593
- pendingCount: channel.pending.length,
1594
- liveAgent: ctx.agents.get(SessionId(channel.agentId)),
1942
+ writeCrashResumeMarkers({
1943
+ ctx,
1944
+ backendStart,
1945
+ channel,
1595
1946
  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();
1947
+ writeResumeTarget,
1948
+ refreshLastRunRecord,
1949
+ });
1604
1950
  },
1605
1951
  finish: crashLine => {
1606
1952
  void finishExit(ctx, instance, bootedFullscreen, undefined, crashLine, () => disposeRootAndExit(ctx, 1));
@@ -1611,8 +1957,13 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1611
1957
  if (updateRequested) {
1612
1958
  try {
1613
1959
  // Non-DSH sessions keep their marker in backend prefs, not DSH's `resume.txt`.
1614
- if (backendStart === undefined)
1615
- writeResumeTarget(channel.agentId);
1960
+ if (backendStart === undefined) {
1961
+ const sessionId = handoffSessionId();
1962
+ if (sessionId === '')
1963
+ clearResumeTarget();
1964
+ else
1965
+ writeResumeTarget(sessionId);
1966
+ }
1616
1967
  else if (backendStart.persisted(channel.agentId, channel.rows))
1617
1968
  backendStart.sessionPrefs.setLastSession(channel.agentId);
1618
1969
  }
@@ -1623,7 +1974,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1623
1974
  const hintText = isStandaloneRuntime()
1624
1975
  ? t('update-standalone-starting')
1625
1976
  : t('update-starting');
1626
- void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, handoffSessionId(), updateTargetVersion, backendChoice, handoffHint));
1977
+ void finishExit(ctx, instance, bootedFullscreen, hintText, undefined, () => runUpdate(ctx, profile, handoffSessionId(), updateTargetVersion, backendChoice, handoffHint, exitSeam));
1627
1978
  return;
1628
1979
  }
1629
1980
  // The kernel selector's switch: the same respawn machinery, no resume.
@@ -1633,30 +1984,41 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1633
1984
  // inherited DSH_TUI_RESUME_SESSION marker from the replacement env.
1634
1985
  // kernel.json was already written when the choice was accepted.
1635
1986
  if (backendSwitchRequested !== undefined) {
1636
- logRestartEvent('funnel: backend-switch branch entered', { backend: backendSwitchRequested });
1987
+ // Captured: the closure below runs after this block, where the outer
1988
+ // `let` is no longer narrowed.
1989
+ const switchingTo = backendSwitchRequested;
1990
+ logRestartEvent('funnel: backend-switch branch entered', { backend: switchingTo });
1637
1991
  // Fullscreen keeps the alternate screen and writes the "switching"
1638
1992
  // notice into it until the replacement takes over (see handoffAck.ts);
1639
1993
  // inline restores the main screen and writes the notice there.
1640
- logRestartEvent(handoffEventTag('starting'), { backend: backendSwitchRequested });
1994
+ logRestartEvent(handoffEventTag('starting'), { backend: switchingTo });
1641
1995
  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,
1996
+ void finishExit(ctx, instance, bootedFullscreen, formatHandoffNotice('starting', { name: backendLabel(switchingTo), color: process.stdout.isTTY === true }), undefined, () => runRestart(ctx, profile, '', undefined, {
1997
+ backend: switchingTo,
1998
+ // The replacement's parent prints the same name this process just
1999
+ // put on screen (update.ts has no registry to look it up in).
2000
+ backendName: backendLabel(switchingTo),
1644
2001
  ...(keepAlt ? { handoffScreen: 'alt' } : {}),
1645
- }), { keepAltScreen: keepAlt });
2002
+ }, exitSeam), { keepAltScreen: keepAlt });
1646
2003
  return;
1647
2004
  }
1648
2005
  // `/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).
2006
+ // A fresh initialization-only session has no log to reopen. Restart
2007
+ // it fresh; saved sessions keep their identity across the handoff.
1651
2008
  if (restartRequested) {
1652
2009
  beginRestartAttempt(channel.agentId);
1653
2010
  logRestartEvent('funnel: /restart branch entered');
1654
2011
  try {
1655
- if (backendStart === undefined)
1656
- writeResumeTarget(channel.agentId);
2012
+ if (backendStart === undefined) {
2013
+ const sessionId = handoffSessionId();
2014
+ if (sessionId === '')
2015
+ clearResumeTarget();
2016
+ else
2017
+ writeResumeTarget(sessionId);
2018
+ }
1657
2019
  else if (backendStart.persisted(channel.agentId, channel.rows))
1658
2020
  backendStart.sessionPrefs.setLastSession(channel.agentId);
1659
- logRestartEvent('funnel: resume target written');
2021
+ logRestartEvent('funnel: resume target updated');
1660
2022
  }
1661
2023
  catch (error) {
1662
2024
  // Resume persistence is best effort and must never block a restart.
@@ -1665,7 +2027,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1665
2027
  });
1666
2028
  }
1667
2029
  refreshLastRunRecord();
1668
- void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, handoffSessionId(), handoffHint, { kernel: backendChoice }));
2030
+ void finishExit(ctx, instance, bootedFullscreen, t('restart-starting'), undefined, () => runRestart(ctx, profile, handoffSessionId(), handoffHint, { kernel: backendChoice }, exitSeam));
1669
2031
  return;
1670
2032
  }
1671
2033
  // Judge against the live session behind the channel (channel.agentId),
@@ -1689,7 +2051,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1689
2051
  else {
1690
2052
  const resumable = isExitResumable({
1691
2053
  pendingCount: channel.pending.length,
1692
- liveAgent: ctx.agents.get(SessionId(channel.agentId)),
2054
+ liveAgent: liveDshAgent(),
1693
2055
  startupAgent: agent,
1694
2056
  });
1695
2057
  try {
@@ -1707,10 +2069,39 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1707
2069
  }
1708
2070
  // Same resumability as the markers above.
1709
2071
  refreshLastRunRecord();
1710
- void finishExit(ctx, instance, bootedFullscreen, hint, undefined, () => disposeRootAndExit(ctx, 0));
2072
+ const request = processExitRequest;
2073
+ void finishExit(ctx, instance, bootedFullscreen, hint, undefined, () => {
2074
+ if (request === undefined)
2075
+ disposeRootAndExit(ctx, 0);
2076
+ else if (request.kind === 'code')
2077
+ disposeRootAndExit(ctx, request.code);
2078
+ // A signal: the process dies by it once the root is down (bounded
2079
+ // as any dispose; the stalled case dies by it too).
2080
+ else
2081
+ disposeRootAndThen(ctx, () => { dieBySignal(request.signal); }, () => { dieBySignal(request.signal); });
2082
+ });
1711
2083
  },
1712
2084
  });
1713
2085
  const handleExit = funnel.handleExit;
2086
+ const exitSeam = runtimeOptions.exitSeam;
2087
+ const requestProcessExit = (request) => {
2088
+ // A handoff is under way: no backstop, which could kill the supervisor of
2089
+ // a replacement already up; superviseReplacement takes the seam over.
2090
+ if (updateRequested || restartRequested || backendSwitchRequested !== undefined)
2091
+ return 'supervising';
2092
+ // A `/quit` or a crash is already ending this process.
2093
+ if (exited)
2094
+ return 'pending';
2095
+ processExitRequest = request;
2096
+ logRestartEvent('funnel: process exit requested', request.kind === 'signal' ? { signal: request.signal } : { code: request.code });
2097
+ if (handleExit())
2098
+ return 'exiting';
2099
+ // The tree is being torn down: the funnel cannot own the process.
2100
+ processExitRequest = undefined;
2101
+ return 'refused';
2102
+ };
2103
+ if (exitSeam !== undefined)
2104
+ exitSeam.request = requestProcessExit;
1714
2105
  /** The launchpad kernel selector's accept path (the Chat side passes the
1715
2106
  * chosen kernel through onSwitchBackend — the prop wiring lands together
1716
2107
  * with the selector's Chat rendering). Persists the choice BEFORE the
@@ -1724,7 +2115,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1724
2115
  backendSwitchRequested = backend;
1725
2116
  writeKernelPrefs({ backend });
1726
2117
  logRestartEvent('command: backend switch accepted', { backend });
1727
- notifyChannel(t('kernel-switch-restarting', { name: kernelDisplayName(backend) }));
2118
+ notifyChannel(t('kernel-switch-restarting', { name: backendLabel(backend) }));
1728
2119
  handleExit();
1729
2120
  };
1730
2121
  /** /channel changed the active channel's connection. The running CLI child
@@ -1818,18 +2209,21 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1818
2209
  * and opens straight into its conversation.
1819
2210
  */
1820
2211
  const dshBoot = backendStart === undefined;
1821
- const noResume = isLandingLaunch({ launchSessionId, initialPrompt });
2212
+ const noResume = isLandingLaunch({ launchSessionId: bootSessionId, initialPrompt });
1822
2213
  const openHomeOnBoot = dshBoot && !homeSeen && noResume && requestedWorkspace === undefined;
1823
2214
  /**
1824
2215
  * The launchpad is NOT one-shot the way the workspace home is: every
1825
2216
  * ordinary launch starts on it, because it is where the first sentence gets
1826
2217
  * typed rather than a tutorial that retires itself — on every backend: a
1827
2218
  * 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).
2219
+ * gate below is deliberately absent). The /settings `launchpad` toggle
2220
+ * (settings user layer over cordis.yml, resolved into `bootedLaunchpad`
2221
+ * above) is the persistent off switch — off boots straight into the chat
2222
+ * page. `DSH_TUI_NO_LAUNCHPAD=1` remains the env escape hatch (an
2223
+ * automation that wants the old blank conversation and no dialog in front
2224
+ * of it).
1831
2225
  */
1832
- const launchpadOnBoot = noResume && process.env.DSH_TUI_NO_LAUNCHPAD !== '1';
2226
+ const launchpadOnBoot = noResume && bootedLaunchpad && process.env.DSH_TUI_NO_LAUNCHPAD !== '1';
1833
2227
  /**
1834
2228
  * The first-run guide. Gated on its own preference (not on `homeSeen`): the
1835
2229
  * two answer different questions, and an install that already knows its
@@ -1839,7 +2233,8 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1839
2233
  // 品牌镜像初值(branding.ts):首帧渲染前铺好,避免 Claude 后端先画一屏
1840
2234
  // 蓝再变橙。Chat 里的 effect 会在品牌解析变化时跟进更新这个镜像。
1841
2235
  setActiveBrand(resolveBrand(config.brand, backendChoice));
1842
- const chat = React.createElement(Chat, {
2236
+ const liveThemeHost = () => getHostThemes(ctx.get('tuiThemes'));
2237
+ const buildChat = () => React.createElement(Chat, {
1843
2238
  channel,
1844
2239
  renderScene: createChannelSceneOutlet(() => rawChannel.pluginScene),
1845
2240
  questionStore,
@@ -1859,7 +2254,7 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1859
2254
  // no longer runs a second activity tracker of its own.
1860
2255
  activityStore,
1861
2256
  extensionShortcuts: getHostShortcuts(ctx.get('tuiShortcuts')),
1862
- themeHost,
2257
+ themeHost: liveThemeHost(),
1863
2258
  // Full-screen surfaces inside Chat — the trajectory scene and the session
1864
2259
  // browser — enter the alt screen themselves in inline mode; in fullscreen
1865
2260
  // the tree is already wrapped below, so they must not nest.
@@ -1880,12 +2275,39 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1880
2275
  onSwitchBackend: switchBackend,
1881
2276
  // /channel: restart with a new session after the connection changed.
1882
2277
  onRestartFreshSession: restartFreshSession,
2278
+ // The backend registry as the picker sees it: the UI takes data, not the
2279
+ // registry (P0). dsh first, then the manifest order — picker order. Entries
2280
+ // that registered without an admission are not offered (B-2).
2281
+ kernelEntries: kernelEntriesOf(listOfferedBackends()),
1883
2282
  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,
2283
+ // The kernel picker's SDK install wizard (a dim row's Enter): the surface of
2284
+ // whichever row the user picked, looked up by id — the manifest says what to
2285
+ // install and with which executor, this host holds the actions.
2286
+ onResolveSdkInstall: installSurfaceFor,
2287
+ // `/plugin install <spec>` (C-4): the write path. The wiring lives beside
2288
+ // the flow (`createPluginInstallEntry`) so a regression can drive the real
2289
+ // feed load, declaration read and profile-relative path resolution with a
2290
+ // fixture context. `undefined` ⇒ not an install form, or no service to
2291
+ // install through; the screen then runs the read-only report instead.
2292
+ onPluginInstall: createPluginInstallEntry(ctx, {
2293
+ ownVersion: installedTuiVersion(),
2294
+ profileName: profile ?? resolveDshProfileName(),
2295
+ }),
2296
+ // `/plugin remove|enable|disable` (C-5): the lifecycle write path. Wired the
2297
+ // same way and for the same reason — the entry is where the real profile, the
2298
+ // declaration read and the data directory meet, so a regression can drive
2299
+ // them with a fixture context. `undefined` ⇒ not one of those forms, or no
2300
+ // service to act through; the screen then runs the read-only report.
2301
+ onPluginManage: createPluginManageEntry(ctx, {
2302
+ profileName: profile ?? resolveDshProfileName(),
2303
+ }),
2304
+ // `/team [status] | on | off`: the Agent Teams bundle through the same
2305
+ // lifecycle flow (team-mode.ts).
2306
+ onTeamCommand: createTeamCommandEntry(ctx, {
2307
+ profileName: profile ?? resolveDshProfileName(),
2308
+ // The host reports `unknown` when it has no version: read the installed one.
2309
+ kernelVersion: entrySlot?.kernelVersion === 'unknown' ? undefined : entrySlot?.kernelVersion,
2310
+ }),
1889
2311
  kernelPinned: backendPinned,
1890
2312
  // Only a `dsh --profile <name>` launch has a profile installation for
1891
2313
  // `/update` to act on; source checkouts and `--config` overlays get the
@@ -1957,17 +2379,32 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1957
2379
  // the screen border). It must sit INSIDE AlternateScreen: the alt-screen
1958
2380
  // box sizes itself to the real terminal rows, while PageMargin reports
1959
2381
  // 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
- });
2382
+ const buildTree = () => {
2383
+ const chat = buildChat();
2384
+ const marginChildren = bootedFullscreen
2385
+ ? React.createElement(AlternateScreen, null, React.createElement(PageMargin, null, chat))
2386
+ : React.createElement(PageMargin, null, chat);
2387
+ return React.createElement(ThemeProvider, {
2388
+ themeHost: liveThemeHost(),
2389
+ children: marginChildren,
2390
+ });
2391
+ };
2392
+ const tree = buildTree();
1967
2393
  // Kernel-switch replacement: send the ready ACK once the first frame after
1968
2394
  // adoption is flushed. Does nothing on an ordinary boot.
1969
2395
  armFirstFrameAck(process.stdout);
1970
- instance = await render(tree, { exitOnCtrlC: false, terminalImages: bootedTerminalImages });
2396
+ markBoot('render-start');
2397
+ // The entry's composition right after the mount freezes the event loop for
2398
+ // a while: it waits for the first frame to reach the terminal.
2399
+ const awaitsFirstFrame = entrySlot !== undefined;
2400
+ let frameFlushed = () => undefined;
2401
+ const firstFrame = awaitsFirstFrame ? new Promise(resolve => { frameFlushed = resolve; }) : undefined;
2402
+ instance = await render(tree, {
2403
+ exitOnCtrlC: false,
2404
+ terminalImages: bootedTerminalImages,
2405
+ ...(awaitsFirstFrame ? { onFrame: () => { frameFlushed(); } } : {}),
2406
+ });
2407
+ markBoot('render-done');
1971
2408
  const isRecompose = lastBootedFullscreen !== undefined;
1972
2409
  lastBootedFullscreen = bootedFullscreen;
1973
2410
  lastBootedTerminalImages = bootedTerminalImages;
@@ -1981,14 +2418,32 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
1981
2418
  // External injection channel (dsh.nvim etc.): expose a per-session local
1982
2419
  // socket that appends text into the prompt input and submits it. Optional
1983
2420
  // 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
- }
2421
+ // session. It follows the bound session: a placeholder still opening (empty
2422
+ // id) binds nothing, and adoption or a session switch rebinds under the new
2423
+ // id and cwd, closing the previous socket and discovery record (a connected
2424
+ // client reconnects through servers.json). Closed on teardown so neither leaks.
2425
+ let injectChannel = null;
2426
+ let injectSessionId = '';
2427
+ const bindInjectChannel = () => {
2428
+ if (channel.agentId === injectSessionId)
2429
+ return;
2430
+ injectChannel?.close();
2431
+ injectChannel = null;
2432
+ injectSessionId = channel.agentId;
2433
+ if (injectSessionId === '')
2434
+ return;
2435
+ injectChannel = openInjectChannel(injectSessionId, channel.cwd, {
2436
+ append: (text) => injectControllerRef.current?.append(text),
2437
+ submit: () => injectControllerRef.current?.submit(),
2438
+ }, (message) => ctx.logger.warn(`dsh-tui: ${message}`));
2439
+ };
2440
+ bindInjectChannel();
2441
+ const unsubscribeInject = channel.subscribe(bindInjectChannel);
2442
+ ctx.effect(() => () => {
2443
+ unsubscribeInject();
2444
+ injectChannel?.close();
2445
+ injectChannel = null;
2446
+ }, 'dsh-tui inject channel');
1992
2447
  // Cross-process session mounting: publish the sessions this process has
1993
2448
  // mounted so another TUI (a different terminal process on the same machine)
1994
2449
  // can see them as occupied and refuse to mount the same log. Two processes
@@ -2034,9 +2489,138 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
2034
2489
  // the process, so a later fatal error falls back to Node's default crash
2035
2490
  // instead of reaching a disposed ctx.
2036
2491
  registerProcessGuardFatalSink(undefined);
2492
+ if (exitSeam?.request === requestProcessExit)
2493
+ exitSeam.request = undefined;
2037
2494
  rawChannel.releaseContributions();
2038
2495
  instance?.unmount();
2039
2496
  });
2497
+ /** The first frame's write, then stdout drained (bounded: a renderer that
2498
+ * never draws must not hold the composition back). */
2499
+ const flushFirstFrame = async () => {
2500
+ await Promise.race([firstFrame ?? Promise.resolve(), new Promise(resolve => { setTimeout(resolve, 1000).unref(); })]);
2501
+ await new Promise(resolve => { process.stdout.write('', () => { resolve(); }); });
2502
+ };
2503
+ /**
2504
+ * Services composed after the mount: re-read what the screen took as values
2505
+ * at mount (Chat's stores, theme host, toast sink, /settings section).
2506
+ */
2507
+ const refreshHostServices = () => {
2508
+ rehomeSettingsSection();
2509
+ bindToastSink();
2510
+ if (!exited)
2511
+ instance?.rerender(buildTree());
2512
+ };
2513
+ if (entrySlot !== undefined)
2514
+ entrySlot.firstFrameFlushed = flushFirstFrame;
2515
+ // The kernels without a dsh-tui row: no row to refresh the mounted screen,
2516
+ // so the slot does it.
2517
+ if (entrySlot !== undefined && !entrySlot.dsh)
2518
+ entrySlot.composeSucceeded = () => { refreshHostServices(); };
2519
+ if (entrySlot?.dsh === true) {
2520
+ entrySlot.composeSucceeded = () => { settleComposition(true); };
2521
+ entrySlot.composeFailed = (error, logPath) => {
2522
+ settleComposition(false);
2523
+ const reason = error instanceof Error ? error.message : String(error);
2524
+ logForDebugging(`dsh-tui: host composition failed (${reason})${logPath === undefined ? '' : ` — startup report: ${logPath}`}`);
2525
+ compositionFailed = true;
2526
+ openDshSession = undefined;
2527
+ // No DSH to open on: the failure row names the report and the ways out.
2528
+ const hint = logPath === undefined ? t('startup-compose-failed-hint') : t('startup-compose-failed-hint-log', { path: logPath });
2529
+ if (!dshStartupSettled) {
2530
+ settleDshStartup?.reject(new StartupOpenError(reason.split('\n')[0] ?? reason, hint, { cause: error }));
2531
+ return;
2532
+ }
2533
+ if (exited)
2534
+ return;
2535
+ // The session may already be adopted (the audit runs after the tree
2536
+ // settled): a row that stays, not a toast.
2537
+ try {
2538
+ channel.pushLocal(t('startup-open-failed', { backend: backendLabel('dsh'), err: reason.split('\n')[0] ?? reason }), [hint]);
2539
+ }
2540
+ catch {
2541
+ }
2542
+ };
2543
+ entrySlot.attachDsh = async (rowCtx, rowRuntimeConfig) => {
2544
+ const dshCtx = rowCtx;
2545
+ // The composition failed (its row is on screen), or the process is
2546
+ // leaving (the root dispose waited for the Loader, which started this row).
2547
+ if (compositionFailed || exited)
2548
+ return;
2549
+ try {
2550
+ const rowConfig = configValues(rowRuntimeConfig);
2551
+ rowFacts.config = rowConfig;
2552
+ await installPresets(dshCtx);
2553
+ const seams = mountDshQuestionSeams(dshCtx);
2554
+ if (seams.notice !== undefined)
2555
+ notifyChannel(seams.notice, { color: 'error' });
2556
+ if (seams.registration.kind === 'waterfall')
2557
+ seams.registration.register(channel);
2558
+ mountDshApprovals(dshCtx);
2559
+ refreshHostServices();
2560
+ const rowRoute = { provider: rowConfig.provider, model: rowConfig.model };
2561
+ // The same startup/deployment split the plugin's own boot makes (issue #67).
2562
+ const { startupRoute: rowStartupRoute, deploymentRoute: rowDeploymentRoute } = startupRoutes(dshCtx, rowRoute);
2563
+ const openOnRow = (sessionId, openMeta) => resolveAgent(dshCtx, sessionId, rowRoute, rowStartupRoute, rowDeploymentRoute, openMeta, rowConfig.preset);
2564
+ /** The opened agent is this screen's from now on; returns its route. */
2565
+ const takeOpened = async (opened) => {
2566
+ agent = opened.agent;
2567
+ await attachWorkspaceOwnership(dshCtx, opened.agent);
2568
+ rowFacts.route = opened.route ?? rowStartupRoute;
2569
+ return rowFacts.route;
2570
+ };
2571
+ // `/new` after a failed startup open: DSH's create path on this row.
2572
+ openDshSession = async (cwd) => {
2573
+ const created = await openOnRow(undefined, { cwd });
2574
+ await takeOpened(created);
2575
+ refreshLastRunRecord();
2576
+ return createDshSession(dshCtx, { agent: created.agent, handle: created.handle });
2577
+ };
2578
+ // The entry mounted on rebuilt defaults: the row's own `workspace` /
2579
+ // `cwd` decide the session directory, with `apply`'s precedence. A
2580
+ // provider-URI target resolves now that the providers exist.
2581
+ const rowWorkspace = rowConfig.workspace ?? requestedWorkspace;
2582
+ let startupMeta = { cwd: resolveSessionCwd(rowConfig.cwd) };
2583
+ if (rowWorkspace !== undefined) {
2584
+ if (initialWorkspace !== undefined && rowWorkspace === requestedWorkspace) {
2585
+ startupMeta = meta;
2586
+ }
2587
+ else {
2588
+ const workspaces = getHostWorkspaceRuntime(dshCtx.get('tuiWorkspaces'));
2589
+ const target = workspaces === undefined ? undefined : await workspaces.resolve(rowWorkspace);
2590
+ if (target === undefined)
2591
+ throw new Error(unsupportedWorkspaceTarget(rowWorkspace));
2592
+ startupMeta = { cwd: target.cwd };
2593
+ }
2594
+ }
2595
+ // A revoked target (the safe-mode retry's warning path) cold-starts here too.
2596
+ const rowSessionId = resumeTarget.kind === 'revoked'
2597
+ ? undefined
2598
+ : rowConfig.sessionId ?? resumeTargetFromArgv(cmdlineArgsOf(dshCtx) ?? process.argv.slice(2));
2599
+ const opened = await openOnRow(rowSessionId, startupMeta);
2600
+ const session = createDshSession(dshCtx, { agent: opened.agent, handle: opened.handle });
2601
+ // The composition failed while this opened: close the unadoptable session.
2602
+ if (compositionFailed) {
2603
+ await session.dispose().catch(() => undefined);
2604
+ return;
2605
+ }
2606
+ const route = await takeOpened(opened);
2607
+ settleDshStartup?.resolve({
2608
+ session,
2609
+ history: [],
2610
+ route,
2611
+ ...(opened.agentPreset === undefined ? {} : { agentPreset: opened.agentPreset }),
2612
+ });
2613
+ refreshLastRunRecord();
2614
+ }
2615
+ catch (error) {
2616
+ logForDebugging(`dsh-tui: DSH session failed to open (${error instanceof Error ? error.message : String(error)})`);
2617
+ // A composition that fails reports itself (its row names the report).
2618
+ if (!await compositionSettled)
2619
+ return;
2620
+ settleDshStartup?.reject(error);
2621
+ }
2622
+ };
2623
+ }
2040
2624
  // The TUI is the front door: when the user unmounts it (Ctrl+C), dispose
2041
2625
  // the app tree and exit the process. The rejection handler covers
2042
2626
  // error-driven unmounts — without it a rejected exitPromise became an
@@ -2044,6 +2628,9 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
2044
2628
  // is swallowed by the funnel (issue #12).
2045
2629
  void instance.waitUntilExit().then(handleExit, handleExit);
2046
2630
  }
2631
+ function unsupportedWorkspaceTarget(target) {
2632
+ return `dsh-tui: unsupported or unavailable workspace target: ${target}`;
2633
+ }
2047
2634
  /**
2048
2635
  * Attach to an existing agent, resume a persisted session (`dsh-tui --resume`
2049
2636
  * feeds the id through `config.sessionId`), or create a fresh one. Resume
@@ -2053,26 +2640,45 @@ export async function apply(ctx, runtimeConfig, configOwner = ctx) {
2053
2640
  * without a session id.
2054
2641
  *
2055
2642
  * 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.
2643
+ * (`DSH_TUI_PRESET` over the persisted `/preset` choice over the cordis.yml
2644
+ * default over the roster default) and mounts it in the factory's setup hook;
2645
+ * a resume re-mounts the preset the session's own log records. Without the
2646
+ * roster both paths behave as before presets existed.
2060
2647
  *
2061
2648
  * Model route (issues #14/#30/#67): a create adopts the caller's atomically
2062
2649
  * 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.
2650
+ * prefers the route the target session's own records carry and falls back to
2651
+ * a COMPLETE cordis.yml route — a provider-only pin must not half-override
2652
+ * the session's route, and the static route is only the deployment default.
2653
+ */
2654
+ /**
2655
+ * The startup route and the deployment route a rejected preference falls back
2656
+ * to (issue #67): the persisted `/model` pick wins whole, else a complete
2657
+ * cordis.yml route, else Harness's agent-default-model selection — never the
2658
+ * lock the user's own pick replaced, never a half-pinned config.
2065
2659
  */
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
2660
+ function startupRoutes(ctx, configuredRoute) {
2661
+ const selected = ctx.get('agentDefaultModel')?.currentSelection?.();
2662
+ const harnessDefault = typeof selected?.provider === 'string' && selected.provider.length > 0
2663
+ && typeof selected.model === 'string' && selected.model.length > 0
2664
+ ? { provider: selected.provider, model: selected.model }
2665
+ : undefined;
2666
+ return {
2667
+ startupRoute: resolveModelRoute(configuredRoute, readModelPref(), harnessDefault),
2668
+ deploymentRoute: resolveModelRoute(configuredRoute, undefined, harnessDefault),
2669
+ };
2670
+ }
2671
+ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRoute, deploymentRoute, meta, configuredPreset) {
2672
+ // Deployment fallback (issue #67): a resumed session continues on the route
2673
+ // its own request/header records carry (issue #30) — the route it actually
2674
+ // ran is more specific than any static config — and only a session that
2675
+ // records none adopts the complete cordis.yml route. The recorded route is
2676
+ // ALSO fed back into agentOptions (not just the status line): a resume
2071
2677
  // whose cordis.yml pins only `provider` would otherwise leave
2072
2678
  // agentOptions.model undefined, which breaks the `{{model}}` persona
2073
2679
  // variable for the resumed agent's own assembly and for every subagent it
2074
2680
  // spawns (dsh-subagent inherits `parent.options.model`).
2075
- const resumeRoute = explicitModelRoute(configuredRoute);
2681
+ const resumeRoute = explicitModelRoute(configuredRoute) ?? deploymentRoute;
2076
2682
  if (requestedSessionId !== undefined) {
2077
2683
  const resumeId = SessionId(requestedSessionId);
2078
2684
  const existing = ctx.agents.get(resumeId);
@@ -2125,8 +2731,8 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2125
2731
  const composed = await composePreset(ctx, persisted);
2126
2732
  const recorded = await resolvePersistedRoute(ctx, resumeId);
2127
2733
  const resumeOptions = {
2128
- provider: resumeRoute?.provider ?? recorded?.provider,
2129
- model: resumeRoute?.model ?? recorded?.model,
2734
+ provider: recorded?.provider ?? resumeRoute?.provider,
2735
+ model: recorded?.model ?? resumeRoute?.model,
2130
2736
  };
2131
2737
  const resumed = await ctx.agents.resume({
2132
2738
  resumeSessionId: resumeId,
@@ -2134,14 +2740,14 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2134
2740
  ...(composed.setup === undefined ? {} : { setup: composed.setup }),
2135
2741
  });
2136
2742
  // 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).
2743
+ // continues on — its own request/header record, else the complete
2744
+ // cordis.yml deployment route (a bare log that pins none falls back to
2745
+ // the startup resolution, best effort).
2140
2746
  return {
2141
2747
  agent: resumed.agent,
2142
2748
  handle: resumed,
2143
2749
  agentPreset: composed.agentPreset,
2144
- route: resumeRoute ?? recordedModelRoute(snapshotLiveSessionEvents(resumed.agent.session)),
2750
+ route: recordedModelRoute(snapshotLiveSessionEvents(resumed.agent.session)) ?? resumeRoute,
2145
2751
  };
2146
2752
  }
2147
2753
  catch (error) {
@@ -2165,18 +2771,24 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2165
2771
  }
2166
2772
  }
2167
2773
  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)) {
2774
+ // Fresh-session preset precedence (issue #8): the DSH_TUI_PRESET launch
2775
+ // instruction, then the persisted `/preset` choice (the standing user
2776
+ // default), then the cordis.yml value as the deployment default, then the
2777
+ // roster default. The pref is migrated only when it was the effective
2778
+ // source — an override must not rewrite the remembered id.
2779
+ const presetOverride = presetOverrideFromEnv();
2780
+ const presetPref = readPresetPref();
2781
+ const composed = await composePreset(ctx, presetOverride ?? presetPref ?? configuredPreset);
2782
+ if (presetOverride === undefined && !migratePresetPref(presetPref, composed.agentPreset)) {
2171
2783
  ctx.logger.warn(`dsh-tui: resolved preset preference "${presetPref}" as "${composed.agentPreset}" but could not persist the migrated id`);
2172
2784
  }
2173
2785
  // Fresh-session route precedence (issues #14/#30/#67): resolved atomically
2174
- // by the caller (complete cordis.yml route > the persisted `/model` choice
2786
+ // by the caller (the persisted `/model` choice > a complete cordis.yml route
2175
2787
  // > 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.
2788
+ // stale persisted choice falls back to the deployment route wholesale
2789
+ // instead of reaching the server as an unknown model name.
2178
2790
  const llm = ctx.get('llm');
2179
- const { route, rejected } = await validateModelRoute(llm, startupRoute);
2791
+ const { route, rejected } = await validateModelRoute(llm, startupRoute, deploymentRoute);
2180
2792
  if (rejected !== undefined) {
2181
2793
  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
2794
  }
@@ -2193,7 +2805,7 @@ async function resolveAgent(ctx, requestedSessionId, configuredRoute, startupRou
2193
2805
  const bootReservation = bootReserved.ok ? bootReserved.reservation : undefined;
2194
2806
  let created;
2195
2807
  try {
2196
- created = await ctx.agents.create({
2808
+ created = await createFreshAgent(ctx, ctx.agents, {
2197
2809
  sessionId,
2198
2810
  meta: {
2199
2811
  ...meta,
@@ -2276,6 +2888,31 @@ export function runCrashExit(deps) {
2276
2888
  }
2277
2889
  deps.finish(`dsh-tui crashed: ${detail.message}`);
2278
2890
  }
2891
+ /** None where the root has no `ctx.agents` (before DSH composes, or a non-DSH kernel). */
2892
+ function dshAgentOn(ctx, agentId) {
2893
+ return ctx.get('agents')?.get(SessionId(agentId));
2894
+ }
2895
+ /**
2896
+ * The crash tail's resume markers (exported for scripts/verify-shutdown-fallback).
2897
+ * Leaves the marker a clean exit would, never CLEARS one.
2898
+ */
2899
+ export function writeCrashResumeMarkers(deps) {
2900
+ const { ctx, backendStart, channel } = deps;
2901
+ if (backendStart === undefined) {
2902
+ if (isExitResumable({
2903
+ pendingCount: channel.pending.length,
2904
+ liveAgent: dshAgentOn(ctx, channel.agentId),
2905
+ startupAgent: deps.startupAgent,
2906
+ })) {
2907
+ deps.writeResumeTarget(channel.agentId);
2908
+ }
2909
+ }
2910
+ else if (backendStart.persisted(channel.agentId, channel.rows)) {
2911
+ // Non-DSH sessions use their backend preference as the launcher marker.
2912
+ backendStart.sessionPrefs.setLastSession(channel.agentId);
2913
+ }
2914
+ deps.refreshLastRunRecord();
2915
+ }
2279
2916
  /**
2280
2917
  * Whether a user exit should leave the resume marker (and print the resume
2281
2918
  * hint). Must be judged against the LIVE session behind the channel, not the
@@ -2291,6 +2928,8 @@ export function isExitResumable(deps) {
2291
2928
  const agent = deps.liveAgent ?? deps.startupAgent;
2292
2929
  if (agent === undefined)
2293
2930
  return false;
2931
+ if (isUnstoredFreshSession(agent.session))
2932
+ return false;
2294
2933
  return (deps.pendingCount > 0 ||
2295
2934
  snapshotLiveSessionEvents(agent.session).some(event => event.type === 'user/message' && event.data.source.kind === 'user'));
2296
2935
  }
@@ -2336,7 +2975,7 @@ export async function finishExit(ctx, instance, fullscreen, notice, stderrNotice
2336
2975
  }
2337
2976
  const cursor = fullscreen ? '' : cursorMoveToFrameEnd(runtime);
2338
2977
  try {
2339
- runtime?.detachForShutdown?.();
2978
+ await runtime?.detachForShutdown?.();
2340
2979
  // The /update continuation spawns children that inherit this stdin;
2341
2980
  // strip the readable pump so the parent cannot swallow their input
2342
2981
  // (issues #284/#307). Harmless on plain exits — the process exits
@@ -2466,11 +3105,47 @@ function writeStream(stream, data) {
2466
3105
  function preservedSessionTail(sessionId, hint) {
2467
3106
  return sessionId === '' ? '\n\n' : ` Your session is preserved — resume with:\n${hint(sessionId)}\n\n`;
2468
3107
  }
2469
- function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile, id), options = {}) {
3108
+ /**
3109
+ * The entry's signals while following a replacement: SIGTERM is forwarded;
3110
+ * SIGINT/SIGHUP already reach it through the process group. A replacement
3111
+ * that dies by a termination signal ends this process the same way.
3112
+ */
3113
+ function superviseReplacement(exitSeam, options) {
3114
+ if (exitSeam === undefined)
3115
+ return options;
3116
+ let replacement;
3117
+ exitSeam.request = request => {
3118
+ if (replacement === undefined)
3119
+ return 'refused';
3120
+ const forwarded = request.kind === 'signal' && request.signal === 'SIGTERM';
3121
+ if (forwarded)
3122
+ replacement.kill('SIGTERM');
3123
+ logRestartEvent('supervisor: exit request while following the replacement', request.kind === 'signal' ? { signal: request.signal, forwarded } : { code: request.code });
3124
+ return 'supervising';
3125
+ };
3126
+ return {
3127
+ ...options,
3128
+ onSpawn: child => { replacement = child; },
3129
+ onTerminationSignal: signal => {
3130
+ if (TERMINATION_SIGNALS.includes(signal))
3131
+ dieBySignal(signal);
3132
+ },
3133
+ };
3134
+ }
3135
+ function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile, id), options = {}, exitSeam) {
2470
3136
  logRestartEvent('runRestart: entered, disposing cordis root');
2471
3137
  disposeRootAndThen(ctx, () => {
2472
3138
  logRestartEvent('runRestart: root disposed, starting restartTui');
2473
- void restartTui(sessionId, options).then(restartCode => {
3139
+ const supervision = superviseReplacement(exitSeam, options);
3140
+ // The React tree stays mounted while supervising, so a late UI read of the
3141
+ // ended channel is logged instead of crashing the supervisor.
3142
+ addProcessErrorAbsorber(error => {
3143
+ if (!(error instanceof Error) || error.message !== CHANNEL_UI_LIFETIME_ENDED)
3144
+ return false;
3145
+ logRestartEvent('supervisor: late UI read after dispose ignored', { at: error.stack?.split('\n').slice(1, 4).map(line => line.trim()).join(' | ') });
3146
+ return true;
3147
+ });
3148
+ void restartTui(sessionId, supervision).then(restartCode => {
2474
3149
  logRestartEvent('runRestart: restartTui resolved', { restartCode });
2475
3150
  if (restartCode !== 0) {
2476
3151
  writeHandoffNotice(`\ndsh-tui restart failed to spawn (exit ${restartCode}).${preservedSessionTail(sessionId, hint)}`);
@@ -2484,13 +3159,13 @@ function runRestart(ctx, profile, sessionId, hint = id => resumeCommand(profile,
2484
3159
  });
2485
3160
  });
2486
3161
  }
2487
- function runUpdate(ctx, profile, sessionId, targetVersion, kernel, hint = id => resumeCommand(profile, id)) {
3162
+ function runUpdate(ctx, profile, sessionId, targetVersion, kernel, hint = id => resumeCommand(profile, id), exitSeam) {
2488
3163
  disposeRootAndThen(ctx, () => {
2489
3164
  if (profile === undefined) {
2490
3165
  process.stderr.write(`\n${t('update-aborted-no-profile')}\n`);
2491
3166
  process.exit(1);
2492
3167
  }
2493
- void updateTuiAndRestart(sessionId, profile, targetVersion, kernel).then(({ updateCode, restartCode }) => {
3168
+ void updateTuiAndRestart(sessionId, profile, targetVersion, kernel, superviseReplacement(exitSeam, {})).then(({ updateCode, restartCode }) => {
2494
3169
  if (updateCode !== 0) {
2495
3170
  process.stderr.write(`\ndsh-tui update failed (exit ${updateCode}).${preservedSessionTail(sessionId, hint)}`);
2496
3171
  }
@@ -2534,16 +3209,25 @@ function resumeCommand(profile, sessionId) {
2534
3209
  * handoff (update/restart) may legitimately take longer than the bound, and
2535
3210
  * reporting failure on a clean exit would mislead wrapper scripts.
2536
3211
  */
2537
- function disposeRootAndThen(ctx, done, fallbackCode = 1) {
3212
+ function disposeRootAndThen(ctx, done, fallback = 1) {
3213
+ const startedAt = Date.now();
2538
3214
  const timer = setTimeout(() => {
2539
3215
  // Diagnosis for a stalled disposal: without this line the fallback exit
2540
3216
  // is indistinguishable from a successful handoff in the field.
2541
- logRestartEvent('dispose: timeout, taking fallback exit', { fallbackCode });
2542
- process.exit(fallbackCode);
3217
+ logRestartEvent('dispose: timeout, taking fallback exit', typeof fallback === 'number' ? { fallbackCode: fallback } : { fallback: 'signal' });
3218
+ if (typeof fallback === 'number')
3219
+ process.exit(fallback);
3220
+ else
3221
+ fallback();
2543
3222
  }, 5000);
2544
3223
  timer.unref();
2545
- void withHostRootCapability(() => ctx.root.fiber.dispose()).finally(() => closeBackendResources()).then(() => {
3224
+ // A composition still running into this root settles first.
3225
+ // The pooled, process-wide resources of every backend this process actually
3226
+ // loaded are closed here — after the fiber, never before (P0 D4-P2/P3).
3227
+ void disposeRootSettled(ctx, () => withHostRootCapability(() => ctx.root.fiber.dispose())).finally(() => unloadBackends()).then(() => {
2546
3228
  clearTimeout(timer);
3229
+ // The teardown's length, to restart.log only (never the terminal).
3230
+ logRestartEvent('dispose: root disposed', { ms: Date.now() - startedAt });
2547
3231
  done();
2548
3232
  }, () => {
2549
3233
  clearTimeout(timer);