@deepseek-harness-tui/dsh-tui 0.8.1 → 0.8.3

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 (241) hide show
  1. package/README.md +30 -15
  2. package/bin/dsh-tui.js +43 -7
  3. package/dsh-ecosystem-spec/protocols/profile-definitions.d.ts +13 -0
  4. package/dsh-ecosystem-spec/protocols/profile-definitions.js +59 -0
  5. package/dsh-ecosystem-spec/protocols/tui-channel-http.d.ts +15 -0
  6. package/dsh-ecosystem-spec/protocols/tui-channel-http.js +32 -0
  7. package/dsh-ecosystem-spec/protocols/tui-channel.d.ts +59 -0
  8. package/dsh-ecosystem-spec/protocols/tui-channel.js +166 -0
  9. package/dsh-ecosystem-spec/protocols/tui-contributions.d.ts +42 -0
  10. package/dsh-ecosystem-spec/protocols/tui-contributions.js +143 -0
  11. package/dsh-ecosystem-spec/registry/README.md +14 -0
  12. package/dsh-ecosystem-spec/registry/contracts/scene-v1alpha1.json +13 -0
  13. package/dsh-ecosystem-spec/registry/contracts/settings-section-v1alpha1.json +13 -0
  14. package/dsh-ecosystem-spec/registry/contracts/tui-channel-v1alpha1.json +30 -0
  15. package/dsh-ecosystem-spec/registry/contracts/workspace-provider-v1alpha1.json +6 -0
  16. package/{ecosystem-spec → dsh-ecosystem-spec}/registry/permissions-0.1.json +1 -1
  17. package/dsh-ecosystem-spec/registry/registry-0.15.json +149 -0
  18. package/lib/types/command-trees.d.ts +2 -1
  19. package/lib/types/command-trees.d.ts.map +1 -1
  20. package/lib/types/command-trees.js +1 -1
  21. package/lib/types/components/LoadedContextPanel.d.ts +10 -5
  22. package/lib/types/components/LoadedContextPanel.d.ts.map +1 -1
  23. package/lib/types/components/LoadedContextPanel.js +18 -7
  24. package/lib/types/components/LogoV2.d.ts.map +1 -1
  25. package/lib/types/components/LogoV2.js +16 -7
  26. package/lib/types/components/Markdown.d.ts +13 -1
  27. package/lib/types/components/Markdown.d.ts.map +1 -1
  28. package/lib/types/components/Markdown.js +15 -1
  29. package/lib/types/components/MessageList.d.ts +3 -1
  30. package/lib/types/components/MessageList.d.ts.map +1 -1
  31. package/lib/types/components/MessageList.js +90 -9
  32. package/lib/types/components/PromptInput.d.ts.map +1 -1
  33. package/lib/types/components/PromptInput.js +26 -12
  34. package/lib/types/components/StreamingMarkdown.d.ts +2 -8
  35. package/lib/types/components/StreamingMarkdown.d.ts.map +1 -1
  36. package/lib/types/components/StreamingMarkdown.js +51 -9
  37. package/lib/types/components/activityFrames.d.ts +1 -1
  38. package/lib/types/components/activityFrames.d.ts.map +1 -1
  39. package/lib/types/components/activityFrames.js +4 -1
  40. package/lib/types/components/messages/AssistantThinkingMessage.d.ts +5 -1
  41. package/lib/types/components/messages/AssistantThinkingMessage.d.ts.map +1 -1
  42. package/lib/types/components/messages/AssistantThinkingMessage.js +26 -3
  43. package/lib/types/components/messages/AssistantToolUseMessage.d.ts.map +1 -1
  44. package/lib/types/components/messages/AssistantToolUseMessage.js +12 -1
  45. package/lib/types/dsh-adapter/channel.d.ts +31 -4
  46. package/lib/types/dsh-adapter/channel.d.ts.map +1 -1
  47. package/lib/types/dsh-adapter/channel.js +267 -102
  48. package/lib/types/dsh-adapter/childStderr.d.ts.map +1 -1
  49. package/lib/types/dsh-adapter/childStderr.js +10 -0
  50. package/lib/types/dsh-adapter/command-attribution.d.ts.map +1 -1
  51. package/lib/types/dsh-adapter/command-attribution.js +2 -2
  52. package/lib/types/dsh-adapter/command-trees.d.ts +7 -1
  53. package/lib/types/dsh-adapter/command-trees.d.ts.map +1 -1
  54. package/lib/types/dsh-adapter/command-trees.js +70 -20
  55. package/lib/types/dsh-adapter/component-identity.d.ts +5 -0
  56. package/lib/types/dsh-adapter/component-identity.d.ts.map +1 -1
  57. package/lib/types/dsh-adapter/component-identity.js +14 -14
  58. package/lib/types/dsh-adapter/decision-guard.d.ts.map +1 -1
  59. package/lib/types/dsh-adapter/decision-guard.js +10 -16
  60. package/lib/types/dsh-adapter/dialogs.d.ts +2 -3
  61. package/lib/types/dsh-adapter/dialogs.d.ts.map +1 -1
  62. package/lib/types/dsh-adapter/dialogs.js +114 -60
  63. package/lib/types/dsh-adapter/effect-ledger.d.ts +0 -8
  64. package/lib/types/dsh-adapter/effect-ledger.d.ts.map +1 -1
  65. package/lib/types/dsh-adapter/effect-ledger.js +73 -53
  66. package/lib/types/dsh-adapter/extension-events.d.ts.map +1 -1
  67. package/lib/types/dsh-adapter/extension-events.js +39 -1
  68. package/lib/types/dsh-adapter/grants.d.ts.map +1 -1
  69. package/lib/types/dsh-adapter/grants.js +43 -14
  70. package/lib/types/dsh-adapter/host-access.d.ts +37 -0
  71. package/lib/types/dsh-adapter/host-access.d.ts.map +1 -0
  72. package/lib/types/dsh-adapter/host-access.js +652 -0
  73. package/lib/types/dsh-adapter/host-descriptor.d.ts +2 -2
  74. package/lib/types/dsh-adapter/host-descriptor.d.ts.map +1 -1
  75. package/lib/types/dsh-adapter/host-descriptor.js +31 -8
  76. package/lib/types/dsh-adapter/index.d.ts +4 -0
  77. package/lib/types/dsh-adapter/index.d.ts.map +1 -1
  78. package/lib/types/dsh-adapter/index.js +1 -0
  79. package/lib/types/dsh-adapter/message-observer.d.ts +10 -15
  80. package/lib/types/dsh-adapter/message-observer.d.ts.map +1 -1
  81. package/lib/types/dsh-adapter/message-observer.js +114 -70
  82. package/lib/types/dsh-adapter/plugin-host.d.ts +39 -6
  83. package/lib/types/dsh-adapter/plugin-host.d.ts.map +1 -1
  84. package/lib/types/dsh-adapter/plugin-host.js +139 -44
  85. package/lib/types/dsh-adapter/plugin-storage.d.ts +0 -5
  86. package/lib/types/dsh-adapter/plugin-storage.d.ts.map +1 -1
  87. package/lib/types/dsh-adapter/plugin-storage.js +48 -36
  88. package/lib/types/dsh-adapter/plugin.d.ts.map +1 -1
  89. package/lib/types/dsh-adapter/plugin.js +97 -16
  90. package/lib/types/dsh-adapter/promptDebug.d.ts +0 -6
  91. package/lib/types/dsh-adapter/promptDebug.d.ts.map +1 -1
  92. package/lib/types/dsh-adapter/promptDebug.js +13 -4
  93. package/lib/types/dsh-adapter/renderers.d.ts +6 -14
  94. package/lib/types/dsh-adapter/renderers.d.ts.map +1 -1
  95. package/lib/types/dsh-adapter/renderers.js +81 -46
  96. package/lib/types/dsh-adapter/scenes.d.ts +8 -4
  97. package/lib/types/dsh-adapter/scenes.d.ts.map +1 -1
  98. package/lib/types/dsh-adapter/scenes.js +136 -37
  99. package/lib/types/dsh-adapter/settings-sections.d.ts +7 -3
  100. package/lib/types/dsh-adapter/settings-sections.d.ts.map +1 -1
  101. package/lib/types/dsh-adapter/settings-sections.js +106 -26
  102. package/lib/types/dsh-adapter/shortcuts.d.ts +8 -10
  103. package/lib/types/dsh-adapter/shortcuts.d.ts.map +1 -1
  104. package/lib/types/dsh-adapter/shortcuts.js +108 -36
  105. package/lib/types/dsh-adapter/status.d.ts +6 -7
  106. package/lib/types/dsh-adapter/status.d.ts.map +1 -1
  107. package/lib/types/dsh-adapter/status.js +88 -22
  108. package/lib/types/dsh-adapter/workspaces.d.ts +16 -5
  109. package/lib/types/dsh-adapter/workspaces.d.ts.map +1 -1
  110. package/lib/types/dsh-adapter/workspaces.js +287 -170
  111. package/lib/types/extensions.d.ts +10 -5
  112. package/lib/types/extensions.d.ts.map +1 -1
  113. package/lib/types/extensions.js +11 -5
  114. package/lib/types/force-production-react.d.ts +1 -0
  115. package/lib/types/force-production-react.d.ts.map +1 -0
  116. package/lib/types/force-production-react.js +12 -0
  117. package/lib/types/i18n.d.ts +22 -6
  118. package/lib/types/i18n.d.ts.map +1 -1
  119. package/lib/types/i18n.js +14 -2
  120. package/lib/types/index.d.ts +1 -0
  121. package/lib/types/index.d.ts.map +1 -1
  122. package/lib/types/index.js +1 -0
  123. package/lib/types/ink/components/App.d.ts +2 -0
  124. package/lib/types/ink/components/App.d.ts.map +1 -1
  125. package/lib/types/ink/components/App.js +4 -0
  126. package/lib/types/ink/ink.d.ts +20 -3
  127. package/lib/types/ink/ink.d.ts.map +1 -1
  128. package/lib/types/ink/ink.js +57 -5
  129. package/lib/types/ink/log-update.d.ts.map +1 -1
  130. package/lib/types/ink/log-update.js +128 -9
  131. package/lib/types/ink/output.d.ts +28 -2
  132. package/lib/types/ink/output.d.ts.map +1 -1
  133. package/lib/types/ink/output.js +158 -12
  134. package/lib/types/ink/screen.d.ts +39 -0
  135. package/lib/types/ink/screen.d.ts.map +1 -1
  136. package/lib/types/ink/screen.js +117 -0
  137. package/lib/types/ink/terminal.d.ts.map +1 -1
  138. package/lib/types/ink/terminal.js +10 -2
  139. package/lib/types/ink/wrapAnsi.d.ts +1 -1
  140. package/lib/types/ink/wrapAnsi.d.ts.map +1 -1
  141. package/lib/types/ink/wrapAnsi.js +29 -2
  142. package/lib/types/plugin-host.d.ts +13 -11
  143. package/lib/types/plugin-host.d.ts.map +1 -1
  144. package/lib/types/plugin-host.js +16 -16
  145. package/lib/types/plugin-spec/registry.d.ts +2 -2
  146. package/lib/types/plugin-spec/registry.d.ts.map +1 -1
  147. package/lib/types/plugin-spec/registry.js +113 -18
  148. package/lib/types/plugin-spec/tui-extension.d.ts.map +1 -1
  149. package/lib/types/plugin-spec/tui-extension.js +2 -0
  150. package/lib/types/plugin-spec/validate.d.ts.map +1 -1
  151. package/lib/types/plugin-spec/validate.js +20 -1
  152. package/lib/types/scenes.d.ts +2 -1
  153. package/lib/types/scenes.d.ts.map +1 -1
  154. package/lib/types/scenes.js +1 -1
  155. package/lib/types/screens/Chat.d.ts +3 -3
  156. package/lib/types/screens/Chat.d.ts.map +1 -1
  157. package/lib/types/screens/Chat.js +38 -17
  158. package/lib/types/screens/SessionBrowser.d.ts.map +1 -1
  159. package/lib/types/screens/SessionBrowser.js +10 -3
  160. package/lib/types/sessions/view.d.ts +3 -2
  161. package/lib/types/sessions/view.d.ts.map +1 -1
  162. package/lib/types/sessions/view.js +18 -0
  163. package/lib/types/settings-sections.d.ts +2 -1
  164. package/lib/types/settings-sections.d.ts.map +1 -1
  165. package/lib/types/settings-sections.js +1 -1
  166. package/lib/types/utils/execFileNoThrow.d.ts.map +1 -1
  167. package/lib/types/utils/execFileNoThrow.js +3 -0
  168. package/lib/types/workspaces.d.ts +2 -1
  169. package/lib/types/workspaces.d.ts.map +1 -1
  170. package/lib/types/workspaces.js +1 -1
  171. package/node_modules/@dsh-std/command/CHANGELOG.md +8 -0
  172. package/node_modules/@dsh-std/command/package.json +1 -1
  173. package/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
  174. package/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
  175. package/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
  176. package/node_modules/@dsh-std/connection/lib/memory.js +1 -0
  177. package/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  178. package/node_modules/@dsh-std/connection/package.json +1 -1
  179. package/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  180. package/node_modules/@dsh-std/core/package.json +1 -1
  181. package/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
  182. package/node_modules/@dsh-std/manifest/package.json +1 -1
  183. package/node_modules/@dsh-std/messages/CHANGELOG.md +8 -0
  184. package/node_modules/@dsh-std/messages/package.json +1 -1
  185. package/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
  186. package/node_modules/@dsh-std/presentation/README.md +2 -1
  187. package/node_modules/@dsh-std/presentation/README.zh.md +2 -1
  188. package/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
  189. package/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
  190. package/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
  191. package/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
  192. package/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
  193. package/node_modules/@dsh-std/presentation/lib/index.js +6 -3
  194. package/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  195. package/node_modules/@dsh-std/presentation/package.json +7 -3
  196. package/node_modules/@dsh-std/storage/CHANGELOG.md +8 -0
  197. package/node_modules/@dsh-std/storage/package.json +1 -1
  198. package/package.json +10 -5
  199. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
  200. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
  201. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
  202. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js +1 -0
  203. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  204. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/connection/package.json +1 -1
  205. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  206. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/core/package.json +1 -1
  207. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/CHANGELOG.md +9 -0
  208. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/manifest/package.json +1 -1
  209. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/CHANGELOG.md +9 -0
  210. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.md +2 -1
  211. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/README.zh.md +2 -1
  212. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.d.ts +2 -0
  213. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js +179 -0
  214. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/callback.js.map +1 -0
  215. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts +44 -1
  216. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.d.ts.map +1 -1
  217. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js +6 -3
  218. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/lib/index.js.map +1 -1
  219. package/vendor/dsh-std/packages/command/node_modules/@dsh-std/presentation/package.json +7 -3
  220. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  221. package/vendor/dsh-std/packages/connection/node_modules/@dsh-std/core/package.json +1 -1
  222. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  223. package/vendor/dsh-std/packages/manifest/node_modules/@dsh-std/core/package.json +1 -1
  224. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  225. package/vendor/dsh-std/packages/messages/node_modules/@dsh-std/core/package.json +1 -1
  226. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/CHANGELOG.md +8 -0
  227. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts +1 -1
  228. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.d.ts.map +1 -1
  229. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js +1 -0
  230. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/lib/memory.js.map +1 -1
  231. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/connection/package.json +1 -1
  232. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  233. package/vendor/dsh-std/packages/presentation/node_modules/@dsh-std/core/package.json +1 -1
  234. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/CHANGELOG.md +8 -0
  235. package/vendor/dsh-std/packages/storage/node_modules/@dsh-std/core/package.json +1 -1
  236. package/ecosystem-spec/registry/registry-0.15.json +0 -57
  237. /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/contracts/decision-events-v1alpha1.json +0 -0
  238. /package/{ecosystem-spec → dsh-ecosystem-spec}/registry/host-descriptor.tui.example.json +0 -0
  239. /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/conformance-claim.schema.json +0 -0
  240. /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/effect-ledger-record.schema.json +0 -0
  241. /package/{ecosystem-spec → dsh-ecosystem-spec}/schemas/host-descriptor.schema.json +0 -0
