@deepseek-harness-tui/dsh-tui 0.8.5 → 0.8.7

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 (253) hide show
  1. package/README.md +13 -3
  2. package/bin/dsh-tui.js +269 -218
  3. package/cordis.patch.yml +48 -22
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -13
  5. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -59
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -15
  7. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -32
  8. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -59
  9. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -166
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -42
  11. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -143
  12. package/dsh-ecosystem-spec/registry/README.md +14 -14
  13. package/dsh-ecosystem-spec/registry/host-descriptor.tui.example.json +56 -56
  14. package/dsh-ecosystem-spec/registry/permissions-0.1.json +13 -13
  15. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -149
  16. package/lib/types/cc/figures.d.ts +7 -0
  17. package/lib/types/cc/figures.d.ts.map +1 -1
  18. package/lib/types/cc/figures.js +24 -0
  19. package/lib/types/cc/markdown.d.ts.map +1 -1
  20. package/lib/types/cc/markdown.js +47 -14
  21. package/lib/types/cc/syntaxRuns.d.ts +16 -0
  22. package/lib/types/cc/syntaxRuns.d.ts.map +1 -0
  23. package/lib/types/cc/syntaxRuns.js +79 -0
  24. package/lib/types/cc/syntaxTheme.d.ts +12 -0
  25. package/lib/types/cc/syntaxTheme.d.ts.map +1 -0
  26. package/lib/types/cc/syntaxTheme.js +76 -0
  27. package/lib/types/commands.d.ts +14 -0
  28. package/lib/types/commands.d.ts.map +1 -1
  29. package/lib/types/commands.js +20 -1
  30. package/lib/types/components/Chat/SubagentMessage.d.ts +19 -0
  31. package/lib/types/components/Chat/SubagentMessage.d.ts.map +1 -0
  32. package/lib/types/components/Chat/SubagentMessage.js +75 -0
  33. package/lib/types/components/EffortChargeGlyph.d.ts +26 -0
  34. package/lib/types/components/EffortChargeGlyph.d.ts.map +1 -0
  35. package/lib/types/components/EffortChargeGlyph.js +70 -0
  36. package/lib/types/components/EffortInputBorder.d.ts +36 -0
  37. package/lib/types/components/EffortInputBorder.d.ts.map +1 -0
  38. package/lib/types/components/EffortInputBorder.js +73 -0
  39. package/lib/types/components/EffortTierBadge.d.ts +26 -0
  40. package/lib/types/components/EffortTierBadge.d.ts.map +1 -0
  41. package/lib/types/components/EffortTierBadge.js +97 -0
  42. package/lib/types/components/HelpMenu.d.ts +8 -1
  43. package/lib/types/components/HelpMenu.d.ts.map +1 -1
  44. package/lib/types/components/HelpMenu.js +15 -4
  45. package/lib/types/components/LoadedContextPanel.js +1 -1
  46. package/lib/types/components/LogoV2.d.ts +3 -1
  47. package/lib/types/components/LogoV2.d.ts.map +1 -1
  48. package/lib/types/components/LogoV2.js +2 -2
  49. package/lib/types/components/MessageList.d.ts +6 -2
  50. package/lib/types/components/MessageList.d.ts.map +1 -1
  51. package/lib/types/components/MessageList.js +22 -13
  52. package/lib/types/components/ModelPicker.js +2 -2
  53. package/lib/types/components/PromptInput.d.ts +1 -1
  54. package/lib/types/components/PromptInput.d.ts.map +1 -1
  55. package/lib/types/components/PromptInput.js +83 -18
  56. package/lib/types/components/SkillsPicker.js +2 -2
  57. package/lib/types/components/SplitDiffView.d.ts +2 -17
  58. package/lib/types/components/SplitDiffView.d.ts.map +1 -1
  59. package/lib/types/components/SplitDiffView.js +9 -162
  60. package/lib/types/components/StreamingMarkdown.js +1 -1
  61. package/lib/types/components/SubagentCard.d.ts +9 -0
  62. package/lib/types/components/SubagentCard.d.ts.map +1 -0
  63. package/lib/types/components/SubagentCard.js +26 -0
  64. package/lib/types/components/SubagentDashboard.d.ts +13 -0
  65. package/lib/types/components/SubagentDashboard.d.ts.map +1 -0
  66. package/lib/types/components/SubagentDashboard.js +50 -0
  67. package/lib/types/components/SubagentDetailScene.d.ts +16 -0
  68. package/lib/types/components/SubagentDetailScene.d.ts.map +1 -0
  69. package/lib/types/components/SubagentDetailScene.js +141 -0
  70. package/lib/types/components/SyntaxText.d.ts +15 -0
  71. package/lib/types/components/SyntaxText.d.ts.map +1 -0
  72. package/lib/types/components/SyntaxText.js +34 -0
  73. package/lib/types/components/ThinkingToggle.d.ts +3 -10
  74. package/lib/types/components/ThinkingToggle.d.ts.map +1 -1
  75. package/lib/types/components/ThinkingToggle.js +4 -9
  76. package/lib/types/components/design-system/ListItem.d.ts.map +1 -1
  77. package/lib/types/components/design-system/ListItem.js +1 -1
  78. package/lib/types/components/design-system/ThemedBox.d.ts.map +1 -1
  79. package/lib/types/components/design-system/ThemedBox.js +4 -1
  80. package/lib/types/components/design-system/ThemedText.js +2 -2
  81. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +12 -6
  82. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  83. package/lib/types/components/messages/AssistantThinkingMessage.js +37 -10
  84. package/lib/types/components/messages/AssistantToolUseMessage.d.ts +2 -0
  85. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  86. package/lib/types/components/messages/AssistantToolUseMessage.js +71 -18
  87. package/lib/types/components/messages/UserPromptMessage.d.ts +4 -6
  88. package/lib/types/components/messages/UserPromptMessage.d.ts.map +1 -1
  89. package/lib/types/components/messages/UserPromptMessage.js +19 -10
  90. package/lib/types/dsh-adapter/channel.d.ts +58 -2
  91. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  92. package/lib/types/dsh-adapter/channel.js +337 -7
  93. package/lib/types/dsh-adapter/contract.d.ts +27 -4
  94. package/lib/types/dsh-adapter/contract.d.ts.map +1 -1
  95. package/lib/types/dsh-adapter/contract.js +56 -10
  96. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  97. package/lib/types/dsh-adapter/index.js +9 -3
  98. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  99. package/lib/types/dsh-adapter/plugin.js +38 -0
  100. package/lib/types/dsh-adapter/shortcuts.js +3 -3
  101. package/lib/types/dsh-adapter/subagents.d.ts +87 -0
  102. package/lib/types/dsh-adapter/subagents.d.ts.map +1 -0
  103. package/lib/types/dsh-adapter/subagents.js +204 -0
  104. package/lib/types/herdr.d.ts +28 -0
  105. package/lib/types/herdr.d.ts.map +1 -0
  106. package/lib/types/herdr.js +133 -0
  107. package/lib/types/i18n.d.ts +215 -31
  108. package/lib/types/i18n.d.ts.map +1 -1
  109. package/lib/types/i18n.js +65 -14
  110. package/lib/types/ink/ink.d.ts +4 -0
  111. package/lib/types/ink/ink.d.ts.map +1 -1
  112. package/lib/types/ink/ink.js +46 -15
  113. package/lib/types/minimalMode.d.ts +13 -0
  114. package/lib/types/minimalMode.d.ts.map +1 -0
  115. package/lib/types/minimalMode.js +17 -0
  116. package/lib/types/screens/Chat.d.ts +1 -1
  117. package/lib/types/screens/Chat.d.ts.map +1 -1
  118. package/lib/types/screens/Chat.js +95 -45
  119. package/lib/types/screens/StatusLine.d.ts.map +1 -1
  120. package/lib/types/screens/StatusLine.js +6 -2
  121. package/lib/types/screens/TrajectoryScene.d.ts.map +1 -1
  122. package/lib/types/screens/TrajectoryScene.js +4 -2
  123. package/lib/types/theme.d.ts +20 -0
  124. package/lib/types/theme.d.ts.map +1 -1
  125. package/lib/types/theme.js +77 -23
  126. package/lib/types/tips.js +3 -3
  127. package/lib/types/trajectory/effortIgnition.d.ts +66 -0
  128. package/lib/types/trajectory/effortIgnition.d.ts.map +1 -0
  129. package/lib/types/trajectory/effortIgnition.js +142 -0
  130. package/lib/types/update.d.ts +18 -0
  131. package/lib/types/update.d.ts.map +1 -1
  132. package/lib/types/update.js +73 -2
  133. package/lib/types/utils/execFileNoThrow.d.ts +3 -20
  134. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  135. package/lib/types/utils/execFileNoThrow.js +53 -32
  136. package/lib/types/utils/externalEditor.d.ts +12 -11
  137. package/lib/types/utils/externalEditor.d.ts.map +1 -1
  138. package/lib/types/utils/externalEditor.js +11 -11
  139. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -8
  140. package/node_modules/@dsh-std/command/LICENSE +21 -21
  141. package/node_modules/@dsh-std/command/README.md +26 -26
  142. package/node_modules/@dsh-std/command/README.zh.md +28 -28
  143. package/node_modules/@dsh-std/command/lib/index.js.map +1 -1
  144. package/node_modules/@dsh-std/command/package.json +22 -22
  145. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  146. package/node_modules/@dsh-std/connection/LICENSE +21 -21
  147. package/node_modules/@dsh-std/connection/README.md +42 -42
  148. package/node_modules/@dsh-std/connection/README.zh.md +42 -42
  149. package/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  150. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  151. package/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  152. package/node_modules/@dsh-std/connection/package.json +20 -20
  153. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  154. package/node_modules/@dsh-std/core/LICENSE +21 -21
  155. package/node_modules/@dsh-std/core/README.md +13 -13
  156. package/node_modules/@dsh-std/core/README.zh.md +13 -13
  157. package/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  158. package/node_modules/@dsh-std/core/package.json +21 -21
  159. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  160. package/node_modules/@dsh-std/manifest/LICENSE +21 -21
  161. package/node_modules/@dsh-std/manifest/README.md +5 -5
  162. package/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  163. package/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  164. package/node_modules/@dsh-std/manifest/package.json +20 -20
  165. package/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  166. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -8
  167. package/node_modules/@dsh-std/messages/LICENSE +21 -21
  168. package/node_modules/@dsh-std/messages/README.md +5 -5
  169. package/node_modules/@dsh-std/messages/README.zh.md +7 -7
  170. package/node_modules/@dsh-std/messages/lib/index.js.map +1 -1
  171. package/node_modules/@dsh-std/messages/package.json +20 -20
  172. package/node_modules/@dsh-std/messages/schema/message-observer-envelope-0.15.schema.json +54 -54
  173. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  174. package/node_modules/@dsh-std/presentation/LICENSE +21 -21
  175. package/node_modules/@dsh-std/presentation/README.md +27 -27
  176. package/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  177. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  178. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  179. package/node_modules/@dsh-std/presentation/package.json +25 -25
  180. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -8
  181. package/node_modules/@dsh-std/storage/LICENSE +21 -21
  182. package/node_modules/@dsh-std/storage/README.md +5 -5
  183. package/node_modules/@dsh-std/storage/README.zh.md +7 -7
  184. package/node_modules/@dsh-std/storage/lib/index.js.map +1 -1
  185. package/node_modules/@dsh-std/storage/package.json +20 -20
  186. package/node_modules/@dsh-std/storage/schema/local-storage-0.15.schema.json +56 -56
  187. package/package.json +64 -58
  188. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  189. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/LICENSE +21 -21
  190. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.md +42 -42
  191. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/README.zh.md +42 -42
  192. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  193. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  194. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  195. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +20 -20
  196. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  197. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/LICENSE +21 -21
  198. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.md +13 -13
  199. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/README.zh.md +13 -13
  200. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  201. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +21 -21
  202. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -9
  203. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/LICENSE +21 -21
  204. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.md +5 -5
  205. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/README.zh.md +5 -5
  206. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/lib/index.js.map +1 -1
  207. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +20 -20
  208. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/schema/dsh-plugin-0.15.schema.json +144 -144
  209. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -9
  210. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/LICENSE +21 -21
  211. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +27 -27
  212. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +29 -29
  213. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -1
  214. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  215. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +25 -25
  216. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  217. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/LICENSE +21 -21
  218. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.md +13 -13
  219. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/README.zh.md +13 -13
  220. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  221. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +21 -21
  222. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  223. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/LICENSE +21 -21
  224. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.md +13 -13
  225. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/README.zh.md +13 -13
  226. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  227. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +21 -21
  228. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  229. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/LICENSE +21 -21
  230. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.md +13 -13
  231. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/README.zh.md +13 -13
  232. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  233. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +21 -21
  234. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -8
  235. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/LICENSE +21 -21
  236. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.md +42 -42
  237. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/README.zh.md +42 -42
  238. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/index.js.map +1 -1
  239. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  240. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/resolve-3X6ausgz.js.map +1 -1
  241. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +20 -20
  242. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  243. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/LICENSE +21 -21
  244. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.md +13 -13
  245. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/README.zh.md +13 -13
  246. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  247. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +21 -21
  248. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -8
  249. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/LICENSE +21 -21
  250. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.md +13 -13
  251. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/README.zh.md +13 -13
  252. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/lib/index.js.map +1 -1
  253. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +21 -21