package/README.md CHANGED
@@ -13,6 +13,12 @@
13
13
  <a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-263146?style=flat-square"></a>
14
14
  <img alt="Public beta" src="https://img.shields.io/badge/status-public%20beta-7da1de?style=flat-square">
15
15
  <img alt="官方收录" src="https://img.shields.io/badge/DeepSeek%20Harness%20官方公众号-收录-brightgreen">
16
+ <a href="https://github.com/ccch1mneyyy/dsh-TUI/stargazers"><img alt="GitHub stars" src="https://img.shields.io/github/stars/ccch1mneyyy/dsh-TUI?style=flat-square&color=4b6fff"></a>
17
+ <a href="https://www.npmjs.com/package/@deepseek-harness-tui/dsh-tui"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@deepseek-harness-tui/dsh-tui?style=flat-square&color=4b6fff"></a>
18
+ </p>
19
+
20
+ <p align="center">
21
+ <a href="https://trendshift.io/repositories/146168" title="GitHub Trending 日榜 #7 · TypeScript 口径"><img alt="Trendshift" src="https://trendshift.io/api/badge/trendshift/repositories/146168/daily?language=TypeScript"></a>
16
22
  </p>
17
23
 
18
24
  # dsh-TUI
@@ -87,8 +93,14 @@ sh install.sh
87
93
  已上架 VS Code Marketplace**)见
88
94
  [在 VS Code 中运行 dsh-TUI](docs/vscode.md)。
89
95
 
90
- TUI 启动后会在后台检查 npm 是否有新版本;发现更新时会提示,输入 `/update`
91
- 即可自动更新并重启恢复当前会话。
96
+ TUI 启动后会在后台检查 npm 是否有新版本;发现更新时可输入 `/update`。
97
+ `/update` 更新当前 `dsh-tui` profile 中的 runtime 并重启会话,它不会静默修改
98
+ npm/pnpm 的全局安装。若通过全局 `dsh-tui` 命令启动且 Launcher 版本落后,
99
+ 0.8.3 起会给出精确的全局对齐命令,例如:
100
+
101
+ ```sh
102
+ npm install -g @deepseek-harness-tui/dsh-tui@<profile-version>
103
+ ```
92
104
 
93
105
  旧版 `dsh-cc-tui` / `cc-tui` profile 的迁移命令与兼容数据说明见
94
106
  [安装与快速开始](docs/getting-started.md#从旧包迁移)。
@@ -100,7 +112,7 @@ TUI 启动后会在后台检查 npm 是否有新版本;发现更新时会提
100
112
 
101
113
  | 键 | 功能 |
102
114
  |---|---|
103
- | `Enter` | 发送(`Shift+Enter` 换行);命令菜单打开时执行选中项 |
115
+ | `Enter` | 发送(`Shift+Enter` 换行,无法上报修饰键时可用 `Ctrl+J`);命令菜单打开时执行选中项 |
104
116
  | `Ctrl+C` | 中断当前回合;空闲时连按两次退出 |
105
117
  | `Esc` | 关闭命令/文件菜单;空闲双击清空输入;**空输入双击 = 时间回溯** |
106
118
  | `Ctrl+O` | 展开/收起详情(思考全文、工具参数与输出) |
@@ -143,12 +155,26 @@ macOS 自带 Terminal.app 会自行消费 `⌘` 快捷键,请继续使用 `Ctr
143
155
  |---|---|
144
156
  | 会话 | `/new` 新会话 · `/resume` 会话浏览器(搜索、预览、跨项目、折叠子 agent 运行) · `/rename` 重命名会话 · `/workspace resume|rename|open` 管理工作区 · `/clear` 清屏 · `/compact` 压缩 · `/export` 导出 Markdown · `/trace` 轨迹场景(亦可 `Ctrl+T`) |
145
157
  | 状态 | `/context` 已加载上下文明细 · `/status` 会话信息 · `/cost` token 用量 · `/doctor` 环境自检 · `/config` 配置来源 · `/init` 创建 AGENTS.md |
146
- | 模型 | `/model` 选择器 · `/thinking` 思考显示 · `/tokens` token 明细 · `/theme` 主题选择器 · `/lang` 中英界面切换 |
158
+ | 模型 | `/model` 选择器 · `/thinking` 思考显示 · `/tokens` token 明细 · `/theme` 主题选择器 · `/lang` 中英界面切换(`/settings` 中亦可选择) |
147
159
  | 账号/策略 | `/provider` 添加模型提供方 · `/login` 凭证状态 · `/logout` 登出说明 · `/permissions` 权限说明 · `/add-dir` 文件策略范围 · `/hooks` · `/mcp` |
148
160
  | 技能 | `/audit` 代码审计 · `/bug` bug 报告 · `/review` 代码评审 · `/practice` 编程练习 · `/pr_comments` PR 评论 · `/release-notes` 发布说明 · `/vuln-check` 漏洞检查 |
149
161
  | 其它 | `/agents` 子代理列表 · `/update` 自动更新并重启 · `/vim` · `/terminal-setup` · `/connect` · `/help` · `/exit` |
150
162
  | 注册表 | `/plan` `/goal`(DSH 命令注册表插件,随插件自动并入 `/` 菜单) |
151
163
 
164
+ ## 插件生态
165
+
166
+ 想为 dsh-TUI 做插件/扩展?欢迎加入生态!
167
+
168
+ - **接口与兼容性协定**:[终端交互生态插件准入规范与实施标准](https://github.com/T-Auto/dsh-ecosystem-spec)
169
+ - **插件开发指南**:[`docs/plugins.md`](docs/plugins.md)(接缝、契约、规范与验证清单)
170
+ - **生态组织**:[dsh-tui-ecosystem](https://github.com/dsh-tui-ecosystem)(社区插件与模板的家)
171
+ - **模板仓库**:[plugin-template](https://github.com/dsh-tui-ecosystem/plugin-template)(从模板起步,5 分钟出一个插件)
172
+ - **参考实现**:`dsh-working-activity`(实时工作状态行:TUI 槽位 + `activity/status` 会话事件双出口)
173
+
174
+ 核心仓库不迁移、社区插件独立成仓。组织只维护收录列表与准入规范,不对社区插件
175
+ 的功能、质量或安全作背书或担保;插件作者对自己的仓库保持完全所有权,并自行承担
176
+ 维护与安全责任。
177
+
152
178
  ## 文档
153
179
 
154
180
  | 主题 | 内容 |
@@ -256,18 +282,7 @@ pnpm smoke
256
282
  构建门禁。npm Git URL 安装通过 `prepare` 生成同一套运行时。渲染、问卷和工具卡
257
283
  改动还需运行对应回归脚本。
258
284
 
259
- ## 插件生态
260
-
261
- 想为 dsh-TUI 做插件/扩展?欢迎加入生态:
262
-
263
- - **插件开发指南**:[`docs/plugins.md`](docs/plugins.md)(接缝、契约、规范与验证清单)
264
- - **生态组织**:[dsh-tui-ecosystem](https://github.com/dsh-tui-ecosystem)(社区插件与模板的家)
265
- - **模板仓库**:[plugin-template](https://github.com/dsh-tui-ecosystem/plugin-template)(从模板起步,5 分钟出一个插件)
266
- - **参考实现**:`dsh-working-activity`(实时工作状态行:TUI 槽位 + `activity/status` 会话事件双出口)
267
285
 
268
- 核心仓库不迁移、社区插件独立成仓。组织只维护收录列表与准入规范,不对社区插件
269
- 的功能、质量或安全作背书或担保;插件作者对自己的仓库保持完全所有权,并自行承担
270
- 维护与安全责任。
271
286
 
272
287
  ## 社区
273
288
 
package/bin/dsh-tui.js CHANGED
@@ -30,6 +30,7 @@ import { existsSync, readFileSync } from 'node:fs'
30
30
  import { homedir } from 'node:os'
31
31
  import { isAbsolute, join, resolve } from 'node:path'
32
32
  import { fileURLToPath } from 'node:url'
33
+ import { gt, valid } from 'semver'
33
34
  import { shellQuote } from '../lib/types/utils/shellQuote.js'
34
35
  import { detectLegacyEnv, RENAMED_ENV } from '../lib/types/utils/paths.js'
35
36
 
@@ -63,15 +64,32 @@ const MSG = {
63
64
  en: `[dsh-tui] Plugin install failed. Retry manually later:\n dsh plugin --profile ${PROFILE} add -w ${PACKAGE}@${ownVersion}`,
64
65
  zh: `[dsh-tui] 插件安装失败。可稍后手工重试:\n dsh plugin --profile ${PROFILE} add -w ${PACKAGE}@${ownVersion}`,
65
66
  },
66
- versionMismatch: {
67
+ // Non-fatal skew comes in two directions and each needs its own repair:
68
+ // a profile NEWER than the launcher (typical after /update) must point at
69
+ // the global install, while a profile older only in patch must point back
70
+ // at the profile. The old generic message told forward-skew users to run
71
+ // /update again — an "Already up to date" loop.
72
+ profileOutdated: {
67
73
  en: (installed, own) =>
68
74
  `[dsh-tui] note: the profile is running v${installed} but this launcher is v${own}.\n` +
69
- ` To update the profile: run /update inside the TUI, or:\n` +
70
- ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@latest`,
75
+ ` Align the profile to this launcher:\n` +
76
+ ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${own}`,
71
77
  zh: (installed, own) =>
72
- `[dsh-tui] 提示:profile 内运行的是 v${installed},而启动器是 v${own}。\n` +
73
- ` 更新 profile:在 TUI 内执行 /update,或运行:\n` +
74
- ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@latest`,
78
+ `[dsh-tui] 提示:profile 内运行的是 v${installed},启动器是 v${own}。\n` +
79
+ ` 请把 profile 对齐到启动器版本:\n` +
80
+ ` dsh plugin --profile ${PROFILE} add ${PACKAGE}@${own}`,
81
+ },
82
+ launcherOutdated: {
83
+ en: (installed, own) =>
84
+ `[dsh-tui] note: the profile is already v${installed}, but the global launcher is still v${own}.\n` +
85
+ ` Align the global dsh-tui launcher to the profile:\n` +
86
+ ` npm install -g ${PACKAGE}@${installed}\n` +
87
+ ` (if you installed it globally with pnpm: pnpm add -g ${PACKAGE}@${installed})`,
88
+ zh: (installed, own) =>
89
+ `[dsh-tui] 提示:profile 已是 v${installed},但全局启动器仍是 v${own}。\n` +
90
+ ` 请把全局 dsh-tui 启动器对齐到 profile 版本:\n` +
91
+ ` npm install -g ${PACKAGE}@${installed}\n` +
92
+ ` (如果你使用 pnpm 全局安装:pnpm add -g ${PACKAGE}@${installed})`,
75
93
  },