package/README.md CHANGED
@@ -88,6 +88,14 @@ sh install.sh
88
88
 
89
89
  `dsh-tui --resume` 恢复上次会话;Windows 也可用仓库里的 `dsh-tui.cmd`(等价)。
90
90
 
91
+ ### Herdr
92
+
93
+ 在 [Herdr](https://herdr.dev) pane 中直接运行 `dsh-tui` 即可,无需额外配置。
94
+ dsh-TUI 会通过 Herdr 提供的本地接口上报 `idle`、`working`、`blocked` 状态;
95
+ 问卷或工具审批出现时标记为 `blocked`。离开 Herdr 运行时该集成完全不启用。
96
+ Herdr 的 `agent start --kind dsh-tui`、session 身份与服务重启后自动恢复仍需 Herdr
97
+ 上游加入原生 agent kind;当前可正常保活、重连和观察手工启动的 pane。
98
+
91
99
  在 VS Code 中运行的完整指南(内置终端直接使用 + companion 扩展
92
100
  `dsh-tui-vscode`——**真实集成终端承载、体验与 Claude Code 官方扩展几乎一致、
93
101
  已上架 VS Code Marketplace**)见
@@ -122,7 +130,7 @@ npm install -g @deepseek-harness-tui/dsh-tui@<profile-version>
122
130
  | `Ctrl+R` | 历史消息搜索 |
123
131
  | `/` | 会话内全文搜索(`n`/`N` 跳转) |
124
132
  | `Ctrl+V` | 粘贴文本或文件管理器中的文件;图片显示为 `[Image #N]` 并作为持久附件发送 |
125
- | `Ctrl+X` | 用 `$VISUAL`/`$EDITOR`(如 nvim)打开当前输入编辑,保存退出后回填 |
133
+ | `Ctrl+G` | 用 `$VISUAL`/`$EDITOR`(如 nvim)打开当前输入编辑,保存退出后回填 |
126
134
  | `?` | 快捷键菜单(仅输入框为空时响应) |
127
135
  | `Shift+↑` | 消息选择模式(Enter 展开单条) |
128
136
  | `Ctrl+P` | 切换启动时 loaded-context 面板(面板在屏时有效) |
@@ -145,7 +153,7 @@ macOS 自带 Terminal.app 会自行消费 `⌘` 快捷键,请继续使用 `Ctr
145
153
  |---|---|
146
154
  | 拖拽选择 | 应用内文本选区,**松开即复制**(OSC 52 + `wl-copy`/`xclip`/`xsel` 原生兜底;tmux 内走 `load-buffer -w`),复制后自动取消选区并弹出「已复制 N 个字符」提示 |
147
155
  | 双击 / 三击 | 选词 / 选行,同样即选即复制 |
148
- | 滚轮 | 滚动消息列表(±3 行/格) |
156
+ | 滚轮 | 仅在 fullscreen 且鼠标跟踪启用时:Help 打开时滚动帮助,否则滚动消息列表(±3 行/格);默认 inline 模式不会把滚轮事件交给 TUI |
149
157
  | `Esc` | 拖拽进行中取消选区(不复制) |
150
158
  | 单击消息行 | 展开/收起该行 |
151
159
  | 单击「加载更早消息」/「ctrl+e 显示前 N 条」 | 加载更早消息 / 展开全部 |
@@ -298,7 +306,9 @@ pnpm smoke
298
306
  ```
299
307
 
300
308
  `lib/types/` 是忽略入库的生成目录;`pnpm build` 会从干净输出目录重新编译并运行
301
- 构建门禁。npm Git URL 安装通过 `prepare` 生成同一套运行时。渲染、问卷和工具卡
309
+ 构建门禁。**Git URL 安装不受支持**(源 manifest 的 `@dsh-std/*` 为 workspace 依赖、
310
+ `vendor/dsh-std` 为子模块、且 pnpm ≥11 默认拒绝 git 依赖的 `prepare` 脚本);请安装
311
+ registry 包:`dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui`。渲染、问卷和工具卡
302
312
  改动还需运行对应回归脚本。
303
313
 
304
314
 
package/bin/dsh-tui.js CHANGED
@@ -1,47 +1,103 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * dsh-tui — dsh-tui profile 的一键直达启动器。
3
+ * dsh-tui — 双态启动器(delegating launcher,0.8.7)。
4
4
  *
5
- * 全局安装 @deepseek-harness-tui/dsh-tui 后获得 `dsh-tui` 命令,免去手工
6
- * 输入 `dsh --profile dsh-tui`:
5
+ * 同一个文件按“自己住在哪”决定扮演的角色:
7
6
  *
8
- * 1. 检测 dsh CLI(缺失时提示安装 @deepseek-ai/dsh);
9
- * 2. 检测 $DSH_HOME/profiles/dsh-tui 是否已初始化,未初始化则自动执行
10
- * `dsh plugin --profile dsh-tui add @deepseek-harness-tui/dsh-tui@<本包版本>`
11
- * 自举——版本号与本包对齐,避免 pnpm store 缓存带来的旧版漂移;
12
- * 3. 已初始化但版本与本包不一致时按方向处理(issue #183):profile
13
- * 更新(前向错位)打印一行提示后继续启动(TUI 内 /update 或重新
14
- * add);profile 次版本更旧(反向错位)拒绝启动并给出对齐命令——
15
- * 该方向 dsh CLI 会把启动器的 bundle patch 套到 profile 旧包上,
16
- * 启动必然以模块解析错误崩溃;
17
- * 4. 透传全部参数启动 `dsh --profile dsh-tui`。
7
+ * 全局安装副本(npm i -g 得到的 `dsh-tui` 命令)→ 瘦壳:
8
+ * 1. 找到 $DSH_HOME/profiles/dsh-tui 里的同包 bin;
9
+ * 2. 可读 → 原样转发 argv 委托它执行(完整逻辑永远来自 profile 副本,
10
+ * 版本随 /update 一起前进,启动器滞后问题从结构上消失);
11
+ * 3. 不可读(首次运行)→ 探测 dsh/pnpm 后自举
12
+ * `dsh plugin --profile dsh-tui add <本包>@<本包版本>`,成功后委托。
18
13
  *
19
- * `--resume` 由本启动器拦截:读取 TUI 保留的 ~/.dsh-tui/resume.txt
20
- * (旧路径 ~/.dsh-cc/resume.txt 兜底,直到旧版 TUI 退场——见
21
- * src/sessionHistory.ts 的启动器契约,issue #120),以
22
- * DSH_TUI_RESUME_SESSION(并兼容写 DSH_CC_RESUME_SESSION)环境变量喂回,
23
- * 该 flag 本身不再传给 dsh。
14
+ * profile 内副本(被委托执行,或 junction/源码目录里直接运行)→ 完整
15
+ * 启动逻辑(与 0.8.6 及之前一致):
16
+ * dsh 预检 / profile 版本核对 / --resume 与工作区目标拦截 /
17
+ * 旧环境变量警告 / `dsh --profile dsh-tui` 启动与退出码透传。
24
18
  *
25
- * 面向用户的消息走下方 MSG 双语表:与 TUI 的语言契约一致——
19
+ * 自举角色判定用 realpath:Windows Junction 轨(profile 指回仓库)与
20
+ * `pnpm run dev` 源码运行都会折叠成同一物理目录 → 走完整逻辑,不会
21
+ * 自己委托自己形成循环。
22
+ *
23
+ * 本文件必须保持零 lib/ 依赖:/update 的启动器迁移只覆写这一个文件
24
+ * (外加 package.json 的版本号),旧全局安装里不存在新 lib 助手时同样
25
+ * 可用。shellQuote 等小工具在此内联。
26
+ *
27
+ * 面向用户的消息走 MSG 双语表:与 TUI 的语言契约一致——
26
28
  * `DSH_TUI_LANG` 显式指定时从其值,否则默认中文(同 src/i18n.ts 的缺省)。
27
29
  */
28
30
  import { spawn, spawnSync } from 'node:child_process'
29
- import { existsSync, readFileSync } from 'node:fs'
31
+ import { existsSync, readFileSync, realpathSync } from 'node:fs'
30
32
  import { homedir } from 'node:os'
31
- import { isAbsolute, join, resolve } from 'node:path'
33
+ import { dirname, isAbsolute, join, resolve } from 'node:path'
32
34
  import { fileURLToPath } from 'node:url'
33
- import { gt, valid } from 'semver'
34
- import { shellQuote } from '../lib/types/utils/shellQuote.js'
35
- import { detectLegacyEnv, RENAMED_ENV } from '../lib/types/utils/paths.js'
36
35
 
37
- const here = fileURLToPath(new URL('.', import.meta.url))
38
- const ownVersion = JSON.parse(readFileSync(join(here, '..', 'package.json'), 'utf8')).version
36
+ const here = dirname(fileURLToPath(import.meta.url))
37
+ const ownDir = dirname(here)
38
+ const readJson = p => {
39
+ try {
40
+ return JSON.parse(readFileSync(p, 'utf8'))
41
+ } catch {
42
+ return undefined
43
+ }
44
+ }
45
+ const ownPackage = readJson(join(ownDir, 'package.json'))
46
+ const ownVersion = ownPackage?.name === '@deepseek-harness-tui/dsh-tui' ? ownPackage.version : undefined
39
47
  const PACKAGE = '@deepseek-harness-tui/dsh-tui'
40
48
  const PROFILE = 'dsh-tui'
41
49
 
42
- // --- 双语消息表(启动器跑在 TUI boot 之前,无法复用 src/i18n.ts)-------------
43
- // 与 TUI 一致:DSH_TUI_LANG 显式指定才生效,否则默认中文;旧名 CC_TUI_LANG
44
- // 仅用于让警告本身以用户习惯的语言显示(配置不再从旧名生效)。
50
+ // --- 内联小工具(见文件头:零 lib 依赖是迁移契约的一部分)---------------------
51
+ // 与 lib/types/utils/shellQuote.js 同语义的最小实现:cmd.exe 以空格拼接参数
52
+ // 且不做转义,含空格/引号的参数必须整体加引号(内层引号与反斜杠转义)。
53
+ const shellQuote = args =>
54
+ args.map(arg => {
55
+ const s = String(arg)
56
+ if (s === '') return '""'
57
+ if (!/[\s"^]/.test(s)) return s
58
+ return `"${s.replace(/(\\*)"/g, '$1$1\\"').replace(/(\\+)$/, '$1$1')}"`
59
+ })
60
+ const isWin = process.platform === 'win32'
61
+ const shellOpt = isWin ? { shell: true } : {}
62
+ // DEP0190(issue #148):shell:true + 非空参数数组触发语法级弃用告警——
63
+ // 转义后拼进命令字符串(空参数数组不触发),非 Windows 保持数组直传。
64
+ const cmd = (command, args) =>
65
+ isWin ? [`${command} ${shellQuote(args).join(' ')}`, []] : [command, args]
66
+
67
+ // 内联 semver(解析 + 严格大于):启动器可能在依赖不完整的环境里被执行
68
+ // (迁移、半损坏安装、测试沙箱),零外部依赖是自保底线。覆盖 semver 的
69
+ // 核心-先行版比较规则:先行版标识符逐段比(数字段按数值、小于字母段),
70
+ // 前缀相同时段数少者更旧,无先行版者最新。
71
+ const parseVersion = v => {
72
+ const m = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(String(v).trim())
73
+ return m
74
+ ? { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]), pre: m[4] === undefined ? null : m[4].split('.') }
75
+ : null
76
+ }
77
+ const isVersionNewer = (a, b) => {
78
+ const A = parseVersion(a)
79
+ const B = parseVersion(b)
80
+ if (A === null || B === null) return false
81
+ for (const key of ['major', 'minor', 'patch']) {
82
+ if (A[key] !== B[key]) return A[key] > B[key]
83
+ }
84
+ if (A.pre === null) return B.pre !== null
85
+ if (B.pre === null) return false
86
+ for (let i = 0; i < Math.max(A.pre.length, B.pre.length); i++) {
87
+ const x = A.pre[i]
88
+ const y = B.pre[i]
89
+ if (x === undefined) return false
90
+ if (y === undefined) return true
91
+ if (x === y) continue
92
+ const xn = /^\d+$/.test(x)
93
+ const yn = /^\d+$/.test(y)
94
+ if (xn && yn) return Number(x) > Number(y)
95
+ if (xn !== yn) return yn
96
+ return x > y
97
+ }
98
+ return false
99
+ }
100
+
45
101
  const lang = (process.env.DSH_TUI_LANG ?? process.env.CC_TUI_LANG) === 'en' ? 'en' : 'zh'
46
102
  const MSG = {
47
103
  noDsh: {
@@ -64,10 +120,6 @@ const MSG = {
64
120
  en: `[dsh-tui] Plugin install failed. Retry manually later:\n dsh plugin --profile ${PROFILE} add -w ${PACKAGE}@${ownVersion}`,
65
121
  zh: `[dsh-tui] 插件安装失败。可稍后手工重试:\n dsh plugin --profile ${PROFILE} add -w ${PACKAGE}@${ownVersion}`,
66
122
  },
67
- // no-op 假成功(自举后复查发现判定文件依旧缺失):pnpm 把包文件残缺的
68
- // profile 视为 already up to date,重装什么都不做却报告成功;继续启动
69
- // 只会以 cannot resolve profile bundle 崩溃,而崩溃提示的 `plugin
70
- // install` 同样 no-op——用户会陷入「每步都成功、每次都崩」的循环。
71
123
  bootstrapUnreadable: {
72
124
  en: dir =>
73
125
  `[dsh-tui] install reported success but the plugin package is still unreadable under:\n` +
@@ -82,60 +134,16 @@ const MSG = {
82
134
  ` 恢复方法:\n` +
83
135
  ` rm -rf ${dir} 后重新运行 dsh-tui`,
84
136
  },
85
- // Non-fatal skew comes in two directions and each needs its own repair:
86
- // a profile NEWER than the launcher (typical after /update) must point at
87
- // the global install, while a profile older only in patch must point back
88
- // at the profile. The old generic message told forward-skew users to run
89
- // /update again — an "Already up to date" loop.
90
- profileOutdated: {
91
- en: (installed, own) =>
92
- `[dsh-tui] note: the profile is running v${installed} but this launcher is v${own}.\n` +
93
- ` Align the profile to this launcher:\n` +
94
- ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${own}`,
95
- zh: (installed, own) =>
96
- `[dsh-tui] 提示:profile 内运行的是 v${installed},启动器是 v${own}。\n` +
97
- ` 请把 profile 对齐到启动器版本:\n` +
98
- ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${own}`,
99
- },
100
- launcherOutdated: {
101
- en: (installed, own) =>
102
- `[dsh-tui] note: the profile is already v${installed}, but the global launcher is still v${own}.\n` +
103
- ` Align the global dsh-tui launcher to the profile:\n` +
104
- ` npm install -g ${PACKAGE}@${installed}\n` +
105
- ` (if you installed it globally with pnpm: pnpm add -g ${PACKAGE}@${installed})`,
106
- zh: (installed, own) =>
107
- `[dsh-tui] 提示:profile 已是 v${installed},但全局启动器仍是 v${own}。\n` +
108
- ` 请把全局 dsh-tui 启动器对齐到 profile 版本:\n` +
109
- ` npm install -g ${PACKAGE}@${installed}\n` +
110
- ` (如果你使用 pnpm 全局安装:pnpm add -g ${PACKAGE}@${installed})`,
111
- },
112
- // Reverse skew (issue #183): the dsh CLI reads the bundle patch from the
113
- // FIRST copy found from its own install anchor — this globally installed
114
- // launcher — while the plugin modules load from the profile's copy. A
115
- // launcher minor NEWER than the profile means the patch may reference
116
- // subpath exports the profile's older package does not have, and boot
117
- // crashes opaquely (ERR_PACKAGE_PATH_NOT_EXPORTED) before any TUI code
118
- // runs. Fail loud with the fix instead. (Forward skew degrades to a
119
- // working local-workspace fallback since 0.7.2 — soft note below.)
120
- profileOlderThanLauncher: {
121
- en: (installed, own) =>
122
- `[dsh-tui] cannot start: the profile runs v${installed} but this launcher is v${own}.\n` +
123
- ` The launcher's bundle patch would be applied to the profile's older package,\n` +
124
- ` which does not export everything the patch references — boot would crash.\n` +
125
- ` Align the profile with the launcher:\n` +
126
- ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${own}\n` +
127
- ` (or update everything to the latest release: dsh plugin --profile ${PROFILE} add ${PACKAGE}@latest)`,
128
- zh: (installed, own) =>
129
- `[dsh-tui] 无法启动:profile 内运行的是 v${installed},而启动器是 v${own}。\n` +
130
- ` 启动器的 bundle patch 会套用到 profile 里的旧版包上,其中缺少 patch 引用\n` +
131
- ` 的子路径导出——启动会以模块解析错误崩溃。请让 profile 与启动器对齐:\n` +
132
- ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${own}\n` +
133
- ` (或全部升到最新:dsh plugin --profile ${PROFILE} add ${PACKAGE}@latest)`,
134
- },
135
137
  launchFailed: {
136
138
  en: err => `[dsh-tui] Failed to launch: ${err.message}`,
137
139
  zh: err => `[dsh-tui] 启动失败:${err.message}`,
138
140
  },
141
+ delegateFailed: {
142
+ en: path =>
143
+ `[dsh-tui] cannot launch the profile copy:\n ${path}\nReinstall the global launcher:\n npm install -g ${PACKAGE}@latest`,
144
+ zh: path =>
145
+ `[dsh-tui] 无法启动 profile 内副本:\n ${path}\n请重装全局启动器:\n npm install -g ${PACKAGE}@latest`,
146
+ },
139
147
  profileExited: {
140
148
  en: code => `[dsh-tui] dsh profile exited with code ${code}. Run it directly for diagnostics:\n dsh --profile ${PROFILE}`,
141
149
  zh: code => `[dsh-tui] dsh profile 已退出(退出码 ${code})。可直接运行以下命令查看诊断:\n dsh --profile ${PROFILE}`,
@@ -151,55 +159,59 @@ const msg = key => MSG[key][lang]
151
159
  // 长会话 OOM——与仓库根 dsh-tui.cmd 保持一致,强制 production。
152
160
  process.env.NODE_ENV ??= 'production'
153
161
 
154
- const isWin = process.platform === 'win32'
155
- // Windows 上 .cmd shim 必须经 shell 启动(Node ≥18.20.2 的安全限制);
156
- // 其余平台直接 spawn 无后缀的 dsh。cmd.exe 以空格拼接参数且 Node 不做
157
- // 转义——shell 路径的所有参数必须先过 shellQuote(同 src/update.ts 的
158
- // /update 重启路径),否则含空格/引号的参数会被拆断。
159
- const shellOpt = isWin ? { shell: true } : {}
160
- // DEP0190(issue #148):Node ≥22 对「shell:true + 非空参数数组」的调用
161
- // 发出弃用警告,判定是语法级的——参数已过 shellQuote 也一样告警;且未来
162
- // 大版本可能升级为运行时错误。把转义后的参数拼进命令字符串传入
163
- // (shell:true + 空参数数组不触发),非 Windows 路径保持数组直传。
164
- const cmd = (command, args) =>
165
- isWin ? [`${command} ${shellQuote(args).join(' ')}`, []] : [command, args]
166
-
167
- // --- 1. dsh CLI 预检 ---------------------------------------------------------
168
- const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
169
- if (probe.error || probe.status !== 0) {
170
- console.error(msg('noDsh'))
171
- process.exit(1)
162
+ const sameDir = (a, b) => {
163
+ try {
164
+ return realpathSync(resolve(a)) === realpathSync(resolve(b))
165
+ } catch {
166
+ return resolve(a) === resolve(b)
167
+ }
172
168
  }
173
169
 
174
- // --- 2. profile 自举与版本检查 -------------------------------------------------
175
170
  const dshHome = process.env.DSH_HOME || join(homedir(), '.dsh')
176
171
  const profileDir = join(dshHome, 'profiles', PROFILE)
177
- // 以已装包的 package.json 可读为准(而非目录存在):安装中途失败留下的
178
- // 残骸目录会触发重新安装,而不是以坏 profile 直接启动。
179
- // 包可读判定文件:installedVersion 读取与自举后的复查共用(残缺 profile
180
- // 的 no-op 假成功靠它识别——见 bootstrapUnreadable)。
181
- const installedPkgPath = join(profileDir, 'node_modules', '@deepseek-harness-tui', 'dsh-tui', 'package.json')
182
- let installedVersion
183
- try {
184
- installedVersion = JSON.parse(readFileSync(installedPkgPath, 'utf8')).version
185
- } catch {
186
- installedVersion = undefined
172
+ const profilePkgDir = join(profileDir, 'node_modules', '@deepseek-harness-tui', 'dsh-tui')
173
+ const profileBin = join(profilePkgDir, 'bin', 'dsh-tui.js')
174
+ const installedPkgPath = join(profilePkgDir, 'package.json')
175
+ const runningInsideProfile = sameDir(ownDir, profilePkgDir)
176
+
177
+ const forwardExit = child => {
178
+ child.on('error', err => {
179
+ console.error(msg('launchFailed')(err))
180
+ process.exit(1)
181
+ })
182
+ child.on('exit', (code, signal) => {
183
+ if (signal) {
184
+ process.kill(process.pid, signal)
185
+ } else {
186
+ if (code !== null && code !== 0) console.error(msg('profileExited')(code))
187
+ process.exit(code ?? 0)
188
+ }
189
+ })
187
190
  }
188
- if (installedVersion === undefined) {
191
+
192
+ // 首次运行自举:探测 dsh 与 pnpm,随后 `dsh plugin add` 固定到与启动器一
193
+ // 致的版本(避免 pnpm store 缓存带来的旧版漂移)。-w 重试(issue #239)与
194
+ // 「no-op 假成功」复查(issue #209)在此集中实现,瘦壳与完整逻辑共用。
195
+ const profileReady = () => {
196
+ try {
197
+ readFileSync(installedPkgPath, 'utf8')
198
+ return true
199
+ } catch {
200
+ return false
201
+ }
202
+ }
203
+ const bootstrapProfile = () => {
204
+ const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
205
+ if (probe.error || probe.status !== 0) {
206
+ console.error(msg('noDsh'))
207
+ process.exit(1)
208
+ }
189
209
  const pnpmProbe = spawnSync(...cmd('pnpm', ['--version']), { stdio: 'pipe', ...shellOpt })
190
210
  if (pnpmProbe.error || pnpmProbe.status !== 0) {
191
211
  console.error(msg('noPnpm'))
192
212
  process.exit(1)
193
213
  }
194
214
  console.log(msg('bootstrapStart'))
195
- // pnpm ≥11 在带 pnpm-workspace.yaml 的 profile 目录里可能拒绝向
196
- // workspace root 写依赖(ERR_PNPM_ADDING_TO_ROOT,issue #239):识别该
197
- // 错误时带 -w 重试一次。签名在 stdout——pnpm 的错误报告写 stdout(经
198
- // dsh 原样转发),stderr 上只有 dsh 自己的失败说明,识别 stderr 永远
199
- // 匹配不到(PR #241 的回归)。因此首次 add 的 stdout/stderr 都走 pipe
200
- // 捕获,结束后回放(失败回 stderr 保诊断,成功回 stdout 保进度,代价
201
- // 是不再实时);-w 重试大概率成功,恢复 inherit 让进度实时可见。手工
202
- // 重试提示同步带 -w。
203
215
  const runAdd = (extraArgs, capture) => spawnSync(
204
216
  ...cmd('dsh', ['plugin', '--profile', PROFILE, 'add', ...extraArgs, `${PACKAGE}@${ownVersion}`]),
205
217
  { stdio: capture ? ['inherit', 'pipe', 'pipe'] : 'inherit', ...shellOpt },
@@ -219,116 +231,155 @@ if (installedVersion === undefined) {
219
231
  console.error(msg('installFailed'))
220
232
  process.exit(add.status ?? 1)
221
233
  }
222
- // 自举后复查:pnpm 对包文件残缺的 profile 视为 already up to date,
223
- // add 报告成功却什么都没装(no-op 假成功)。此时继续启动只会以 cannot
224
- // resolve profile bundle 崩溃,提示的 `plugin install` 又同样 no-op——
225
- // fail loud 给出唯一可靠的恢复路径(删 profile 目录重建)。
234
+ if (!profileReady()) {
235
+ console.error(msg('bootstrapUnreadable')(profileDir))
236
+ process.exit(1)
237
+ }
238
+ }
239
+
240
+ // ─── 全局副本:瘦壳角色 ───────────────────────────────────────────────────────
241
+ // DSH_TUI_NO_DELEGATE=1 是测试/调试逃生口:强制走完整逻辑(verify-launcher
242
+ // 的沙箱用它直接驱动全量路径;现场排查委托链时同样可用)。
243
+ if (!runningInsideProfile && ownVersion !== undefined && process.env.DSH_TUI_NO_DELEGATE !== '1') {
244
+ if (!profileReady()) bootstrapProfile()
245
+ // 委托 profile 内副本执行全部启动逻辑。外层代际通过
246
+ // DSH_TUI_LAUNCHER_VERSION 交代(/update 的对齐诊断沿用该契约)。
226
247
  try {
227
- readFileSync(installedPkgPath, 'utf8')
248
+ readFileSync(profileBin, 'utf8')
228
249
  } catch {
229
- console.error(MSG.bootstrapUnreadable[lang](profileDir))
250
+ console.error(msg('delegateFailed')(profileBin))
230
251
  process.exit(1)
231
252
  }
232
- } else if (installedVersion !== ownVersion) {
233
- // Reverse skew is fatal (see MSG.profileOlderThanLauncher): compare
234
- // major/minor only — patch-level differences never move the patch surface.
235
- const majorMinor = v => v.split('-')[0].split('.').slice(0, 2).map(Number)
236
- const [installedMajor, installedMinor] = majorMinor(installedVersion)
237
- const [ownMajor, ownMinor] = majorMinor(ownVersion)
238
- if (installedMajor < ownMajor || (installedMajor === ownMajor && installedMinor < ownMinor)) {
239
- console.error(MSG.profileOlderThanLauncher[lang](installedVersion, ownVersion))
240
- process.exit(1)
253
+ process.env.DSH_TUI_LAUNCHER_VERSION = ownVersion
254
+ const child = spawn(process.execPath, [profileBin, ...process.argv.slice(2)], {
255
+ stdio: 'inherit',
256
+ env: process.env,
257
+ })
258
+ forwardExit(child)
259
+ } else {
260
+ // ─── profile 副本(或源码运行):完整启动逻辑 ─────────────────────────────
261
+ // dsh CLI 预检(缺失时给安装指引,先于一切 profile 逻辑)。
262
+ {
263
+ const probe = spawnSync(...cmd('dsh', ['--version']), { stdio: 'pipe', ...shellOpt })
264
+ if (probe.error || probe.status !== 0) {
265
+ console.error(msg('noDsh'))
266
+ process.exit(1)
267
+ }
241
268
  }
242
-
243
- // Non-fatal skew: repair in the direction the versions actually moved.
244
- // A profile NEWER than the launcher (e.g. right after /update) means the
245
- // GLOBAL launcher is stale — never point those users back at /update.
246
- // Exact versions only: @latest could skip past the alignment point if a
247
- // newer release or a mirror dist-tag is in play.
248
- const installedSemver = valid(installedVersion)
249
- const ownSemver = valid(ownVersion)
250
- if (installedSemver !== null && ownSemver !== null && gt(installedSemver, ownSemver)) {
251
- console.error(MSG.launcherOutdated[lang](installedVersion, ownVersion))
252
- } else {
253
- console.error(MSG.profileOutdated[lang](installedVersion, ownVersion))
269
+ let installedVersion
270
+ try {
271
+ installedVersion = JSON.parse(readFileSync(installedPkgPath, 'utf8')).version
272
+ } catch {
273
+ installedVersion = undefined
254
274
  }
255
- }
256
-
257
- // --- 3. --resume 拦截 ---------------------------------------------------------
258
- // 换名过渡(issue #120):全局 bin 与 profile 内 TUI 包版本可能错位,所以
259
- // env 双写(新旧名都设),文件读取新路径优先、旧路径兜底。
260
- // 支持的形态(对齐 issue #53 的诉求):
261
- // --resume <id> / --resume=<id> 恢复指定会话
262
- // --resume / -c / --continue 恢复最近一次会话(读 resume.txt)
263
- // 其余位置参数原样透传给 dsh CLI,由插件经 ctx.cmdlineArgs 读取(初始 prompt)。
264
- const setResumeEnv = sessionId => {
265
- process.env.DSH_TUI_RESUME_SESSION = sessionId
266
- process.env.DSH_CC_RESUME_SESSION = sessionId
267
- }
268
- const readLastResumeTarget = () => {
269
- for (const dir of ['.dsh-tui', '.dsh-cc']) {
275
+ // 残骸/未初始化 profile:与旧启动器一致地就地自举(add 固定到本包版
276
+ // 本,成功后版本天然对齐),而不是拒绝启动。
277
+ if (installedVersion === undefined) {
278
+ bootstrapProfile()
270
279
  try {
271
- const sessionId = readFileSync(join(homedir(), dir, 'resume.txt'), 'utf8').trim()
272
- if (sessionId) return sessionId
280
+ installedVersion = JSON.parse(readFileSync(installedPkgPath, 'utf8')).version
273
281
  } catch {
274
- // 没有历史会话可恢复——静默忽略,正常冷启动。
282
+ installedVersion = undefined
275
283
  }
276
284
  }
277
- return ''
278
- }
279
- const args = []
280
- const argv = process.argv.slice(2)
281
- for (let i = 0; i < argv.length; i++) {
282
- const a = argv[i]
283
- if (a === '--resume' || a === '-c' || a === '--continue' || a.startsWith('--resume=')) {
284
- let sessionId = ''
285
- if (a.startsWith('--resume=')) {
286
- sessionId = a.slice('--resume='.length).trim()
287
- } else if (a === '--resume' && argv[i + 1] !== undefined && !argv[i + 1].startsWith('-')) {
288
- sessionId = argv[++i].trim()
285
+ if (installedVersion !== undefined && ownVersion !== undefined && installedVersion !== ownVersion && !runningInsideProfile) {
286
+ const majorMinor = v => v.split('-')[0].split('.').slice(0, 2).map(Number)
287
+ const [installedMajor, installedMinor] = majorMinor(installedVersion)
288
+ const [ownMajor, ownMinor] = majorMinor(ownVersion)
289
+ if (installedMajor < ownMajor || (installedMajor === ownMajor && installedMinor < ownMinor)) {
290
+ console.error(
291
+ `[dsh-tui] cannot start: the profile runs v${installedVersion} but this launcher is v${ownVersion}.\n` +
292
+ ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${ownVersion}`,
293
+ )
294
+ process.exit(1)
295
+ }
296
+ const installedNewer = installedVersion !== undefined && ownVersion !== undefined && isVersionNewer(installedVersion, ownVersion)
297
+ if (installedNewer) {
298
+ console.error(
299
+ `[dsh-tui] note: the profile is already v${installedVersion}; this launcher copy is v${ownVersion}.\n` +
300
+ ` npm install -g ${PACKAGE}@${installedVersion}`,
301
+ )
302
+ } else {
303
+ // profile 更旧但同 minor(patch 级错位):允许启动,指引用 add 把
304
+ // profile 对齐到启动器版本(精确版本,@latest 可能越过对齐点)。
305
+ console.error(
306
+ `[dsh-tui] note: the profile is running v${installedVersion} but this launcher is v${ownVersion}.\n` +
307
+ ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${ownVersion}`,
308
+ )
289
309
  }
290
- // 裸形态(含 -c/--continue)回退到 resume.txt。
291
- if (!sessionId) sessionId = readLastResumeTarget()
292
- if (sessionId) setResumeEnv(sessionId)
293
- } else if (
294
- process.env.DSH_TUI_WORKSPACE_TARGET === undefined
295
- && !a.startsWith('-')
296
- && (isAbsolute(a) || /^[a-z][a-z0-9+.-]*:\/\//iu.test(a) || existsSync(resolve(a)))
297
- ) {
298
- // A workspace target is launcher syntax, not an argument for the profile
299
- // app. The registry resolves local paths/file URLs and provider URIs.
300
- process.env.DSH_TUI_WORKSPACE_TARGET = a
301
- } else {
302
- args.push(a)
303
310
  }
304
- }
305
311
 
306
- // --- 3.5 旧环境变量警告(必须在 TUI 渲染前输出,fullscreen 下写 stderr 会破坏界面)
307
- for (const oldName of detectLegacyEnv()) {
308
- console.error(MSG.legacyEnv[lang](oldName, RENAMED_ENV[oldName]))
309
- }
312
+ // --resume / 工作区目标拦截(issue #120/#53 的启动器契约)。
313
+ const setResumeEnv = sessionId => {
314
+ process.env.DSH_TUI_RESUME_SESSION = sessionId
315
+ process.env.DSH_CC_RESUME_SESSION = sessionId
316
+ }
317
+ const readLastResumeTarget = () => {
318
+ for (const dir of ['.dsh-tui', '.dsh-cc']) {
319
+ try {
320
+ const sessionId = readFileSync(join(homedir(), dir, 'resume.txt'), 'utf8').trim()
321
+ if (sessionId) return sessionId
322
+ } catch {
323
+ // 没有历史会话可恢复——静默忽略,正常冷启动。
324
+ }
325
+ }
326
+ return ''
327
+ }
328
+ const args = []
329
+ const argv = process.argv.slice(2)
330
+ for (let i = 0; i < argv.length; i++) {
331
+ const a = argv[i]
332
+ if (a === '--resume' || a === '-c' || a === '--continue' || a.startsWith('--resume=')) {
333
+ let sessionId = ''
334
+ if (a.startsWith('--resume=')) {
335
+ sessionId = a.slice('--resume='.length).trim()
336
+ } else if (a === '--resume' && argv[i + 1] !== undefined && !argv[i + 1].startsWith('-')) {
337
+ sessionId = argv[++i].trim()
338
+ }
339
+ if (!sessionId) sessionId = readLastResumeTarget()
340
+ if (sessionId) setResumeEnv(sessionId)
341
+ } else if (
342
+ process.env.DSH_TUI_WORKSPACE_TARGET === undefined
343
+ && !a.startsWith('-')
344
+ && (isAbsolute(a) || /^[a-z][a-z0-9+.-]*:\/\//iu.test(a) || existsSync(resolve(a)))
345
+ ) {
346
+ process.env.DSH_TUI_WORKSPACE_TARGET = a
347
+ } else {
348
+ args.push(a)
349
+ }
350
+ }
310
351
 
311
- // --- 4. 启动 ------------------------------------------------------------------
312
- // Contract for the profile runtime: lets /update diagnose whether the
313
- // global dsh-tui launcher is older than the profile it just installed.
314
- // Not a one-shot marker — it must survive restarts so the runtime always
315
- // knows the launcher generation it boots under.
316
- process.env.DSH_TUI_LAUNCHER_VERSION = ownVersion
352
+ // 旧环境变量警告(必须在 TUI 渲染前输出,fullscreen 下写 stderr 会破坏
353
+ // 界面)。与 utils/paths 的 RENAMED_ENV 同表,内联以维持零 lib 依赖。
354
+ const RENAMED_ENV = {
355
+ CC_TUI_THEME: 'DSH_TUI_THEME',
356
+ CC_TUI_LANG: 'DSH_TUI_LANG',
357
+ CC_TUI_PERSONA: 'DSH_TUI_PERSONA',
358
+ CC_TUI_PRESET: 'DSH_TUI_PRESET',
359
+ CC_TUI_DISABLE_MOUSE: 'DSH_TUI_DISABLE_MOUSE',
360
+ CC_TUI_DEBUG: 'DSH_TUI_DEBUG',
361
+ CC_TUI_COMPACT_RATIO: 'DSH_TUI_COMPACT_RATIO',
362
+ CC_TUI_COMPACT_RETAIN: 'DSH_TUI_COMPACT_RETAIN',
363
+ DSH_CC_UPDATED_FROM: 'DSH_TUI_UPDATED_FROM',
364
+ DSH_CC_RENDER_LOG: 'DSH_TUI_RENDER_LOG',
365
+ DSH_CC_SESSION_ROOT: 'DSH_TUI_SESSION_ROOT',
366
+ DSH_CC_WORKSPACE: 'DSH_TUI_WORKSPACE',
367
+ }
368
+ for (const oldName of Object.keys(RENAMED_ENV)) {
369
+ if (process.env[oldName] !== undefined) {
370
+ console.error(msg('legacyEnv')(oldName, RENAMED_ENV[oldName]))
371
+ }
372
+ }
317
373
 
318
- const child = spawn(...cmd('dsh', ['--profile', PROFILE, ...args]), {
319
- stdio: 'inherit',
320
- env: process.env,
321
- ...shellOpt,
322
- })
323
- child.on('error', err => {
324
- console.error(MSG.launchFailed[lang](err))
325
- process.exit(1)
326
- })
327
- child.on('exit', (code, signal) => {
328
- if (signal) {
329
- process.kill(process.pid, signal)
330
- } else {
331
- if (code !== null && code !== 0) console.error(MSG.profileExited[lang](code))
332
- process.exit(code ?? 0)
374
+ // 启动:被委托场景下本副本自己的版本即对齐诊断所见的启动器代际。
375
+ if (process.env.DSH_TUI_LAUNCHER_VERSION === undefined && ownVersion !== undefined) {
376
+ process.env.DSH_TUI_LAUNCHER_VERSION = ownVersion
333
377
  }
334
- })
378
+
379
+ const child = spawn(...cmd('dsh', ['--profile', PROFILE, ...args]), {
380
+ stdio: 'inherit',
381
+ env: process.env,
382
+ ...shellOpt,
383
+ })
384
+ forwardExit(child)
385
+ }