76
94
  // Reverse skew (issue #183): the dsh CLI reads the bundle patch from the
77
95
  // FIRST copy found from its own install anchor — this globally installed
@@ -182,7 +200,19 @@ if (installedVersion === undefined) {
182
200
  console.error(MSG.profileOlderThanLauncher[lang](installedVersion, ownVersion))
183
201
  process.exit(1)
184
202
  }
185
- console.error(MSG.versionMismatch[lang](installedVersion, ownVersion))
203
+
204
+ // Non-fatal skew: repair in the direction the versions actually moved.
205
+ // A profile NEWER than the launcher (e.g. right after /update) means the
206
+ // GLOBAL launcher is stale — never point those users back at /update.
207
+ // Exact versions only: @latest could skip past the alignment point if a
208
+ // newer release or a mirror dist-tag is in play.
209
+ const installedSemver = valid(installedVersion)
210
+ const ownSemver = valid(ownVersion)
211
+ if (installedSemver !== null && ownSemver !== null && gt(installedSemver, ownSemver)) {
212
+ console.error(MSG.launcherOutdated[lang](installedVersion, ownVersion))
213
+ } else {
214
+ console.error(MSG.profileOutdated[lang](installedVersion, ownVersion))
215
+ }
186
216
  }
187
217
 
188
218
  // --- 3. --resume 拦截 ---------------------------------------------------------
@@ -240,6 +270,12 @@ for (const oldName of detectLegacyEnv()) {
240
270
  }
241
271
 
242
272
  // --- 4. 启动 ------------------------------------------------------------------
273
+ // Contract for the profile runtime: lets /update diagnose whether the
274
+ // global dsh-tui launcher is older than the profile it just installed.
275
+ // Not a one-shot marker — it must survive restarts so the runtime always
276
+ // knows the launcher generation it boots under.
277
+ process.env.DSH_TUI_LAUNCHER_VERSION = ownVersion
278
+
243
279
  const child = spawn(...cmd('dsh', ['--profile', PROFILE, ...args]), {
244
280
  stdio: 'inherit',
245
281
  env: process.env,
@@ -0,0 +1,13 @@
1
+ import type { ApiReference, ProtocolCatalog, ProtocolDefinition } from '@dsh-std/core'
2
+ export { registerTuiContributionExtensions } from './tui-contributions.js'
3
+ export * from './tui-channel.js'
4
+
5
+ export const DECISION_EVENTS: Readonly<ApiReference & {
6
+ apiVersion: 'tui.dsh/v1alpha1'
7
+ kind: 'DecisionEvents'
8
+ }>
9
+
10
+ export const decisionEventsDefinition: ProtocolDefinition
11
+ export const profileDefinitions: readonly ProtocolDefinition[]
12
+
13
+ export function registerProfileProtocols(catalog: ProtocolCatalog): () => void
@@ -0,0 +1,59 @@
1
+ /** dsh-TUI private protocol definitions evaluated by @dsh-std/core. */
2
+
3
+ export { registerTuiContributionExtensions } from './tui-contributions.js'
4
+ export * from './tui-channel.js'
5
+ import { tuiChannelDefinition } from './tui-channel.js'
6
+
7
+ export const DECISION_EVENTS = Object.freeze({
8
+ apiVersion: 'tui.dsh/v1alpha1',
9
+ kind: 'DecisionEvents',
10
+ })
11
+
12
+ function featureSupport(value, label) {
13
+ if (value === undefined) return Object.freeze({ features: Object.freeze([]) })
14
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
15
+ throw new TypeError(`${label} support spec must be an object`)
16
+ }
17
+ const unknown = Object.keys(value).filter(key => key !== 'features' && !key.startsWith('x-'))
18
+ if (unknown.length > 0) throw new TypeError(`${label} support spec contains unknown field ${JSON.stringify(unknown[0])}`)
19
+ if (value.features !== undefined && (!Array.isArray(value.features)
20
+ || value.features.some(feature => typeof feature !== 'string' || feature.trim() === ''))) {
21
+ throw new TypeError(`${label} support spec.features must be an array of non-empty strings`)
22
+ }
23
+ return Object.freeze({ features: Object.freeze([...(value.features ?? [])].sort()) })
24
+ }
25
+
26
+ function providerDefinition(reference, label) {
27
+ return Object.freeze({
28
+ ...reference,
29
+ validateRequirement(value) {
30
+ if (value !== undefined) throw new TypeError(`${label} requirement does not accept spec in manifest v0.15`)
31
+ return undefined
32
+ },
33
+ validateSupport(value) { return featureSupport(value, label) },
34
+ negotiate(input) {
35
+ const providers = [...new Set(input.supports.map(row => row.participant))].sort()
36
+ const issues = input.requirements.flatMap(row => providers.length === 0
37
+ ? [{
38
+ code: row.requirement.optional === true ? 'optional-support-missing' : 'required-support-missing',
39
+ severity: row.requirement.optional === true ? 'warning' : 'error',
40
+ participant: row.participant,
41
+ message: `${label} has no provider in this negotiation scope`,
42
+ }]
43
+ : [])
44
+ return { agreement: Object.freeze({ providers: Object.freeze(providers) }), issues: Object.freeze(issues) }
45
+ },
46
+ })
47
+ }
48
+
49
+ export const decisionEventsDefinition = providerDefinition(DECISION_EVENTS, 'DecisionEvents')
50
+
51
+ export const profileDefinitions = Object.freeze([
52
+ decisionEventsDefinition,
53
+ tuiChannelDefinition,
54
+ ])
55
+
56
+ export function registerProfileProtocols(catalog) {
57
+ const disposers = profileDefinitions.map(definition => catalog.register(definition))
58
+ return () => { for (const dispose of disposers.reverse()) dispose() }
59
+ }
@@ -0,0 +1,15 @@
1
+ export const TUI_CHANNEL_HTTP_API_VERSION: 'tui.dsh/v1alpha1'
2
+ export const TUI_CHANNEL_HTTP_KIND: 'ChannelEndpoint'
3
+ export const TUI_CHANNEL_HTTP_HEALTH_PATH: '/dsh-tui/v1/health'
4
+ export const TUI_CHANNEL_HTTP_DESCRIPTOR_PATH: '/dsh-tui/v1/endpoint'
5
+ export const TUI_CHANNEL_HTTP_WEBSOCKET_PATH: '/dsh-tui/v1/connection'
6
+
7
+ export interface TuiChannelEndpointDescriptor {
8
+ readonly apiVersion: typeof TUI_CHANNEL_HTTP_API_VERSION
9
+ readonly kind: typeof TUI_CHANNEL_HTTP_KIND
10
+ readonly origin: string
11
+ readonly connection: typeof TUI_CHANNEL_HTTP_WEBSOCKET_PATH
12
+ }
13
+
14
+ export function createTuiChannelEndpointDescriptor(origin: string): TuiChannelEndpointDescriptor
15
+ export function validateTuiChannelEndpointDescriptor(value: unknown): TuiChannelEndpointDescriptor
@@ -0,0 +1,32 @@
1
+ export const TUI_CHANNEL_HTTP_API_VERSION = 'tui.dsh/v1alpha1'
2
+ export const TUI_CHANNEL_HTTP_KIND = 'ChannelEndpoint'
3
+ export const TUI_CHANNEL_HTTP_HEALTH_PATH = '/dsh-tui/v1/health'
4
+ export const TUI_CHANNEL_HTTP_DESCRIPTOR_PATH = '/dsh-tui/v1/endpoint'
5
+ export const TUI_CHANNEL_HTTP_WEBSOCKET_PATH = '/dsh-tui/v1/connection'
6
+
7
+ export function createTuiChannelEndpointDescriptor(origin) {
8
+ const normalized = new URL(origin)
9
+ if (normalized.protocol !== 'http:' && normalized.protocol !== 'https:') {
10
+ throw new TypeError('TUI Channel endpoint origin must use http or https')
11
+ }
12
+ normalized.pathname = '/'
13
+ normalized.search = ''
14
+ normalized.hash = ''
15
+ return Object.freeze({
16
+ apiVersion: TUI_CHANNEL_HTTP_API_VERSION,
17
+ kind: TUI_CHANNEL_HTTP_KIND,
18
+ origin: normalized.toString().replace(/\/$/u, ''),
19
+ connection: TUI_CHANNEL_HTTP_WEBSOCKET_PATH,
20
+ })
21
+ }
22
+
23
+ export function validateTuiChannelEndpointDescriptor(value) {
24
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new TypeError('TUI Channel endpoint descriptor must be an object')
25
+ if (value.apiVersion !== TUI_CHANNEL_HTTP_API_VERSION || value.kind !== TUI_CHANNEL_HTTP_KIND) {
26
+ throw new TypeError('TUI Channel endpoint descriptor version or kind is unsupported')
27
+ }
28
+ if (typeof value.origin !== 'string') throw new TypeError('TUI Channel endpoint origin must be a string')
29
+ createTuiChannelEndpointDescriptor(value.origin)
30
+ if (value.connection !== TUI_CHANNEL_HTTP_WEBSOCKET_PATH) throw new TypeError('TUI Channel endpoint connection path is unsupported')
31
+ return value
32
+ }
@@ -0,0 +1,59 @@
1
+ import type { ApiReference, ProtocolDefinition } from '@dsh-std/core'
2
+
3
+ export type JsonValue = null | boolean | number | string | readonly JsonValue[] | { readonly [key: string]: JsonValue }
4
+
5
+ export const TUI_CHANNEL: Readonly<ApiReference & {
6
+ apiVersion: 'tui.dsh/v1alpha1'
7
+ kind: 'Channel'
8
+ }>
9
+ export const TUI_CHANNEL_WIRE_REVISION: 6
10
+ export const TUI_CHANNEL_FEATURES: readonly string[]
11
+
12
+ export interface TuiChannelRequirementSpec {
13
+ readonly wireRevision: number
14
+ readonly features: readonly string[]
15
+ }
16
+ export interface TuiChannelSupportSpec extends TuiChannelRequirementSpec {}
17
+
18
+ export interface TuiChannelSnapshot {
19
+ readonly wireRevision: number
20
+ readonly channelId: string
21
+ readonly version: number
22
+ readonly state: Readonly<Record<string, JsonValue>>
23
+ }
24
+
25
+ export interface TuiChannelWorkspaceFlowChoice {
26
+ readonly id: string
27
+ readonly label: string
28
+ readonly description?: string
29
+ readonly badge?: string
30
+ readonly action: string
31
+ readonly input?: {
32
+ readonly action: string
33
+ readonly initialValue?: string
34
+ readonly placeholder?: string
35
+ }
36
+ }
37
+
38
+ export type TuiChannelWorkspaceFlowResult =
39
+ | { readonly kind: 'target'; readonly target: import('./tui-contributions.js').WorkspaceTarget }
40
+ | { readonly kind: 'choices'; readonly title: string; readonly choices: readonly TuiChannelWorkspaceFlowChoice[] }
41
+
42
+ export type TuiChannelInput =
43
+ | { readonly operation: 'open'; readonly value: { readonly workspace?: string; readonly sessionId?: string; readonly options?: JsonValue } }
44
+ | { readonly operation: 'subscribe'; readonly value: { readonly channelId: string; readonly afterVersion: number } }
45
+ | { readonly operation: 'invoke'; readonly value: { readonly channelId: string; readonly method: string; readonly arguments: readonly JsonValue[] } }
46
+ | { readonly operation: 'close'; readonly value: { readonly channelId: string } }
47
+
48
+ export interface TuiChannelInvokeOutput {
49
+ readonly value: JsonValue
50
+ readonly valueDefined?: boolean
51
+ readonly snapshot?: TuiChannelSnapshot
52
+ }
53
+
54
+ export const tuiChannelDefinition: ProtocolDefinition
55
+ export function validateTuiChannelRequirement(value: unknown): TuiChannelRequirementSpec
56
+ export function validateTuiChannelSupport(value: unknown): TuiChannelSupportSpec
57
+ export function validateTuiChannelInput(operation: string, value: unknown): object
58
+ export function validateTuiChannelSnapshot(value: unknown): TuiChannelSnapshot
59
+ export function validateTuiChannelOutput(operation: string, value: unknown): object
@@ -0,0 +1,166 @@
1
+ import { defineCapabilityProtocol } from '@dsh-std/connection'
2
+
3
+ export const TUI_CHANNEL = Object.freeze({
4
+ apiVersion: 'tui.dsh/v1alpha1',
5
+ kind: 'Channel',
6
+ })
7
+
8
+ export const TUI_CHANNEL_WIRE_REVISION = 6
9
+
10
+ export const TUI_CHANNEL_FEATURES = Object.freeze([
11
+ 'commands',
12
+ 'credentials',
13
+ 'diagnostics',
14
+ 'files',
15
+ 'models',
16
+ 'modes',
17
+ 'presets',
18
+ 'presentation',
19
+ 'provider-setup',
20
+ 'scenes',
21
+ 'session-history',
22
+ 'session-input',
23
+ 'session-lifecycle',
24
+ 'session-state',
25
+ 'settings',
26
+ 'skills',
27
+ 'subagents',
28
+ 'trace',
29
+ 'workspaces',
30
+ ])
31
+
32
+ export const tuiChannelDefinition = defineCapabilityProtocol({
33
+ ...TUI_CHANNEL,
34
+ validateRequirement: validateTuiChannelRequirement,
35
+ validateSupport: validateTuiChannelSupport,
36
+ })
37
+
38
+ export function validateTuiChannelRequirement(value) {
39
+ const spec = exactRecord(value, ['wireRevision', 'features'], 'Channel requirement')
40
+ const wireRevision = positiveInteger(spec.wireRevision, 'Channel requirement.wireRevision')
41
+ const features = featureList(spec.features, 'Channel requirement.features')
42
+ return Object.freeze({ wireRevision, features })
43
+ }
44
+
45
+ export function validateTuiChannelSupport(value) {
46
+ const spec = exactRecord(value, ['wireRevision', 'features'], 'Channel support')
47
+ const wireRevision = positiveInteger(spec.wireRevision, 'Channel support.wireRevision')
48
+ const features = featureList(spec.features, 'Channel support.features')
49
+ return Object.freeze({ wireRevision, features })
50
+ }
51
+
52
+ export function validateTuiChannelInput(operation, value) {
53
+ switch (operation) {
54
+ case 'open': {
55
+ const input = exactRecord(value, ['workspace', 'sessionId', 'options'], 'Channel.open input')
56
+ optionalString(input.workspace, 'Channel.open input.workspace')
57
+ optionalString(input.sessionId, 'Channel.open input.sessionId')
58
+ if (input.options !== undefined) jsonValue(input.options, 'Channel.open input.options', new Set())
59
+ return input
60
+ }
61
+ case 'subscribe': {
62
+ const input = exactRecord(value, ['channelId', 'afterVersion'], 'Channel.subscribe input')
63
+ nonEmpty(input.channelId, 'Channel.subscribe input.channelId')
64
+ nonNegativeInteger(input.afterVersion, 'Channel.subscribe input.afterVersion')
65
+ return input
66
+ }
67
+ case 'invoke': {
68
+ const input = exactRecord(value, ['channelId', 'method', 'arguments'], 'Channel.invoke input')
69
+ nonEmpty(input.channelId, 'Channel.invoke input.channelId')
70
+ nonEmpty(input.method, 'Channel.invoke input.method')
71
+ if (!Array.isArray(input.arguments)) throw new TypeError('Channel.invoke input.arguments must be an array')
72
+ jsonValue(input.arguments, 'Channel.invoke input.arguments', new Set())
73
+ return input
74
+ }
75
+ case 'close': {
76
+ const input = exactRecord(value, ['channelId'], 'Channel.close input')
77
+ nonEmpty(input.channelId, 'Channel.close input.channelId')
78
+ return input
79
+ }
80
+ default:
81
+ throw new TypeError(`Channel operation ${JSON.stringify(operation)} is not defined`)
82
+ }
83
+ }
84
+
85
+ export function validateTuiChannelSnapshot(value) {
86
+ const snapshot = exactRecord(value, ['wireRevision', 'channelId', 'version', 'state'], 'Channel snapshot')
87
+ positiveInteger(snapshot.wireRevision, 'Channel snapshot.wireRevision')
88
+ nonEmpty(snapshot.channelId, 'Channel snapshot.channelId')
89
+ nonNegativeInteger(snapshot.version, 'Channel snapshot.version')
90
+ const state = exactRecord(snapshot.state, Object.keys(snapshot.state ?? {}), 'Channel snapshot.state')
91
+ jsonValue(state, 'Channel snapshot.state', new Set())
92
+ return snapshot
93
+ }
94
+
95
+ export function validateTuiChannelOutput(operation, value) {
96
+ if (operation === 'open' || operation === 'subscribe') return validateTuiChannelSnapshot(value)
97
+ if (operation === 'invoke') {
98
+ const output = exactRecord(value, ['value', 'valueDefined', 'snapshot'], 'Channel.invoke output')
99
+ jsonValue(output.value, 'Channel.invoke output.value', new Set())
100
+ if (output.valueDefined !== undefined && typeof output.valueDefined !== 'boolean') {
101
+ throw new TypeError('Channel.invoke output.valueDefined must be boolean')
102
+ }
103
+ if (output.snapshot !== undefined) validateTuiChannelSnapshot(output.snapshot)
104
+ return output
105
+ }
106
+ if (operation === 'close') {
107
+ const output = exactRecord(value, ['closed'], 'Channel.close output')
108
+ if (output.closed !== true) throw new TypeError('Channel.close output.closed must be true')
109
+ return output
110
+ }
111
+ throw new TypeError(`Channel operation ${JSON.stringify(operation)} is not defined`)
112
+ }
113
+
114
+ function exactRecord(value, allowed, label) {
115
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) throw new TypeError(`${label} must be an object`)
116
+ const unknown = Object.keys(value).filter(key => !allowed.includes(key) && !key.startsWith('x-'))
117
+ if (unknown.length > 0) throw new TypeError(`${label} contains unknown field ${JSON.stringify(unknown[0])}`)
118
+ return value
119
+ }
120
+
121
+ function featureList(value, label) {
122
+ if (!Array.isArray(value) || value.length === 0) throw new TypeError(`${label} must be a non-empty array`)
123
+ const features = value.map((feature, index) => {
124
+ nonEmpty(feature, `${label}[${index}]`)
125
+ return feature
126
+ })
127
+ if (new Set(features).size !== features.length) throw new TypeError(`${label} contains duplicates`)
128
+ return Object.freeze([...features].sort())
129
+ }
130
+
131
+ function positiveInteger(value, label) {
132
+ if (!Number.isSafeInteger(value) || value < 1) throw new TypeError(`${label} must be a positive integer`)
133
+ return value
134
+ }
135
+
136
+ function nonNegativeInteger(value, label) {
137
+ if (!Number.isSafeInteger(value) || value < 0) throw new TypeError(`${label} must be a non-negative integer`)
138
+ return value
139
+ }
140
+
141
+ function nonEmpty(value, label) {
142
+ if (typeof value !== 'string' || value.trim() === '') throw new TypeError(`${label} must be a non-empty string`)
143
+ }
144
+
145
+ function optionalString(value, label) {
146
+ if (value !== undefined) nonEmpty(value, label)
147
+ }
148
+
149
+ function jsonValue(value, label, ancestors) {
150
+ if (value === null || typeof value === 'boolean' || typeof value === 'string') return
151
+ if (typeof value === 'number') {
152
+ if (!Number.isFinite(value)) throw new TypeError(`${label} must contain only finite numbers`)
153
+ return
154
+ }
155
+ if (typeof value !== 'object') throw new TypeError(`${label} must be a JSON value`)
156
+ if (ancestors.has(value)) throw new TypeError(`${label} must not contain cycles`)
157
+ ancestors.add(value)
158
+ if (Array.isArray(value)) {
159
+ for (const [index, item] of value.entries()) jsonValue(item, `${label}[${index}]`, ancestors)
160
+ } else {
161
+ const prototype = Object.getPrototypeOf(value)
162
+ if (prototype !== Object.prototype && prototype !== null) throw new TypeError(`${label} must contain only plain objects`)
163
+ for (const [key, item] of Object.entries(value)) jsonValue(item, `${label}.${key}`, ancestors)
164
+ }
165
+ ancestors.delete(value)
166
+ }
@@ -0,0 +1,42 @@
1
+ import type { ApiReference } from '@dsh-std/core'
2
+ import type { ManifestDefinitionCatalog, ManifestObjectDefinition } from '@dsh-std/manifest'
3
+
4
+ export const API_VERSION: 'tui.dsh/v1alpha1'
5
+ export const SETTINGS_SECTION: Readonly<ApiReference & { kind: 'SettingsSection' }>
6
+ export const SCENE: Readonly<ApiReference & { kind: 'Scene' }>
7
+
8
+ export type LocalizedText = Readonly<Record<string, string>>
9
+ export type SettingsFieldKind = 'text' | 'number' | 'boolean' | 'select'
10
+ export interface SettingsFieldOption { readonly value: string; readonly label: string; readonly titles?: LocalizedText }
11
+ export interface SettingsFieldSpec {
12
+ readonly path: readonly string[]
13
+ readonly label: string
14
+ readonly titles?: LocalizedText
15
+ readonly hint?: string
16
+ readonly hintTitles?: LocalizedText
17
+ readonly kind: SettingsFieldKind
18
+ readonly options?: readonly SettingsFieldOption[]
19
+ readonly placeholder?: string
20
+ readonly secretRef?: string
21
+ }
22
+ export interface SettingsSectionSpec {
23
+ readonly namespace: string
24
+ readonly title: string
25
+ readonly titles?: LocalizedText
26
+ readonly fields: readonly SettingsFieldSpec[]
27
+ }
28
+ export interface SettingsSectionHandler {}
29
+
30
+ export interface SceneSpec { readonly title?: string; readonly titles?: LocalizedText }
31
+ export interface SceneHandler<Props = unknown, Result = unknown> {
32
+ readonly component: (props: Props) => Result
33
+ }
34
+
35
+ export const settingsSectionExtensionDefinition: ManifestObjectDefinition
36
+ export const sceneExtensionDefinition: ManifestObjectDefinition
37
+ export const contributionExtensionDefinitions: readonly ManifestObjectDefinition[]
38
+ export function validateSettingsSectionSpec(value: unknown): Readonly<SettingsSectionSpec>
39
+ export function validateSceneSpec(value: unknown): Readonly<SceneSpec>
40
+ export function registerTuiContributionExtensions(catalog: ManifestDefinitionCatalog): () => void
41
+ export function assertSettingsSectionHandler(value: unknown): asserts value is SettingsSectionHandler
42
+ export function assertSceneHandler(value: unknown): asserts value is SceneHandler