@jingyi0605/codingns4dsh 0.2.0-rc3 → 0.2.1-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (377) hide show
  1. package/README.en.md +6 -6
  2. package/README.md +42 -29
  3. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  4. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts.map +1 -1
  5. package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js +2 -2
  6. package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js.map +1 -1
  7. package/data/build/dist/bootstrap/index.d.ts +1 -1
  8. package/data/build/dist/bootstrap/index.d.ts.map +1 -1
  9. package/data/build/dist/client/bundle.js +3164 -181
  10. package/data/build/dist/client/bundle.js.map +1 -1
  11. package/data/build/dist/client/cli-catalog.d.ts +1 -1
  12. package/data/build/dist/client/cli-catalog.d.ts.map +1 -1
  13. package/data/build/dist/client/cli-catalog.js +3 -3
  14. package/data/build/dist/client/cli-catalog.js.map +1 -1
  15. package/data/build/dist/client/cli-slots.d.ts +4 -0
  16. package/data/build/dist/client/cli-slots.d.ts.map +1 -1
  17. package/data/build/dist/client/cli-slots.js +70 -9
  18. package/data/build/dist/client/cli-slots.js.map +1 -1
  19. package/data/build/dist/client/context-breakdown-dom.d.ts +1 -1
  20. package/data/build/dist/client/context-breakdown-dom.d.ts.map +1 -1
  21. package/data/build/dist/client/context-breakdown-dom.js +1 -1
  22. package/data/build/dist/client/context-breakdown-dom.js.map +1 -1
  23. package/data/build/dist/client/features/assistant-panel.d.ts +5 -0
  24. package/data/build/dist/client/features/assistant-panel.d.ts.map +1 -0
  25. package/data/build/dist/client/features/assistant-panel.js +127 -0
  26. package/data/build/dist/client/features/assistant-panel.js.map +1 -0
  27. package/data/build/dist/client/features/global-voice-assistant.d.ts +10 -0
  28. package/data/build/dist/client/features/global-voice-assistant.d.ts.map +1 -0
  29. package/data/build/dist/client/features/global-voice-assistant.js +330 -0
  30. package/data/build/dist/client/features/global-voice-assistant.js.map +1 -0
  31. package/data/build/dist/client/features/index.d.ts +2 -0
  32. package/data/build/dist/client/features/index.d.ts.map +1 -1
  33. package/data/build/dist/client/features/index.js +4 -0
  34. package/data/build/dist/client/features/index.js.map +1 -1
  35. package/data/build/dist/client/features/lan-access.d.ts.map +1 -1
  36. package/data/build/dist/client/features/lan-access.js +3 -1
  37. package/data/build/dist/client/features/lan-access.js.map +1 -1
  38. package/data/build/dist/client/features/types.d.ts +4 -1
  39. package/data/build/dist/client/features/types.d.ts.map +1 -1
  40. package/data/build/dist/client/features/voice-conversation-dialog.d.ts +20 -0
  41. package/data/build/dist/client/features/voice-conversation-dialog.d.ts.map +1 -0
  42. package/data/build/dist/client/features/voice-conversation-dialog.js +59 -0
  43. package/data/build/dist/client/features/voice-conversation-dialog.js.map +1 -0
  44. package/data/build/dist/client/features/voice-initialization-dialog.d.ts +10 -0
  45. package/data/build/dist/client/features/voice-initialization-dialog.d.ts.map +1 -0
  46. package/data/build/dist/client/features/voice-initialization-dialog.js +90 -0
  47. package/data/build/dist/client/features/voice-initialization-dialog.js.map +1 -0
  48. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts.map +1 -1
  49. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +57 -2
  50. package/data/build/dist/client/features/workspace-session-enhancement-panel.js.map +1 -1
  51. package/data/build/dist/client/features/workspace-session-enhancement.d.ts.map +1 -1
  52. package/data/build/dist/client/features/workspace-session-enhancement.js +27 -0
  53. package/data/build/dist/client/features/workspace-session-enhancement.js.map +1 -1
  54. package/data/build/dist/client/global-voice-runtime-registry.d.ts +27 -0
  55. package/data/build/dist/client/global-voice-runtime-registry.d.ts.map +1 -0
  56. package/data/build/dist/client/global-voice-runtime-registry.js +12 -0
  57. package/data/build/dist/client/global-voice-runtime-registry.js.map +1 -0
  58. package/data/build/dist/client/index.d.ts +1 -1
  59. package/data/build/dist/client/index.d.ts.map +1 -1
  60. package/data/build/dist/client/locale.d.ts.map +1 -1
  61. package/data/build/dist/client/locale.js +185 -1
  62. package/data/build/dist/client/locale.js.map +1 -1
  63. package/data/build/dist/client/locales/terminalAuth.d.ts.map +1 -1
  64. package/data/build/dist/client/locales/terminalAuth.js +2 -0
  65. package/data/build/dist/client/locales/terminalAuth.js.map +1 -1
  66. package/data/build/dist/client/locales/usage.d.ts.map +1 -1
  67. package/data/build/dist/client/locales/usage.js +4 -0
  68. package/data/build/dist/client/locales/usage.js.map +1 -1
  69. package/data/build/dist/client/mobile-session-interaction.d.ts +9 -0
  70. package/data/build/dist/client/mobile-session-interaction.d.ts.map +1 -1
  71. package/data/build/dist/client/mobile-session-interaction.js +63 -0
  72. package/data/build/dist/client/mobile-session-interaction.js.map +1 -1
  73. package/data/build/dist/client/model-catalog-cache.d.ts +23 -0
  74. package/data/build/dist/client/model-catalog-cache.d.ts.map +1 -1
  75. package/data/build/dist/client/model-catalog-cache.js +81 -9
  76. package/data/build/dist/client/model-catalog-cache.js.map +1 -1
  77. package/data/build/dist/client/service-tier.d.ts +11 -0
  78. package/data/build/dist/client/service-tier.d.ts.map +1 -1
  79. package/data/build/dist/client/service-tier.js +15 -0
  80. package/data/build/dist/client/service-tier.js.map +1 -1
  81. package/data/build/dist/client/sherpa-voice-adapter.d.ts +46 -0
  82. package/data/build/dist/client/sherpa-voice-adapter.d.ts.map +1 -0
  83. package/data/build/dist/client/sherpa-voice-adapter.js +252 -0
  84. package/data/build/dist/client/sherpa-voice-adapter.js.map +1 -0
  85. package/data/build/dist/client/skill-command.d.ts +10 -0
  86. package/data/build/dist/client/skill-command.d.ts.map +1 -0
  87. package/data/build/dist/client/skill-command.js +258 -0
  88. package/data/build/dist/client/skill-command.js.map +1 -0
  89. package/data/build/dist/client/skill-reference-dom.d.ts +38 -0
  90. package/data/build/dist/client/skill-reference-dom.d.ts.map +1 -0
  91. package/data/build/dist/client/skill-reference-dom.js +187 -0
  92. package/data/build/dist/client/skill-reference-dom.js.map +1 -0
  93. package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
  94. package/data/build/dist/client/subscription-slot.js +51 -3
  95. package/data/build/dist/client/subscription-slot.js.map +1 -1
  96. package/data/build/dist/client/terminal/model.d.ts +40 -2
  97. package/data/build/dist/client/terminal/model.d.ts.map +1 -1
  98. package/data/build/dist/client/terminal/model.js +151 -24
  99. package/data/build/dist/client/terminal/model.js.map +1 -1
  100. package/data/build/dist/client/terminal/styles.d.ts +8 -0
  101. package/data/build/dist/client/terminal/styles.d.ts.map +1 -1
  102. package/data/build/dist/client/terminal/styles.js +23 -2
  103. package/data/build/dist/client/terminal/styles.js.map +1 -1
  104. package/data/build/dist/client/terminal/ui.d.ts.map +1 -1
  105. package/data/build/dist/client/terminal/ui.js +161 -26
  106. package/data/build/dist/client/terminal/ui.js.map +1 -1
  107. package/data/build/dist/client/terminal/xterm-view.d.ts.map +1 -1
  108. package/data/build/dist/client/terminal/xterm-view.js +218 -42
  109. package/data/build/dist/client/terminal/xterm-view.js.map +1 -1
  110. package/data/build/dist/client/voice-capture.d.ts +32 -0
  111. package/data/build/dist/client/voice-capture.d.ts.map +1 -0
  112. package/data/build/dist/client/voice-capture.js +158 -0
  113. package/data/build/dist/client/voice-capture.js.map +1 -0
  114. package/data/build/dist/client/voice-device-manager.d.ts +24 -0
  115. package/data/build/dist/client/voice-device-manager.d.ts.map +1 -0
  116. package/data/build/dist/client/voice-device-manager.js +197 -0
  117. package/data/build/dist/client/voice-device-manager.js.map +1 -0
  118. package/data/build/dist/client/voice-output.d.ts +14 -0
  119. package/data/build/dist/client/voice-output.d.ts.map +1 -0
  120. package/data/build/dist/client/voice-output.js +32 -0
  121. package/data/build/dist/client/voice-output.js.map +1 -0
  122. package/data/build/dist/client/voice-security.d.ts +8 -0
  123. package/data/build/dist/client/voice-security.d.ts.map +1 -0
  124. package/data/build/dist/client/voice-security.js +26 -0
  125. package/data/build/dist/client/voice-security.js.map +1 -0
  126. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +12 -1
  127. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts.map +1 -1
  128. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +32 -0
  129. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +15 -0
  131. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -1
  132. package/data/build/dist/host/cli-adapters/acp-cli-driver.js +39 -9
  133. package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +39 -2
  135. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -1
  136. package/data/build/dist/host/cli-adapters/antigravity-driver.js +198 -10
  137. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  138. package/data/build/dist/host/cli-adapters/antigravity-subscription.d.ts +121 -0
  139. package/data/build/dist/host/cli-adapters/antigravity-subscription.d.ts.map +1 -0
  140. package/data/build/dist/host/cli-adapters/antigravity-subscription.js +504 -0
  141. package/data/build/dist/host/cli-adapters/antigravity-subscription.js.map +1 -0
  142. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +21 -0
  143. package/data/build/dist/host/cli-adapters/claude-driver.d.ts.map +1 -1
  144. package/data/build/dist/host/cli-adapters/claude-driver.js +173 -7
  145. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  146. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -1
  147. package/data/build/dist/host/cli-adapters/codebuddy-driver.js +4 -0
  148. package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -1
  149. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +28 -2
  150. package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
  151. package/data/build/dist/host/cli-adapters/codex-driver.js +234 -5
  152. package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
  153. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
  154. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  155. package/data/build/dist/host/cli-adapters/command-code-subscription.js +7 -4
  156. package/data/build/dist/host/cli-adapters/command-code-subscription.js.map +1 -1
  157. package/data/build/dist/host/cli-adapters/cursor-driver.d.ts +9 -0
  158. package/data/build/dist/host/cli-adapters/cursor-driver.d.ts.map +1 -1
  159. package/data/build/dist/host/cli-adapters/cursor-driver.js +72 -2
  160. package/data/build/dist/host/cli-adapters/cursor-driver.js.map +1 -1
  161. package/data/build/dist/host/cli-adapters/driver.d.ts +21 -1
  162. package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
  163. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
  164. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +15 -3
  165. package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
  166. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  167. package/data/build/dist/host/cli-adapters/feature.js +18 -1
  168. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  169. package/data/build/dist/host/cli-adapters/gemini-driver.d.ts.map +1 -1
  170. package/data/build/dist/host/cli-adapters/gemini-driver.js +4 -0
  171. package/data/build/dist/host/cli-adapters/gemini-driver.js.map +1 -1
  172. package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
  173. package/data/build/dist/host/cli-adapters/grok-driver.js +4 -0
  174. package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
  175. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts.map +1 -1
  176. package/data/build/dist/host/cli-adapters/json-rpc-process.js.map +1 -1
  177. package/data/build/dist/host/cli-adapters/kimi-driver.d.ts.map +1 -1
  178. package/data/build/dist/host/cli-adapters/kimi-driver.js +6 -6
  179. package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
  180. package/data/build/dist/host/cli-adapters/mcode-driver.d.ts.map +1 -1
  181. package/data/build/dist/host/cli-adapters/mcode-driver.js +2 -0
  182. package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
  183. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +49 -0
  184. package/data/build/dist/host/cli-adapters/model-catalog.d.ts.map +1 -1
  185. package/data/build/dist/host/cli-adapters/model-catalog.js +126 -0
  186. package/data/build/dist/host/cli-adapters/model-catalog.js.map +1 -1
  187. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +11 -0
  188. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
  189. package/data/build/dist/host/cli-adapters/opencode-driver.js +92 -8
  190. package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
  191. package/data/build/dist/host/cli-adapters/pi-driver.d.ts.map +1 -1
  192. package/data/build/dist/host/cli-adapters/pi-driver.js +4 -0
  193. package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
  194. package/data/build/dist/host/cli-adapters/process-utils.d.ts +10 -3
  195. package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
  196. package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
  197. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +4 -1
  198. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
  199. package/data/build/dist/host/cli-adapters/provider-subscription.js +8 -1
  200. package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
  201. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts +15 -0
  202. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts.map +1 -1
  203. package/data/build/dist/host/cli-adapters/qoder-driver.js +171 -3
  204. package/data/build/dist/host/cli-adapters/qoder-driver.js.map +1 -1
  205. package/data/build/dist/host/cli-adapters/qoder-subscription.js +3 -1
  206. package/data/build/dist/host/cli-adapters/qoder-subscription.js.map +1 -1
  207. package/data/build/dist/host/cli-adapters/reasoning-content.d.ts +11 -0
  208. package/data/build/dist/host/cli-adapters/reasoning-content.d.ts.map +1 -0
  209. package/data/build/dist/host/cli-adapters/reasoning-content.js +79 -0
  210. package/data/build/dist/host/cli-adapters/reasoning-content.js.map +1 -0
  211. package/data/build/dist/host/cli-adapters/registry.d.ts +24 -1
  212. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  213. package/data/build/dist/host/cli-adapters/registry.js +109 -4
  214. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  215. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts +3 -2
  216. package/data/build/dist/host/cli-adapters/rpc-driver-utils.d.ts.map +1 -1
  217. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +8 -2
  218. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js.map +1 -1
  219. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -2
  220. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
  221. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +14 -2
  222. package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
  223. package/data/build/dist/host/cli-bridge/injections.d.ts +10 -1
  224. package/data/build/dist/host/cli-bridge/injections.d.ts.map +1 -1
  225. package/data/build/dist/host/cli-bridge/injections.js +28 -8
  226. package/data/build/dist/host/cli-bridge/injections.js.map +1 -1
  227. package/data/build/dist/host/features/assistant-dispatch.d.ts +45 -0
  228. package/data/build/dist/host/features/assistant-dispatch.d.ts.map +1 -0
  229. package/data/build/dist/host/features/assistant-dispatch.js +49 -0
  230. package/data/build/dist/host/features/assistant-dispatch.js.map +1 -0
  231. package/data/build/dist/host/features/assistant-intent.d.ts +46 -0
  232. package/data/build/dist/host/features/assistant-intent.d.ts.map +1 -0
  233. package/data/build/dist/host/features/assistant-intent.js +149 -0
  234. package/data/build/dist/host/features/assistant-intent.js.map +1 -0
  235. package/data/build/dist/host/features/assistant-scope-settings.d.ts +25 -0
  236. package/data/build/dist/host/features/assistant-scope-settings.d.ts.map +1 -0
  237. package/data/build/dist/host/features/assistant-scope-settings.js +33 -0
  238. package/data/build/dist/host/features/assistant-scope-settings.js.map +1 -0
  239. package/data/build/dist/host/features/assistant-scope.d.ts +79 -0
  240. package/data/build/dist/host/features/assistant-scope.d.ts.map +1 -0
  241. package/data/build/dist/host/features/assistant-scope.js +107 -0
  242. package/data/build/dist/host/features/assistant-scope.js.map +1 -0
  243. package/data/build/dist/host/features/assistant-session-index.d.ts +44 -0
  244. package/data/build/dist/host/features/assistant-session-index.d.ts.map +1 -0
  245. package/data/build/dist/host/features/assistant-session-index.js +109 -0
  246. package/data/build/dist/host/features/assistant-session-index.js.map +1 -0
  247. package/data/build/dist/host/features/assistant-summary.d.ts +28 -0
  248. package/data/build/dist/host/features/assistant-summary.d.ts.map +1 -0
  249. package/data/build/dist/host/features/assistant-summary.js +166 -0
  250. package/data/build/dist/host/features/assistant-summary.js.map +1 -0
  251. package/data/build/dist/host/features/assistant-voice-stream.d.ts +10 -0
  252. package/data/build/dist/host/features/assistant-voice-stream.d.ts.map +1 -0
  253. package/data/build/dist/host/features/assistant-voice-stream.js +106 -0
  254. package/data/build/dist/host/features/assistant-voice-stream.js.map +1 -0
  255. package/data/build/dist/host/features/assistant-voice-turn.d.ts +27 -0
  256. package/data/build/dist/host/features/assistant-voice-turn.d.ts.map +1 -0
  257. package/data/build/dist/host/features/assistant-voice-turn.js +54 -0
  258. package/data/build/dist/host/features/assistant-voice-turn.js.map +1 -0
  259. package/data/build/dist/host/features/assistant-waiting-state.d.ts +14 -0
  260. package/data/build/dist/host/features/assistant-waiting-state.d.ts.map +1 -0
  261. package/data/build/dist/host/features/assistant-waiting-state.js +17 -0
  262. package/data/build/dist/host/features/assistant-waiting-state.js.map +1 -0
  263. package/data/build/dist/host/features/global-voice-coordinator.d.ts +55 -0
  264. package/data/build/dist/host/features/global-voice-coordinator.d.ts.map +1 -0
  265. package/data/build/dist/host/features/global-voice-coordinator.js +221 -0
  266. package/data/build/dist/host/features/global-voice-coordinator.js.map +1 -0
  267. package/data/build/dist/host/features/global-voice-rpc.d.ts +25 -0
  268. package/data/build/dist/host/features/global-voice-rpc.d.ts.map +1 -0
  269. package/data/build/dist/host/features/global-voice-rpc.js +862 -0
  270. package/data/build/dist/host/features/global-voice-rpc.js.map +1 -0
  271. package/data/build/dist/host/features/index.d.ts +10 -0
  272. package/data/build/dist/host/features/index.d.ts.map +1 -1
  273. package/data/build/dist/host/features/index.js +10 -0
  274. package/data/build/dist/host/features/index.js.map +1 -1
  275. package/data/build/dist/host/features/peer-host.d.ts +5 -0
  276. package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
  277. package/data/build/dist/host/features/peer-host.js +232 -2
  278. package/data/build/dist/host/features/peer-host.js.map +1 -1
  279. package/data/build/dist/host/features/sherpa-voice-runtime.d.ts +42 -0
  280. package/data/build/dist/host/features/sherpa-voice-runtime.d.ts.map +1 -0
  281. package/data/build/dist/host/features/sherpa-voice-runtime.js +193 -0
  282. package/data/build/dist/host/features/sherpa-voice-runtime.js.map +1 -0
  283. package/data/build/dist/host/features/types.d.ts +26 -0
  284. package/data/build/dist/host/features/types.d.ts.map +1 -1
  285. package/data/build/dist/host/features/voice-agent-actions.d.ts +26 -0
  286. package/data/build/dist/host/features/voice-agent-actions.d.ts.map +1 -0
  287. package/data/build/dist/host/features/voice-agent-actions.js +128 -0
  288. package/data/build/dist/host/features/voice-agent-actions.js.map +1 -0
  289. package/data/build/dist/host/features/voice-agent-service.d.ts +117 -0
  290. package/data/build/dist/host/features/voice-agent-service.d.ts.map +1 -0
  291. package/data/build/dist/host/features/voice-agent-service.js +243 -0
  292. package/data/build/dist/host/features/voice-agent-service.js.map +1 -0
  293. package/data/build/dist/host/features/voice-model-setup.d.ts +20 -0
  294. package/data/build/dist/host/features/voice-model-setup.d.ts.map +1 -0
  295. package/data/build/dist/host/features/voice-model-setup.js +89 -0
  296. package/data/build/dist/host/features/voice-model-setup.js.map +1 -0
  297. package/data/build/dist/host/index.d.ts +2 -2
  298. package/data/build/dist/host/index.d.ts.map +1 -1
  299. package/data/build/dist/host/index.js +7 -1
  300. package/data/build/dist/host/index.js.map +1 -1
  301. package/data/build/dist/host/lan-access-dsh.js.map +1 -1
  302. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.d.ts.map +1 -1
  303. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js +53 -2
  304. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js.map +1 -1
  305. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js +15 -1
  306. package/data/build/dist/host/modules/peer-host/peer-host-remote-summary-source.js.map +1 -1
  307. package/data/build/dist/host/native-session-bridge.d.ts +2 -0
  308. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  309. package/data/build/dist/host/native-session-bridge.js +1 -0
  310. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  311. package/data/build/dist/host/rpc.d.ts.map +1 -1
  312. package/data/build/dist/host/rpc.js +9 -4
  313. package/data/build/dist/host/rpc.js.map +1 -1
  314. package/data/build/dist/host/settings.d.ts.map +1 -1
  315. package/data/build/dist/host/settings.js +19 -1
  316. package/data/build/dist/host/settings.js.map +1 -1
  317. package/data/build/dist/host/terminal/backends/conpty-backend.js.map +1 -1
  318. package/data/build/dist/host/terminal/terminal-controller.d.ts.map +1 -1
  319. package/data/build/dist/host/terminal/terminal-controller.js +4 -0
  320. package/data/build/dist/host/terminal/terminal-controller.js.map +1 -1
  321. package/data/build/dist/host/terminal/terminal-service.d.ts +21 -1
  322. package/data/build/dist/host/terminal/terminal-service.d.ts.map +1 -1
  323. package/data/build/dist/host/terminal/terminal-service.js +118 -7
  324. package/data/build/dist/host/terminal/terminal-service.js.map +1 -1
  325. package/data/build/dist/index.d.ts +2 -2
  326. package/data/build/dist/index.d.ts.map +1 -1
  327. package/data/build/dist/index.js +1 -1
  328. package/data/build/dist/index.js.map +1 -1
  329. package/data/build/dist/shared/contracts/assistant.d.ts +70 -0
  330. package/data/build/dist/shared/contracts/assistant.d.ts.map +1 -0
  331. package/data/build/dist/shared/contracts/assistant.js +2 -0
  332. package/data/build/dist/shared/contracts/assistant.js.map +1 -0
  333. package/data/build/dist/shared/contracts/cli-adapter.d.ts +33 -1
  334. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  335. package/data/build/dist/shared/contracts/config.d.ts +30 -0
  336. package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
  337. package/data/build/dist/shared/contracts/config.js +20 -0
  338. package/data/build/dist/shared/contracts/config.js.map +1 -1
  339. package/data/build/dist/shared/contracts/errors.d.ts +2 -2
  340. package/data/build/dist/shared/contracts/errors.d.ts.map +1 -1
  341. package/data/build/dist/shared/contracts/subscription.d.ts +30 -0
  342. package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
  343. package/data/build/dist/shared/contracts/subscription.js.map +1 -1
  344. package/data/build/dist/shared/contracts/terminal.d.ts +2 -0
  345. package/data/build/dist/shared/contracts/terminal.d.ts.map +1 -1
  346. package/data/build/dist/shared/contracts/version.d.ts +12 -5
  347. package/data/build/dist/shared/contracts/version.d.ts.map +1 -1
  348. package/data/build/dist/shared/contracts/version.js +11 -4
  349. package/data/build/dist/shared/contracts/version.js.map +1 -1
  350. package/data/build/dist/shared/contracts/voice-runtime.d.ts +90 -0
  351. package/data/build/dist/shared/contracts/voice-runtime.d.ts.map +1 -0
  352. package/data/build/dist/shared/contracts/voice-runtime.js +16 -0
  353. package/data/build/dist/shared/contracts/voice-runtime.js.map +1 -0
  354. package/data/build/dist/shared/index.d.ts +8 -2
  355. package/data/build/dist/shared/index.d.ts.map +1 -1
  356. package/data/build/dist/shared/index.js +5 -1
  357. package/data/build/dist/shared/index.js.map +1 -1
  358. package/data/build/dist/shared/voice-models.d.ts +21 -0
  359. package/data/build/dist/shared/voice-models.d.ts.map +1 -0
  360. package/data/build/dist/shared/voice-models.js +27 -0
  361. package/data/build/dist/shared/voice-models.js.map +1 -0
  362. package/data/build/dist/shared/voice-stream.d.ts +42 -0
  363. package/data/build/dist/shared/voice-stream.d.ts.map +1 -0
  364. package/data/build/dist/shared/voice-stream.js +99 -0
  365. package/data/build/dist/shared/voice-stream.js.map +1 -0
  366. package/data/build/dist/shared/voice-text.d.ts +3 -0
  367. package/data/build/dist/shared/voice-text.d.ts.map +1 -0
  368. package/data/build/dist/shared/voice-text.js +14 -0
  369. package/data/build/dist/shared/voice-text.js.map +1 -0
  370. package/data/build/dist/typert.host.d.ts.map +1 -1
  371. package/data/build/dist/typert.host.js +1 -0
  372. package/data/build/dist/typert.host.js.map +1 -1
  373. package/package.json +56 -26
  374. package/profile/README.md +8 -5
  375. package/profile/package.json +5 -3
  376. package/profile/version.json +3 -3
  377. package/version.json +3 -3
@@ -877,6 +877,7 @@ window.__ModuleLoader__.load({
877
877
  "terminalView.readOnly": "This page is currently read-only.",
878
878
  "terminalView.reconnect": "Reconnect",
879
879
  "terminalView.readingEnvironment": "Reading the terminal environment…",
880
+ "terminalView.loadTimeout": "The terminal service did not respond in time. Please retry.",
880
881
  "terminalView.starting": "Starting…",
881
882
  "terminalView.connecting": "Connecting…",
882
883
  "terminalView.disconnected": "Connection lost.",
@@ -909,6 +910,7 @@ window.__ModuleLoader__.load({
909
910
  "terminalView.readOnly": "此页面当前只读。",
910
911
  "terminalView.reconnect": "重新连接",
911
912
  "terminalView.readingEnvironment": "正在读取终端环境…",
913
+ "terminalView.loadTimeout": "终端服务响应超时,请重试。",
912
914
  "terminalView.starting": "正在启动…",
913
915
  "terminalView.connecting": "正在连接…",
914
916
  "terminalView.disconnected": "连接已断开。",
@@ -958,6 +960,8 @@ window.__ModuleLoader__.load({
958
960
  "usage.windowFiveHour": "5-hour quota",
959
961
  "usage.windowWeekly": "Weekly quota",
960
962
  "usage.windowMonthly": "Monthly quota",
963
+ "usage.quotaGroupGemini": "Gemini models",
964
+ "usage.quotaGroupThirdParty": "Claude and GPT models",
961
965
  "usage.popoverSubscriptionUsage": "{provider} subscription usage",
962
966
  "usage.popoverSubscriptionTitle": "{provider} subscription",
963
967
  "usage.windowRemaining": "{label} remaining {percent}%",
@@ -1062,6 +1066,8 @@ window.__ModuleLoader__.load({
1062
1066
  "usage.windowFiveHour": "5 小时额度",
1063
1067
  "usage.windowWeekly": "周额度",
1064
1068
  "usage.windowMonthly": "月额度",
1069
+ "usage.quotaGroupGemini": "Gemini 模型",
1070
+ "usage.quotaGroupThirdParty": "Claude 与 GPT 模型",
1065
1071
  "usage.popoverSubscriptionUsage": "{provider} 订阅使用情况",
1066
1072
  "usage.popoverSubscriptionTitle": "{provider} 订阅",
1067
1073
  "usage.windowRemaining": "{label}剩余 {percent}%",
@@ -1231,6 +1237,70 @@ window.__ModuleLoader__.load({
1231
1237
  "feature.cliAdapters.description": "View installed external Agents, versions, command paths, available models, and enable them independently. External Agent context panels keep only the occupancy percentage and totals.",
1232
1238
  "feature.workspaceSession.label": "Workspace session enhancement",
1233
1239
  "feature.workspaceSession.description": "Show Agent logos, archived-session entries, workspace hiding controls, subscription or usage information, local quick sessions, and an optional remembered conversation/right-sidebar width ratio.",
1240
+ "feature.assistant.label": "Global smart assistant (Testing)",
1241
+ "feature.assistant.description": "Summarize progress across selected workspaces and route voice or text instructions to an explicit target session.",
1242
+ "voice.globalAssistant": "Global voice assistant (Testing)",
1243
+ "voice.audioNotice": "Audio is processed by the configured local or cloud voice provider.",
1244
+ "voice.emptyTranscript": "No usable speech was recognized; nothing was submitted. Please try again.",
1245
+ "voice.invalidTranscript": "The transcription was not valid, so nothing was submitted. Please say it again.",
1246
+ "voice.secureContextRequired": "Microphone access requires a secure browser context. Use HTTPS, WSS, or localhost and try again.",
1247
+ "voice.microphoneNotice": "Starting this control uses the microphone in this browser.",
1248
+ "voice.microphoneUpdated": "Microphone updated.",
1249
+ "voice.inputDevice": "Input device",
1250
+ "voice.defaultMicrophone": "Browser default microphone",
1251
+ "voice.unnamedMicrophone": "Unnamed microphone",
1252
+ "voice.outputDevice": "Output device",
1253
+ "voice.defaultOutput": "Browser/OS default output",
1254
+ "voice.unnamedOutput": "Unnamed output",
1255
+ "voice.outputUpdated": "Output device updated.",
1256
+ "voice.dialog.title": "Global voice assistant (Testing)",
1257
+ "voice.dialog.description": "Open this window first. Start voice when you are ready; recognized text appears here in real time.",
1258
+ "voice.dialog.realtimeHint": "Realtime Sherpa-ONNX mode. Partial recognition appears while you speak.",
1259
+ "voice.dialog.unavailable": "Realtime Sherpa-ONNX is unavailable. Check the local runtime and model configuration; fallback transcription is disabled.",
1260
+ "voice.dialog.liveText": "Live transcription",
1261
+ "voice.dialog.empty": "Nothing recognized yet.",
1262
+ "voice.dialog.start": "Start realtime conversation",
1263
+ "voice.dialog.stop": "Stop conversation",
1264
+ "voice.dialog.close": "Close",
1265
+ "voice.dialog.clear": "Clear text",
1266
+ "voice.dialog.status.loading": "Preparing microphone…",
1267
+ "voice.dialog.status.listening": "Listening",
1268
+ "voice.dialog.status.recording": "Recording",
1269
+ "voice.dialog.status.error": "Error",
1270
+ "voice.dialog.status.active": "Active",
1271
+ "voice.dialog.status.idle": "Idle",
1272
+ "voice.setup.open": "Configure voice assistant (Testing)",
1273
+ "voice.setup.title": "Initialize global voice assistant (Testing)",
1274
+ "voice.setup.description": "Choose a recommended realtime model. CodingNS will download and initialize it automatically.",
1275
+ "voice.setup.onlyRealtime": "Only streaming models that support realtime conversation are shown. Files are stored on the current Host and reused after download.",
1276
+ "voice.setup.model": "Realtime voice model",
1277
+ "voice.setup.modelRequired": "Choose a realtime voice model.",
1278
+ "voice.setup.downloadHint": "The first setup downloads the model and configures the runtime. You will not need to enter file paths.",
1279
+ "voice.setup.downloading": "Downloading and initializing the model. Keep this window open…",
1280
+ "voice.setup.provider": "Voice provider",
1281
+ "voice.setup.provider.dsh": "DSH speechToText(browser hold-to-speak)",
1282
+ "voice.setup.provider.sherpa": "Sherpa-ONNX(local realtime)",
1283
+ "voice.setup.providerDshHint": "This mode uses the Host speechToText provider for sentence transcription and browser speech synthesis for replies. No local Sherpa model is required.",
1284
+ "voice.setup.modelHint": "Enter absolute paths to the Sherpa-ONNX model files on the Host machine.",
1285
+ "voice.setup.asrEncoder": "ASR encoder model",
1286
+ "voice.setup.asrDecoder": "ASR decoder model",
1287
+ "voice.setup.asrJoiner": "ASR joiner model",
1288
+ "voice.setup.asrTokens": "ASR tokens file",
1289
+ "voice.setup.vadModel": "VAD model(optional)",
1290
+ "voice.setup.ttsModel": "TTS model(optional)",
1291
+ "voice.setup.ttsTokens": "TTS tokens(optional)",
1292
+ "voice.setup.ttsLexicon": "TTS lexicon(optional)",
1293
+ "voice.setup.optional": "Optional; leave empty to disable",
1294
+ "voice.setup.required": "Sherpa-ONNX requires encoder, decoder, joiner, and tokens paths.",
1295
+ "voice.setup.cancel": "Cancel",
1296
+ "voice.setup.save": "Download and initialize",
1297
+ "voice.setup.reconfigure": "Configure",
1298
+ "voice.setup.status.uninitialized": "Not initialized; the assistant button opens setup instead of starting voice.",
1299
+ "voice.setup.status.dsh": "DSH speechToText sentence transcription with browser speech synthesis.",
1300
+ "voice.setup.status.sherpa": "Sherpa-ONNX local realtime mode.",
1301
+ "assistant.scopeDescription": "Only selected workspaces are readable by the global assistant. Archived sessions are always excluded.",
1302
+ "assistant.noWorkspaces": "No workspaces are available in this Host.",
1303
+ "assistant.saved": "Assistant scope saved.",
1234
1304
  "feature.mobileAccess.label": "Mobile access enhancements",
1235
1305
  "feature.mobileAccess.description": "On phones or narrow viewports, hide the left sidebar entirely and keep only the top-left logo as the way back in.",
1236
1306
  "mobile.hideSidebarOnMobile": "Fully hide the left sidebar on mobile",
@@ -1255,6 +1325,26 @@ window.__ModuleLoader__.load({
1255
1325
  "terminal.title": "Terminal",
1256
1326
  "terminal.new": "New terminal",
1257
1327
  "terminal.close": "Close terminal",
1328
+ "terminal.refresh": "Refresh terminal",
1329
+ "terminal.tools": "Terminal shortcuts",
1330
+ "terminal.holdCtrl": "Hold Ctrl",
1331
+ "terminal.holdAlt": "Hold Alt",
1332
+ "terminal.holdWin": "Hold Win",
1333
+ "terminal.holdOption": "Hold Option",
1334
+ "terminal.holdCommand": "Hold Command",
1335
+ "terminal.holdSuper": "Hold Super",
1336
+ "terminal.shortcutTab": "Tab",
1337
+ "terminal.shortcutEscape": "Escape",
1338
+ "terminal.shortcutEnter": "Enter",
1339
+ "terminal.shortcutBackspace": "Backspace",
1340
+ "terminal.shortcutUp": "Arrow up",
1341
+ "terminal.shortcutDown": "Arrow down",
1342
+ "terminal.shortcutLeft": "Arrow left",
1343
+ "terminal.shortcutRight": "Arrow right",
1344
+ "terminal.shortcutC": "C (Ctrl+C when Ctrl is held)",
1345
+ "terminal.shortcutD": "D (Ctrl+D when Ctrl is held)",
1346
+ "terminal.shortcutL": "L (Ctrl+L when Ctrl is held)",
1347
+ "terminal.shortcutZ": "Z (Ctrl+Z when Ctrl is held)",
1258
1348
  "terminal.description": "Run commands in the session workspace",
1259
1349
  "terminal.loadingShell": "Reading shells…",
1260
1350
  "terminal.retry": "Retry",
@@ -1458,6 +1548,12 @@ window.__ModuleLoader__.load({
1458
1548
  "delegate.cardTitle": "Delegation task",
1459
1549
  "delegate.cardStatus": "Delegating",
1460
1550
  "delegate.cardTarget": "{label} ({adapter}{model})",
1551
+ "skills.label": "Skills",
1552
+ "skills.description": "Browse and explicitly invoke Skills provided by the current Agent",
1553
+ "skills.searchPlaceholder": "Search Skills",
1554
+ "skills.searchEmpty": "The current Agent has no available Skills",
1555
+ "skills.searchNoResults": "No matching Skill",
1556
+ "skills.draftUnsupported": "This DSH version cannot write a Skill mention. Please upgrade DSH.",
1461
1557
  "cli.loadingModel": "Loading models…",
1462
1558
  "cli.noModelsAvailable": "No models available",
1463
1559
  "cli.model": "Model",
@@ -1503,6 +1599,8 @@ window.__ModuleLoader__.load({
1503
1599
  "subscriptionUsage.saved": "Usage query settings saved",
1504
1600
  "workspace.showQuickPhrases": "Show quick sessions",
1505
1601
  "workspace.quickPhrasesDescription": "Keep reusable session instructions locally in Codingns4DSH and show the quick sessions button inside the composer.",
1602
+ "workspace.showSkillQuickReference": "Index the selected Agent Skills for quick command references",
1603
+ "workspace.skillQuickReferenceDescription": "Load Skills from the selected Agent, insert them as one reference chip, and render them as a compact element in the message list.",
1506
1604
  "workspace.rememberConversationRightbarRatio": "Remember conversation and right sidebar width ratio",
1507
1605
  "workspace.rememberConversationRightbarRatioDescription": "Restore the right sidebar proportion after refreshing this browser; the ratio is stored locally.",
1508
1606
  "mobile.sidebarGestures": "Swipe to open or close sidebars",
@@ -1561,6 +1659,70 @@ window.__ModuleLoader__.load({
1561
1659
  "feature.cliAdapters.description": "管理外部 Agent",
1562
1660
  "feature.workspaceSession.label": "工作区会话增强",
1563
1661
  "feature.workspaceSession.description": "增强工作区会话体验",
1662
+ "feature.assistant.label": "全局智能助理(测试中)",
1663
+ "feature.assistant.description": "汇总受管工作区进展,并把语音或文字指令派发到明确的目标会话。",
1664
+ "voice.globalAssistant": "全局语音助理(测试中)",
1665
+ "voice.audioNotice": "音频会交给当前配置的本地或云端语音提供方处理。",
1666
+ "voice.emptyTranscript": "未识别到有效语音,未提交内容;请重试。",
1667
+ "voice.invalidTranscript": "转写结果无效,未提交内容;请重新说一遍。",
1668
+ "voice.secureContextRequired": "麦克风需要浏览器安全上下文,请改用 HTTPS、WSS 或 localhost 后重试。",
1669
+ "voice.microphoneNotice": "启动此入口会使用当前浏览器的麦克风。",
1670
+ "voice.microphoneUpdated": "麦克风已更新。",
1671
+ "voice.inputDevice": "输入设备",
1672
+ "voice.defaultMicrophone": "浏览器默认麦克风",
1673
+ "voice.unnamedMicrophone": "未命名麦克风",
1674
+ "voice.outputDevice": "输出设备",
1675
+ "voice.defaultOutput": "浏览器/系统默认输出",
1676
+ "voice.unnamedOutput": "未命名输出设备",
1677
+ "voice.outputUpdated": "输出设备已更新。",
1678
+ "voice.dialog.title": "全局语音助理(测试中)",
1679
+ "voice.dialog.description": "先打开此窗口,再按需要开始语音;识别文字会实时显示在这里。",
1680
+ "voice.dialog.realtimeHint": "Sherpa-ONNX 实时模式,说话时会显示实时识别结果。",
1681
+ "voice.dialog.unavailable": "Sherpa-ONNX 实时模式不可用,请检查本地运行时和模型配置;已禁用回退转写。",
1682
+ "voice.dialog.liveText": "实时文字转换",
1683
+ "voice.dialog.empty": "暂时还没有识别文字。",
1684
+ "voice.dialog.start": "开始实时对话",
1685
+ "voice.dialog.stop": "停止对话",
1686
+ "voice.dialog.close": "关闭",
1687
+ "voice.dialog.clear": "清空文字",
1688
+ "voice.dialog.status.loading": "正在准备麦克风……",
1689
+ "voice.dialog.status.listening": "正在聆听",
1690
+ "voice.dialog.status.recording": "正在录音",
1691
+ "voice.dialog.status.error": "出错",
1692
+ "voice.dialog.status.active": "运行中",
1693
+ "voice.dialog.status.idle": "未启动",
1694
+ "voice.setup.open": "配置语音助理(测试中)",
1695
+ "voice.setup.title": "初始化全局语音助理(测试中)",
1696
+ "voice.setup.description": "选择一个推荐的实时模型,系统会自动下载并完成初始化。",
1697
+ "voice.setup.onlyRealtime": "这里只显示支持实时对话的流式语音模型。模型文件保存在当前 Host,已下载的文件会自动复用。",
1698
+ "voice.setup.model": "实时语音模型",
1699
+ "voice.setup.modelRequired": "请选择一个实时语音模型。",
1700
+ "voice.setup.downloadHint": "首次使用会自动下载模型并配置运行时,之后无需再次填写路径。",
1701
+ "voice.setup.downloading": "正在下载并初始化模型,请保持此窗口打开……",
1702
+ "voice.setup.provider": "语音提供商",
1703
+ "voice.setup.provider.dsh": "DSH speechToText(浏览器按住说话)",
1704
+ "voice.setup.provider.sherpa": "Sherpa-ONNX(本地实时模式)",
1705
+ "voice.setup.providerDshHint": "此模式使用 Host 的 speechToText 进行句级转写,并使用浏览器 speechSynthesis 播报回复,不需要本地 Sherpa 模型。",
1706
+ "voice.setup.modelHint": "请输入 Host 机器上的 Sherpa-ONNX 模型文件绝对路径。",
1707
+ "voice.setup.asrEncoder": "ASR encoder 模型",
1708
+ "voice.setup.asrDecoder": "ASR decoder 模型",
1709
+ "voice.setup.asrJoiner": "ASR joiner 模型",
1710
+ "voice.setup.asrTokens": "ASR tokens 文件",
1711
+ "voice.setup.vadModel": "VAD 模型(可选)",
1712
+ "voice.setup.ttsModel": "TTS 模型(可选)",
1713
+ "voice.setup.ttsTokens": "TTS tokens(可选)",
1714
+ "voice.setup.ttsLexicon": "TTS lexicon(可选)",
1715
+ "voice.setup.optional": "可选;留空表示禁用",
1716
+ "voice.setup.required": "Sherpa-ONNX 必须填写 encoder、decoder、joiner 和 tokens 路径。",
1717
+ "voice.setup.cancel": "取消",
1718
+ "voice.setup.save": "下载并初始化",
1719
+ "voice.setup.reconfigure": "配置",
1720
+ "voice.setup.status.uninitialized": "尚未初始化;点击助手按钮只打开配置页,不会直接启动语音。",
1721
+ "voice.setup.status.dsh": "使用 DSH speechToText 句级转写和浏览器语音播报。",
1722
+ "voice.setup.status.sherpa": "使用 Sherpa-ONNX 本地实时模式。",
1723
+ "assistant.scopeDescription": "全局助理只读取已勾选的工作区,已归档会话始终排除。",
1724
+ "assistant.noWorkspaces": "当前 Host 没有可选工作区。",
1725
+ "assistant.saved": "助理受管范围已保存。",
1564
1726
  "feature.mobileAccess.label": "移动端访问增强",
1565
1727
  "feature.mobileAccess.description": "优化移动端访问布局",
1566
1728
  "mobile.hideSidebarOnMobile": "移动端彻底隐藏左侧边栏",
@@ -1585,6 +1747,26 @@ window.__ModuleLoader__.load({
1585
1747
  "terminal.title": "终端",
1586
1748
  "terminal.new": "新建终端",
1587
1749
  "terminal.close": "关闭终端",
1750
+ "terminal.refresh": "刷新终端",
1751
+ "terminal.tools": "终端快捷操作",
1752
+ "terminal.holdCtrl": "按住 Ctrl",
1753
+ "terminal.holdAlt": "按住 Alt",
1754
+ "terminal.holdWin": "按住 Win",
1755
+ "terminal.holdOption": "按住 Option",
1756
+ "terminal.holdCommand": "按住 Command",
1757
+ "terminal.holdSuper": "按住 Super",
1758
+ "terminal.shortcutTab": "Tab 制表键",
1759
+ "terminal.shortcutEscape": "Esc 退出键",
1760
+ "terminal.shortcutEnter": "Enter 回车键",
1761
+ "terminal.shortcutBackspace": "Backspace 退格键",
1762
+ "terminal.shortcutUp": "向上箭头",
1763
+ "terminal.shortcutDown": "向下箭头",
1764
+ "terminal.shortcutLeft": "向左箭头",
1765
+ "terminal.shortcutRight": "向右箭头",
1766
+ "terminal.shortcutC": "C(按住 Ctrl 时发送 Ctrl+C)",
1767
+ "terminal.shortcutD": "D(按住 Ctrl 时发送 Ctrl+D)",
1768
+ "terminal.shortcutL": "L(按住 Ctrl 时发送 Ctrl+L)",
1769
+ "terminal.shortcutZ": "Z(按住 Ctrl 时发送 Ctrl+Z)",
1588
1770
  "terminal.description": "在会话工作区运行命令",
1589
1771
  "terminal.loadingShell": "正在读取 Shell…",
1590
1772
  "terminal.retry": "重试",
@@ -1788,6 +1970,12 @@ window.__ModuleLoader__.load({
1788
1970
  "delegate.cardTitle": "委派任务",
1789
1971
  "delegate.cardStatus": "委派中",
1790
1972
  "delegate.cardTarget": "{label}({adapter}{model})",
1973
+ "skills.label": "技能",
1974
+ "skills.description": "浏览并显式调用当前 Agent 提供的技能",
1975
+ "skills.searchPlaceholder": "搜索技能",
1976
+ "skills.searchEmpty": "当前 Agent 没有可用技能",
1977
+ "skills.searchNoResults": "没有匹配的技能",
1978
+ "skills.draftUnsupported": "当前 DSH 不支持写入技能引用,请先升级 DSH。",
1791
1979
  "cli.loadingModel": "加载模型…",
1792
1980
  "cli.noModelsAvailable": "无可用模型",
1793
1981
  "cli.model": "模型",
@@ -1831,8 +2019,10 @@ window.__ModuleLoader__.load({
1831
2019
  "subscriptionUsage.cancel": "取消",
1832
2020
  "subscriptionUsage.save": "保存",
1833
2021
  "subscriptionUsage.saved": "用量查询设置已保存",
1834
- "workspace.showQuickPhrases": "显示快捷会话",
2022
+ "workspace.showQuickPhrases": "在对话框中显示快捷短语组件",
1835
2023
  "workspace.quickPhrasesDescription": "在 Codingns4DSH 内本地保存可复用的会话指令,并将快捷会话按钮显示在输入工具区。",
2024
+ "workspace.showSkillQuickReference": "索引指定 Agent 的 Skill 列表并通过指令快速引用",
2025
+ "workspace.skillQuickReferenceDescription": "加载本次会话所选 Agent 的 Skill,将其作为整体引用元素插入聊天框,并在消息列表中美化显示。",
1836
2026
  "workspace.rememberConversationRightbarRatio": "记忆对话窗口与右侧栏的宽度比例",
1837
2027
  "workspace.rememberConversationRightbarRatioDescription": "刷新后恢复右侧栏比例;比例仅保存在当前浏览器中。",
1838
2028
  "mobile.sidebarGestures": "横滑开合侧栏",
@@ -2571,9 +2761,16 @@ window.__ModuleLoader__.load({
2571
2761
  //#endregion
2572
2762
  //#region src/shared/contracts/version.ts
2573
2763
  /** 当前经过完整验证的 DSH 版本;源文件由根目录 version.json 同步。 */
2574
- const DSH_VERSION = "0.2.0-rc.2";
2575
- /** 插件正式验证的 DSH 版本范围;rc.2 的 Client ABI 未经验证不得放宽上限。 */
2576
- const DSH_COMPATIBILITY = ">=0.2.0-rc.2 <=0.2.0-rc.2";
2764
+ const DSH_VERSION = "0.2.1-alpha.1";
2765
+ /**
2766
+ * 插件正式验证的 DSH 版本范围,覆盖 rc.2 到当前验证的 alpha.1。
2767
+ *
2768
+ * 0.2.0-rc.2 与 0.2.1-alpha.1 属于同一 API 世代:插件消费的 7 个宿主包接口面
2769
+ * 未变(仅 dsh-client-ui-primitives 新增 InlineEditor),会话格式仍为 v4。因此
2770
+ * 这里保留明确上界,是为了让 npm/DSH 安装期也能识别 0.2.1-alpha.1 这个跨 patch
2771
+ * 预发布版本;出现新的 API 世代或结构变化时,应重新验证并更新范围与能力路由。
2772
+ */
2773
+ const DSH_COMPATIBILITY = ">=0.2.0-rc.2 <=0.2.1-alpha.1";
2577
2774
  /**
2578
2775
  * 兼容范围下界,即插件仍然接受的最低 DSH 版本。
2579
2776
  *
@@ -2582,7 +2779,7 @@ window.__ModuleLoader__.load({
2582
2779
  * “不支持的 DSH 版本”,把仅仅缺少启动页注入的页面误判为宿主不兼容。
2583
2780
  */
2584
2781
  function minimumSupportedDshVersion() {
2585
- return /^>=([^ ]+)/u.exec(">=0.2.0-rc.2 <=0.2.0-rc.2")?.[1] ?? "0.2.0-rc.2";
2782
+ return /^>=([^ ]+)/u.exec(">=0.2.0-rc.2 <=0.2.1-alpha.1")?.[1] ?? "0.2.1-alpha.1";
2586
2783
  }
2587
2784
  /** Host 注入到浏览器页面的真实 DSH 版本全局字段。 */
2588
2785
  const CODINGNS_DSH_VERSION_GLOBAL = "__CODINGNS_DSH_VERSION__";
@@ -2591,7 +2788,7 @@ window.__ModuleLoader__.load({
2591
2788
  return /^0\.1\.5(?:-|$)/u.test(version);
2592
2789
  }
2593
2790
  /** Codingns4DSH 插件自身的 npm 版本。 */
2594
- const CODINGNS_VERSION = "0.2.0-rc3";
2791
+ const CODINGNS_VERSION = "0.2.1-beta.1";
2595
2792
  /** 判断宿主版本是否落在当前插件声明的 DSH 兼容范围内;范围省略上界时不校验上限。 */
2596
2793
  function isDshVersionCompatible(version) {
2597
2794
  const match = /^>=([^ ]+)(?: <=([^ ]+))?$/u.exec(DSH_COMPATIBILITY);
@@ -2642,6 +2839,23 @@ window.__ModuleLoader__.load({
2642
2839
  }
2643
2840
  //#endregion
2644
2841
  //#region src/shared/contracts/config.ts
2842
+ const DEFAULT_ASSISTANT_VOICE_SETTINGS = {
2843
+ initialized: false,
2844
+ provider: "dsh-speech-to-text",
2845
+ modelId: "",
2846
+ asrEncoder: "",
2847
+ asrDecoder: "",
2848
+ asrJoiner: "",
2849
+ asrTokens: "",
2850
+ vadModel: "",
2851
+ ttsModel: "",
2852
+ ttsTokens: "",
2853
+ ttsLexicon: ""
2854
+ };
2855
+ const DEFAULT_ASSISTANT_SETTINGS = {
2856
+ managedWorkspaceIds: [],
2857
+ voice: DEFAULT_ASSISTANT_VOICE_SETTINGS
2858
+ };
2645
2859
  /** 并发子代理数上限:下限 1 保证至少能派发,上限避免一次性压垮本机。 */
2646
2860
  const SUBAGENT_BRIDGE_MAX_CONCURRENT_LIMITS = {
2647
2861
  min: 1,
@@ -2809,6 +3023,7 @@ window.__ModuleLoader__.load({
2809
3023
  const CODINGNS_FILE_MANAGEMENT_FIELD = "fileManagement";
2810
3024
  const CODINGNS_MOBILE_ACCESS_FIELD = "mobileAccess";
2811
3025
  const CODINGNS_SUBSCRIPTION_USAGE_FIELD = "subscriptionUsage";
3026
+ const CODINGNS_ASSISTANT_FIELD = "assistant";
2812
3027
  const DEFAULT_CODINGNS_CONTROL_BASE_URLS = ["https://channel.codingns.com:1443"];
2813
3028
  /** 独立 H5 登录页面地址;登录控制站后可从设置页复制给其他设备。 */
2814
3029
  const CODINGNS_H5_LOGIN_URL = "https://dsh.codingns.com";
@@ -2835,6 +3050,7 @@ window.__ModuleLoader__.load({
2835
3050
  hiddenWorkspaceIds: [],
2836
3051
  showSubscriptionUsage: true,
2837
3052
  showQuickPhrases: true,
3053
+ showSkillQuickReference: true,
2838
3054
  rememberConversationRightbarRatio: false,
2839
3055
  quickPhrases: DEFAULT_QUICK_PHRASES.map((phrase) => ({ ...phrase })),
2840
3056
  quickPhrasesSeeded: true
@@ -3108,6 +3324,8 @@ window.__ModuleLoader__.load({
3108
3324
  let userActivation;
3109
3325
  let suppressedClick;
3110
3326
  let dispatchingSyntheticClick = false;
3327
+ let mountedUnsubscribe;
3328
+ let collapseTimers = [];
3111
3329
  const now = () => Date.now();
3112
3330
  const blurComposer = () => {
3113
3331
  const activeElement = hostDocument?.activeElement;
@@ -3161,6 +3379,7 @@ window.__ModuleLoader__.load({
3161
3379
  if (!isShortMobileTap(start, end)) return;
3162
3380
  const row = sessionRow(point.target);
3163
3381
  if (row === null || isRowControl(point.target, row)) return;
3382
+ collapseRightbar();
3164
3383
  preventDefault$1(event);
3165
3384
  suppressedClick = {
3166
3385
  row,
@@ -3185,6 +3404,7 @@ window.__ModuleLoader__.load({
3185
3404
  preventDefault$1(event);
3186
3405
  stopPropagation(event);
3187
3406
  }
3407
+ if (row !== null && !isRowControl(eventTarget(event), row)) collapseRightbar();
3188
3408
  suppressedClick = void 0;
3189
3409
  };
3190
3410
  const onDoubleClickCapture = (event) => {
@@ -3207,6 +3427,7 @@ window.__ModuleLoader__.load({
3207
3427
  hostDocument.addEventListener("focusin", onFocusIn, capture);
3208
3428
  hostDocument.addEventListener("click", onClickCapture, capture);
3209
3429
  hostDocument.addEventListener("dblclick", onDoubleClickCapture, capture);
3430
+ subscribeMountedSession();
3210
3431
  };
3211
3432
  const removeListeners = () => {
3212
3433
  if (!active || hostDocument === void 0) return;
@@ -3218,10 +3439,53 @@ window.__ModuleLoader__.load({
3218
3439
  hostDocument.removeEventListener("focusin", onFocusIn, true);
3219
3440
  hostDocument.removeEventListener("click", onClickCapture, true);
3220
3441
  hostDocument.removeEventListener("dblclick", onDoubleClickCapture, true);
3442
+ mountedUnsubscribe?.();
3443
+ mountedUnsubscribe = void 0;
3444
+ for (const timer of collapseTimers) clearTimeout(timer);
3445
+ collapseTimers = [];
3221
3446
  touchStart = void 0;
3222
3447
  userActivation = void 0;
3223
3448
  suppressedClick = void 0;
3224
3449
  };
3450
+ /**
3451
+ * DSH 的右栏布局按会话保存。移动端切换到新会话时,宿主会先挂载会话,
3452
+ * 随后恢复该会话上次的展开状态;这里仅在移动端把这次自动恢复收回,
3453
+ * 让主界面稳定停留在消息区。用户之后仍可通过右滑或原生按钮主动打开。
3454
+ */
3455
+ const collapseRightbar = () => {
3456
+ const sidebarRight = options.sidebarRight;
3457
+ if (sidebarRight === void 0) return false;
3458
+ try {
3459
+ if (!sidebarRight.isExpanded()) return false;
3460
+ sidebarRight.toggleExpanded();
3461
+ for (const timer of collapseTimers) clearTimeout(timer);
3462
+ collapseTimers = [];
3463
+ return true;
3464
+ } catch {
3465
+ return false;
3466
+ }
3467
+ };
3468
+ const scheduleRightbarCollapse = () => {
3469
+ if (!active || options.sidebarRight === void 0) return;
3470
+ for (const timer of collapseTimers) clearTimeout(timer);
3471
+ collapseTimers = [];
3472
+ if (collapseRightbar()) return;
3473
+ collapseTimers = [
3474
+ setTimeout(collapseRightbar, 0),
3475
+ setTimeout(collapseRightbar, 60),
3476
+ setTimeout(collapseRightbar, 240)
3477
+ ];
3478
+ };
3479
+ const subscribeMountedSession = () => {
3480
+ const subscribe = options.sidebarRight?.mounted?.subscribe;
3481
+ if (typeof subscribe !== "function") return;
3482
+ try {
3483
+ mountedUnsubscribe = subscribe(scheduleRightbarCollapse);
3484
+ } catch {
3485
+ mountedUnsubscribe = void 0;
3486
+ }
3487
+ scheduleRightbarCollapse();
3488
+ };
3225
3489
  const refresh = () => {
3226
3490
  if (disposed) return false;
3227
3491
  if (isMobileTouchViewport$1(hostWindow, viewportMaxPx)) addListeners();
@@ -4080,7 +4344,7 @@ window.__ModuleLoader__.load({
4080
4344
  start: (context) => {
4081
4345
  ensureCryptoRandomUUID();
4082
4346
  let prompt;
4083
- const mobileSessionInteraction = startMobileSessionInteractionDom();
4347
+ const mobileSessionInteraction = startMobileSessionInteractionDom({ sidebarRight: context.services.sidebarRight });
4084
4348
  const sync = () => {
4085
4349
  const pwa = context.services.settings.getSnapshot().value?.lanAccessDsh.pwa ?? DEFAULT_LAN_ACCESS_DSH_PWA_SETTINGS;
4086
4350
  if (!pwa.enabled || !pwa.installPrompt) {
@@ -5001,6 +5265,54 @@ window.__ModuleLoader__.load({
5001
5265
  return age >= 0 && age < refreshIntervalMins * 6e4;
5002
5266
  }
5003
5267
  //#endregion
5268
+ //#region src/shared/voice-stream.ts
5269
+ function encodeVoiceStreamMessage(message) {
5270
+ return new TextEncoder().encode(`${JSON.stringify(message)}\n`);
5271
+ }
5272
+ function encodeVoiceStreamOpen(message) {
5273
+ return encodeVoiceStreamMessage({
5274
+ type: "open",
5275
+ ...message
5276
+ });
5277
+ }
5278
+ //#endregion
5279
+ //#region src/shared/voice-models.ts
5280
+ const STREAMING_FILES = [
5281
+ {
5282
+ setting: "asrEncoder",
5283
+ name: "encoder-epoch-99-avg-1.int8.onnx"
5284
+ },
5285
+ {
5286
+ setting: "asrDecoder",
5287
+ name: "decoder-epoch-99-avg-1.int8.onnx"
5288
+ },
5289
+ {
5290
+ setting: "asrJoiner",
5291
+ name: "joiner-epoch-99-avg-1.int8.onnx"
5292
+ },
5293
+ {
5294
+ setting: "asrTokens",
5295
+ name: "tokens.txt"
5296
+ }
5297
+ ];
5298
+ /** 只暴露支持实时输入的推荐模型;模型地址由 Host 根据此白名单解析。 */
5299
+ const ASSISTANT_VOICE_MODEL_CATALOG = [{
5300
+ id: "sherpa-onnx-streaming-zh-14m",
5301
+ label: "中文实时模型(推荐)",
5302
+ description: "本地运行,约 27 MB,适合中文语音对话。",
5303
+ repository: "csukuangfj/sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23",
5304
+ files: STREAMING_FILES
5305
+ }, {
5306
+ id: "sherpa-onnx-streaming-zh-en",
5307
+ label: "中英双语实时模型",
5308
+ description: "本地运行,约 200 MB,同时识别中文和英文。",
5309
+ repository: "csukuangfj/sherpa-onnx-streaming-zipformer-bilingual-zh-en-2023-02-20",
5310
+ files: STREAMING_FILES
5311
+ }];
5312
+ function findAssistantVoiceModel(modelId) {
5313
+ return ASSISTANT_VOICE_MODEL_CATALOG.find((model) => model.id === modelId);
5314
+ }
5315
+ //#endregion
5004
5316
  //#region src/transport/debug.ts
5005
5317
  init_debug();
5006
5318
  /** 创建一个可注入测试 sink 的调试 logger。默认开关由当前运行环境决定。 */
@@ -5034,12 +5346,12 @@ window.__ModuleLoader__.load({
5034
5346
  if (typeof fields.status === "number" && fields.status >= 400) return true;
5035
5347
  if (typeof fields.state === "string" && /^(failed|disconnected|closed|degraded)$/iu.test(fields.state)) return true;
5036
5348
  if (typeof fields.event === "string" && fields.event !== event) {
5037
- const nestedFields = isRecord$9(fields.fields) ? fields.fields : {};
5349
+ const nestedFields = isRecord$10(fields.fields) ? fields.fields : {};
5038
5350
  return isDshTransportWarningEvent(fields.event, nestedFields);
5039
5351
  }
5040
5352
  return false;
5041
5353
  }
5042
- function isRecord$9(value) {
5354
+ function isRecord$10(value) {
5043
5355
  return typeof value === "object" && value !== null && !Array.isArray(value);
5044
5356
  }
5045
5357
  /**
@@ -5859,15 +6171,15 @@ window.__ModuleLoader__.load({
5859
6171
  sendHello() {
5860
6172
  this.send(this.createEnvelope("session.hello", "session", {
5861
6173
  protocol: this.options.protocol ?? "dsh-transport-v1",
5862
- dshVersion: this.options.dshVersion ?? "0.2.0-rc.2",
6174
+ dshVersion: this.options.dshVersion ?? "0.2.1-alpha.1",
5863
6175
  capabilities: [...this.options.capabilities ?? []]
5864
6176
  }));
5865
6177
  }
5866
6178
  sendReady(capabilities, peerDshVersion) {
5867
6179
  this.send(this.createEnvelope("session.ready", "session", {
5868
6180
  protocol: this.options.protocol ?? "dsh-transport-v1",
5869
- dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.0-rc.2",
5870
- hostDshVersion: this.options.dshVersion ?? "0.2.0-rc.2",
6181
+ dshVersion: peerDshVersion ?? this.options.dshVersion ?? "0.2.1-alpha.1",
6182
+ hostDshVersion: this.options.dshVersion ?? "0.2.1-alpha.1",
5871
6183
  capabilities: [...capabilities],
5872
6184
  byteCredit: 65536,
5873
6185
  messageCredit: 32
@@ -6993,7 +7305,7 @@ window.__ModuleLoader__.load({
6993
7305
  async onMessage(event) {
6994
7306
  const sourceWindow = event.source;
6995
7307
  if (this.disposed || sourceWindow === null || !this.iframeWindows.has(sourceWindow)) return;
6996
- if (!isRecord$8(event.data) || typeof event.data.kind !== "string") return;
7308
+ if (!isRecord$9(event.data) || typeof event.data.kind !== "string") return;
6997
7309
  const message = event.data;
6998
7310
  if (message.kind !== "dsh-web-debug" && typeof message.id !== "string") return;
6999
7311
  if (typeof message.id === "string") this.messageWindows.set(message.id, sourceWindow);
@@ -7004,7 +7316,7 @@ window.__ModuleLoader__.load({
7004
7316
  try {
7005
7317
  if (message.kind === "dsh-web-debug") {
7006
7318
  const eventName = typeof message.event === "string" ? message.event : "unknown";
7007
- const fields = isRecord$8(message.fields) ? message.fields : {};
7319
+ const fields = isRecord$9(message.fields) ? message.fields : {};
7008
7320
  this.debug.log(`iframe.${eventName}`, fields);
7009
7321
  if (/^(bridge\.ws\.|client\.connection\.)/u.test(eventName)) await this.options.transport.webRequest("web.debug", {
7010
7322
  event: eventName,
@@ -7013,7 +7325,7 @@ window.__ModuleLoader__.load({
7013
7325
  return;
7014
7326
  }
7015
7327
  if (message.kind === "fetch") {
7016
- const input = isRecord$8(message.input) ? message.input : {};
7328
+ const input = isRecord$9(message.input) ? message.input : {};
7017
7329
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
7018
7330
  const body = typeof message.body === "string" ? message.body : typeof input.body === "string" ? input.body : void 0;
7019
7331
  const response = path.startsWith("/assets/") || path.startsWith("/plugins/") ? {
@@ -7039,7 +7351,7 @@ window.__ModuleLoader__.load({
7039
7351
  return;
7040
7352
  }
7041
7353
  if (message.kind === "script") {
7042
- const input = isRecord$8(message.input) ? message.input : {};
7354
+ const input = isRecord$9(message.input) ? message.input : {};
7043
7355
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
7044
7356
  const url = await this.loadScript(path, void 0);
7045
7357
  this.postResponse(message.id, {
@@ -7049,7 +7361,7 @@ window.__ModuleLoader__.load({
7049
7361
  return;
7050
7362
  }
7051
7363
  if (message.kind === "style") {
7052
- const input = isRecord$8(message.input) ? message.input : {};
7364
+ const input = isRecord$9(message.input) ? message.input : {};
7053
7365
  const path = resolveRemotePath(typeof input.path === "string" ? input.path : "/");
7054
7366
  const url = await this.loadStyle(path, void 0);
7055
7367
  this.postResponse(message.id, {
@@ -7059,7 +7371,7 @@ window.__ModuleLoader__.load({
7059
7371
  return;
7060
7372
  }
7061
7373
  if (message.kind === "ws.open") {
7062
- const input = isRecord$8(message.input) ? message.input : {};
7374
+ const input = isRecord$9(message.input) ? message.input : {};
7063
7375
  const opened = this.options.transport.openWebStreamWithId("web.ws.open", {
7064
7376
  sessionId: this.sessionIdValue,
7065
7377
  path: resolveRemotePath(typeof input.path === "string" ? input.path : "/")
@@ -7151,7 +7463,7 @@ window.__ModuleLoader__.load({
7151
7463
  for await (const value of stream) {
7152
7464
  if (!opened) {
7153
7465
  opened = true;
7154
- if (isRecord$8(value) && value.opened === true) {
7466
+ if (isRecord$9(value) && value.opened === true) {
7155
7467
  this.socketReady.get(id)?.resolve();
7156
7468
  this.socketReady.delete(id);
7157
7469
  continue;
@@ -7800,7 +8112,7 @@ window.__ModuleLoader__.load({
7800
8112
  const policy = "default-src 'self' blob: data:; script-src 'self' blob: data: 'unsafe-inline' 'unsafe-eval'; style-src 'self' blob: data: 'unsafe-inline'; img-src 'self' blob: data:; font-src 'self' blob: data:; media-src 'self' blob: data:; worker-src 'self' blob:; connect-src 'none'; frame-src 'self' blob: data:";
7801
8113
  for (const node of documentValue.querySelectorAll("meta[http-equiv]")) if ((node.getAttribute("http-equiv") ?? "").toLowerCase() === "content-security-policy") node.setAttribute("content", policy);
7802
8114
  }
7803
- function isRecord$8(value) {
8115
+ function isRecord$9(value) {
7804
8116
  return typeof value === "object" && value !== null && !Array.isArray(value);
7805
8117
  }
7806
8118
  function toBytes(value) {
@@ -8917,14 +9229,14 @@ window.__ModuleLoader__.load({
8917
9229
  //#endregion
8918
9230
  //#region src/client/cli-catalog.ts
8919
9231
  /** Client 侧访问 Host CLI 命名空间的统一入口。 */
8920
- async function callCliRpc(rpc, action, payload) {
9232
+ async function callCliRpc(rpc, action, payload, signal) {
8921
9233
  let response;
8922
9234
  try {
8923
9235
  debugInfo("codingns4dsh: cli rpc request", {
8924
9236
  channel: CODINGNS_RPC_CHANNEL,
8925
9237
  action
8926
9238
  });
8927
- response = await rpc.call(CODINGNS_RPC_CHANNEL, `cli/${action}`, payload);
9239
+ response = await rpc.call(CODINGNS_RPC_CHANNEL, `cli/${action}`, payload, signal);
8928
9240
  } catch (error) {
8929
9241
  const message = error instanceof Error ? error.message : String(error);
8930
9242
  if (!/HTTP (?:404|405)\b/u.test(message)) throw error;
@@ -8933,7 +9245,7 @@ window.__ModuleLoader__.load({
8933
9245
  error: message
8934
9246
  });
8935
9247
  try {
8936
- response = await rpc.call("/api", `codingns/cli/${action}`, payload);
9248
+ response = await rpc.call("/api", `codingns/cli/${action}`, payload, signal);
8937
9249
  } catch (fallbackError) {
8938
9250
  console.error("codingns4dsh: cli rpc fallback failed", {
8939
9251
  action,
@@ -9101,11 +9413,11 @@ window.__ModuleLoader__.load({
9101
9413
  };
9102
9414
  }
9103
9415
  function readMarker(event) {
9104
- if (!isRecord$7(event)) return null;
9416
+ if (!isRecord$8(event)) return null;
9105
9417
  const data = event.data;
9106
- if (!isRecord$7(data)) return null;
9107
- const candidate = event.type === "assistant/live-chunk" ? isRecord$7(data.chunk) ? data.chunk.codingnsExternalTool : void 0 : event.type === "assistant/attempt" ? data.codingnsExternalTool : void 0;
9108
- if (!isRecord$7(candidate) || candidate.source !== "codingns-external-tool") return null;
9418
+ if (!isRecord$8(data)) return null;
9419
+ const candidate = event.type === "assistant/live-chunk" ? isRecord$8(data.chunk) ? data.chunk.codingnsExternalTool : void 0 : event.type === "assistant/attempt" ? data.codingnsExternalTool : void 0;
9420
+ if (!isRecord$8(candidate) || candidate.source !== "codingns-external-tool") return null;
9109
9421
  if (typeof candidate.callId !== "string" || candidate.callId === "") return null;
9110
9422
  if (typeof candidate.name !== "string" || typeof candidate.arguments !== "string") return null;
9111
9423
  if (candidate.phase !== "start" && candidate.phase !== "update") return null;
@@ -9121,7 +9433,7 @@ window.__ModuleLoader__.load({
9121
9433
  ...typeof candidate.error === "string" ? { error: candidate.error } : {}
9122
9434
  };
9123
9435
  }
9124
- function isRecord$7(value) {
9436
+ function isRecord$8(value) {
9125
9437
  return typeof value === "object" && value !== null && !Array.isArray(value);
9126
9438
  }
9127
9439
  //#endregion
@@ -9199,7 +9511,11 @@ window.__ModuleLoader__.load({
9199
9511
  /** 对话根节点上承载当前会话 id 的 DSH 属性。 */
9200
9512
  const CONTEXT_BREAKDOWN_CONVERSATION_ATTRIBUTE = "data-conversation-session";
9201
9513
  /** 默认隐藏构成明细的适配器:它们自己组装请求,启发式估算与真实上下文不同源。 */
9202
- const DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS = ["codex"];
9514
+ const DEFAULT_CONTEXT_BREAKDOWN_HIDDEN_ADAPTERS = [
9515
+ "codex",
9516
+ "qoder",
9517
+ "qoder-cn"
9518
+ ];
9203
9519
  /** 适配器未知时请求刷新映射的最小间隔,避免面板长时间停留时反复查询。 */
9204
9520
  const ADAPTER_REFRESH_INTERVAL_MS = 5e3;
9205
9521
  const CONTEXT_BREAKDOWN_STYLE_TEXT = `[${CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE}]>dl{display:none!important}[${CONTEXT_BREAKDOWN_HIDDEN_ATTRIBUTE}]>div:nth-of-type(2){display:none!important}`;
@@ -9493,7 +9809,7 @@ window.__ModuleLoader__.load({
9493
9809
  decoratePopupRows(listboxes, options.popupPlaceholder(), iconUrl);
9494
9810
  }
9495
9811
  decorateReferenceChips(dom, iconUrl);
9496
- decorateTimelineMessages(dom, iconUrl, options);
9812
+ decorateTimelineMessages$1(dom, iconUrl, options);
9497
9813
  decorateComposerPlaceholders(dom, options.composerPlaceholder);
9498
9814
  };
9499
9815
  const scan = () => {
@@ -9726,23 +10042,23 @@ window.__ModuleLoader__.load({
9726
10042
  * `user/message` 也会携带这段传输文本。这里仅处理 DSH 的 userRow/bubble,
9727
10043
  * 不触碰 assistant、代码块和普通页面文本,避免把模型内容中的 carrier 改写。
9728
10044
  */
9729
- function decorateTimelineMessages(dom, iconUrl, options) {
10045
+ function decorateTimelineMessages$1(dom, iconUrl, options) {
9730
10046
  const rows = [...dom.querySelectorAll("[class*=\"userRow\"]")];
9731
10047
  for (const row of dom.querySelectorAll("[data-submission-echo]")) if (!rows.includes(row)) rows.push(row);
9732
10048
  for (const row of rows) {
9733
10049
  const bubble = row.querySelector("[class*=\"bubble\"]") ?? row.querySelector("[class*=\"preview\"]");
9734
10050
  if (bubble === null) continue;
9735
- decorateTimelineNode(bubble, dom, iconUrl, options);
10051
+ decorateTimelineNode$1(bubble, dom, iconUrl, options);
9736
10052
  }
9737
10053
  }
9738
- function decorateTimelineNode(node, dom, iconUrl, options) {
10054
+ function decorateTimelineNode$1(node, dom, iconUrl, options) {
9739
10055
  if (node.nodeType === 3) {
9740
10056
  const value = node.nodeValue ?? "";
9741
10057
  if (value.includes("<!--codingns:delegate:") || value.includes(DELEGATION_INSTRUCTION_MARKER)) replaceTimelineTextNode(node, value, dom, iconUrl, options);
9742
10058
  return;
9743
10059
  }
9744
10060
  if (node.nodeType !== 1 || node.hasAttribute("data-codingns-delegate-timeline-chip") || node.hasAttribute("data-codingns-delegation-card")) return;
9745
- for (const child of [...node.childNodes]) decorateTimelineNode(child, dom, iconUrl, options);
10061
+ for (const child of [...node.childNodes]) decorateTimelineNode$1(child, dom, iconUrl, options);
9746
10062
  }
9747
10063
  /** 解析 Host 改写后的委派指令,提取用户真正关心的任务和目标 Agent。 */
9748
10064
  function parseDelegationTimelineCard(value) {
@@ -9956,6 +10272,51 @@ window.__ModuleLoader__.load({
9956
10272
  }));
9957
10273
  //#endregion
9958
10274
  //#region src/client/model-catalog-cache.ts
10275
+ function cacheEntry(rpc, adapterId) {
10276
+ return cacheTimesFor(rpc).get(adapterId);
10277
+ }
10278
+ function cacheTimesFor(rpc) {
10279
+ const key = rpc;
10280
+ let times = modelCatalogCacheTimes.get(key);
10281
+ if (times === void 0) {
10282
+ times = /* @__PURE__ */ new Map();
10283
+ modelCatalogCacheTimes.set(key, times);
10284
+ }
10285
+ return times;
10286
+ }
10287
+ function storeCatalog(rpc, adapterId, value) {
10288
+ getModelCatalogCache(rpc).set(adapterId, value);
10289
+ cacheTimesFor(rpc).set(adapterId, {
10290
+ value,
10291
+ storedAt: Date.now()
10292
+ });
10293
+ }
10294
+ /** 作废某个适配器(或全部适配器)的 Client 目录缓存。 */
10295
+ function invalidateModelCatalogCache(rpc, adapterId) {
10296
+ const cache = getModelCatalogCache(rpc);
10297
+ const times = cacheTimesFor(rpc);
10298
+ if (adapterId === void 0) {
10299
+ cache.clear();
10300
+ times.clear();
10301
+ return;
10302
+ }
10303
+ cache.delete(adapterId);
10304
+ times.delete(adapterId);
10305
+ }
10306
+ function shouldRevalidateModelCatalog(rpc, adapterId, needsRevalidation) {
10307
+ if (!needsRevalidation) return false;
10308
+ const key = rpc;
10309
+ let times = modelCatalogRevalidations.get(key);
10310
+ if (times === void 0) {
10311
+ times = /* @__PURE__ */ new Map();
10312
+ modelCatalogRevalidations.set(key, times);
10313
+ }
10314
+ const last = times.get(adapterId);
10315
+ const now = Date.now();
10316
+ if (last !== void 0 && now - last < 6e4) return false;
10317
+ times.set(adapterId, now);
10318
+ return true;
10319
+ }
9959
10320
  function getModelCatalogCache(rpc) {
9960
10321
  const key = rpc;
9961
10322
  let cache = modelCatalogCaches.get(key);
@@ -9967,8 +10328,8 @@ window.__ModuleLoader__.load({
9967
10328
  }
9968
10329
  function loadModelCatalog(rpc, adapterId, sessionId) {
9969
10330
  const cached = getModelCatalogCache(rpc).get(adapterId);
9970
- if (cached !== void 0 && cached.fallback !== true) return Promise.resolve(cached);
9971
- if (cached?.fallback === true) getModelCatalogCache(rpc).delete(adapterId);
10331
+ if (cached !== void 0 && cached.fallback !== true && isFreshCacheEntry(rpc, adapterId, cached)) return Promise.resolve(cached);
10332
+ if (cached !== void 0) invalidateModelCatalogCache(rpc, adapterId);
9972
10333
  const loads = getModelCatalogLoadCache(rpc);
9973
10334
  const running = loads.get(adapterId);
9974
10335
  if (running !== void 0) return running;
@@ -9976,8 +10337,8 @@ window.__ModuleLoader__.load({
9976
10337
  adapterId,
9977
10338
  ...sessionId === void 0 ? {} : { sessionId }
9978
10339
  }).then((value) => {
9979
- if (value.fallback === true) getModelCatalogCache(rpc).delete(adapterId);
9980
- else getModelCatalogCache(rpc).set(adapterId, value);
10340
+ if (value.fallback === true) invalidateModelCatalogCache(rpc, adapterId);
10341
+ else storeCatalog(rpc, adapterId, value);
9981
10342
  return value;
9982
10343
  }).finally(() => {
9983
10344
  if (loads.get(adapterId) === request) loads.delete(adapterId);
@@ -9985,6 +10346,12 @@ window.__ModuleLoader__.load({
9985
10346
  loads.set(adapterId, request);
9986
10347
  return request;
9987
10348
  }
10349
+ /** 缓存条目是否仍在有效期内;没有时间戳的旧条目按过期处理并触发一次重新读取。 */
10350
+ function isFreshCacheEntry(rpc, adapterId, cached) {
10351
+ const entry = cacheEntry(rpc, adapterId);
10352
+ if (entry === void 0 || entry.value !== cached) return false;
10353
+ return Date.now() - entry.storedAt < MODEL_CATALOG_CACHE_TTL_MS;
10354
+ }
9988
10355
  function getModelCatalogLoadCache(rpc) {
9989
10356
  const key = rpc;
9990
10357
  let cache = modelCatalogLoads.get(key);
@@ -9994,11 +10361,15 @@ window.__ModuleLoader__.load({
9994
10361
  }
9995
10362
  return cache;
9996
10363
  }
9997
- var modelCatalogCaches, modelCatalogLoads;
10364
+ var modelCatalogCaches, modelCatalogLoads, MODEL_CATALOG_CACHE_TTL_MS, modelCatalogCacheTimes, MODEL_CATALOG_REVALIDATE_INTERVAL_MS, modelCatalogRevalidations;
9998
10365
  var init_model_catalog_cache = __esmMin((() => {
9999
10366
  init_cli_catalog();
10000
10367
  modelCatalogCaches = /* @__PURE__ */ new WeakMap();
10001
10368
  modelCatalogLoads = /* @__PURE__ */ new WeakMap();
10369
+ MODEL_CATALOG_CACHE_TTL_MS = 6e4;
10370
+ modelCatalogCacheTimes = /* @__PURE__ */ new WeakMap();
10371
+ MODEL_CATALOG_REVALIDATE_INTERVAL_MS = 6e4;
10372
+ modelCatalogRevalidations = /* @__PURE__ */ new WeakMap();
10002
10373
  }));
10003
10374
  //#endregion
10004
10375
  //#region src/dsh-capabilities/client/primitives-adapter.ts
@@ -10014,6 +10385,28 @@ window.__ModuleLoader__.load({
10014
10385
  const value = _deepseek_ai_dsh_client_ui_primitives;
10015
10386
  return value.IconChevronDownOutlineRegular ?? value.IconChevronDownOutlineMedium ?? value.IconChevronDownOutline14 ?? unavailableIcon;
10016
10387
  }
10388
+ /** 终端工具栏使用的原生刷新图标。 */
10389
+ function resolveRefreshIcon() {
10390
+ const value = _deepseek_ai_dsh_client_ui_primitives;
10391
+ return value.IconRefreshOutlineRegular ?? value.IconRefreshOutlineMedium ?? unavailableIcon;
10392
+ }
10393
+ /** 终端工具栏入口使用的原生工具图标。 */
10394
+ function resolveToolIcon() {
10395
+ const value = _deepseek_ai_dsh_client_ui_primitives;
10396
+ return value.IconSlidersTwoOutlineRegular ?? value.IconSlidersTwoOutlineMedium ?? value.IconSettingsOutlineRegular ?? value.IconSettingsOutlineMedium ?? unavailableIcon;
10397
+ }
10398
+ /** 终端快捷操作的方向图标。 */
10399
+ function resolveTerminalArrowIcon(direction) {
10400
+ const value = _deepseek_ai_dsh_client_ui_primitives;
10401
+ if (direction === "up") return value.IconChevronUpOutlineRegular ?? value.IconChevronUpOutlineMedium ?? unavailableIcon;
10402
+ if (direction === "down") return value.IconChevronDownOutlineRegular ?? value.IconChevronDownOutlineMedium ?? unavailableIcon;
10403
+ if (direction === "left") return value.IconChevronLeftOutlineRegular ?? value.IconChevronLeftOutlineMedium ?? unavailableIcon;
10404
+ return value.IconChevronRightOutlineRegular ?? value.IconChevronRightOutlineMedium ?? unavailableIcon;
10405
+ }
10406
+ /** 终端按键标签使用 DSH 原生 ShortcutKeys,旧版本缺失时退回纯文本。 */
10407
+ function resolveShortcutKeys() {
10408
+ return _deepseek_ai_dsh_client_ui_primitives.ShortcutKeys ?? fallbackShortcutKeys;
10409
+ }
10017
10410
  /**
10018
10411
  * 模型图标:DSH 原生模型选择器在紧凑状态显示的 data 图标(0.1.7 起为 Regular/Medium,
10019
10412
  * 0.1.6 为 16 后缀),插件模型选择器收起时与之保持一致。
@@ -10030,9 +10423,10 @@ window.__ModuleLoader__.load({
10030
10423
  const value = _deepseek_ai_dsh_client_ui_primitives;
10031
10424
  return value.IconShareOutlineRegular ?? value.IconShareOutlineMedium ?? value.IconSendOutlineRegular ?? value.IconSendOutlineMedium ?? resolveDataIcon();
10032
10425
  }
10033
- var unavailableIcon;
10426
+ var unavailableIcon, fallbackShortcutKeys;
10034
10427
  var init_primitives_adapter = __esmMin((() => {
10035
10428
  unavailableIcon = () => null;
10429
+ fallbackShortcutKeys = ({ keys }) => (0, react.createElement)("span", void 0, keys.join(" + "));
10036
10430
  }));
10037
10431
  //#endregion
10038
10432
  //#region src/client/delegate-command.ts
@@ -10071,7 +10465,7 @@ window.__ModuleLoader__.load({
10071
10465
  }
10072
10466
  /** 在 commandUi 就绪的作用域内注册 `/委派`;失败只降级为不注册。 */
10073
10467
  function registerDelegateCommandIn(ctx, options) {
10074
- const commandUi = readCommandUi(ctx);
10468
+ const commandUi = readCommandUi$1(ctx);
10075
10469
  if (commandUi === void 0) {
10076
10470
  debugWarn("codingns4dsh: /委派 未注册,当前 DSH 未提供 commandUi 服务");
10077
10471
  return;
@@ -10101,7 +10495,7 @@ window.__ModuleLoader__.load({
10101
10495
  return list;
10102
10496
  },
10103
10497
  async onSelect(option, session) {
10104
- const input = readSessionInput(ctx, session.sessionId);
10498
+ const input = readSessionInput$1(ctx, session.sessionId);
10105
10499
  if (input === void 0) return;
10106
10500
  const snapshot = input.state.getSnapshot();
10107
10501
  const draft = snapshot.draft ?? "";
@@ -10159,7 +10553,7 @@ window.__ModuleLoader__.load({
10159
10553
  }
10160
10554
  /** 注册结构化 Agent 引用源:显示层使用 chip,提交层再序列化为 Host carrier。 */
10161
10555
  function registerDelegateReferenceSource(ctx, options) {
10162
- const inputTriggers = readService(ctx, "inputTriggers");
10556
+ const inputTriggers = readService$1(ctx, "inputTriggers");
10163
10557
  const source = {
10164
10558
  trigger: "@",
10165
10559
  name: DELEGATE_REFERENCE_SOURCE,
@@ -10262,8 +10656,8 @@ window.__ModuleLoader__.load({
10262
10656
  };
10263
10657
  const inputFor = (sessionId) => {
10264
10658
  try {
10265
- const conversation = readConversation(ctx);
10266
- const actx = readSessions(ctx)?.scope(sessionId);
10659
+ const conversation = readConversation$1(ctx);
10660
+ const actx = readSessions$1(ctx)?.scope(sessionId);
10267
10661
  if (actx !== void 0 && conversation !== void 0) return conversation.input.for(actx);
10268
10662
  return conversation?.input.shell?.(sessionId);
10269
10663
  } catch {
@@ -10627,29 +11021,29 @@ window.__ModuleLoader__.load({
10627
11021
  query: before.slice(hash + 1)
10628
11022
  };
10629
11023
  }
10630
- function readSessionInput(ctx, sessionId) {
11024
+ function readSessionInput$1(ctx, sessionId) {
10631
11025
  try {
10632
- const actx = readSessions(ctx)?.scope(sessionId);
11026
+ const actx = readSessions$1(ctx)?.scope(sessionId);
10633
11027
  if (actx === void 0) return void 0;
10634
- return readConversation(ctx)?.input.for(actx);
11028
+ return readConversation$1(ctx)?.input.for(actx);
10635
11029
  } catch {
10636
11030
  return;
10637
11031
  }
10638
11032
  }
10639
- function readCommandUi(ctx) {
10640
- const service = readService(ctx, "commandUi");
11033
+ function readCommandUi$1(ctx) {
11034
+ const service = readService$1(ctx, "commandUi");
10641
11035
  return service !== void 0 && typeof service.register === "function" ? service : void 0;
10642
11036
  }
10643
- function readSessions(ctx) {
10644
- const service = readService(ctx, "sessions");
11037
+ function readSessions$1(ctx) {
11038
+ const service = readService$1(ctx, "sessions");
10645
11039
  return service !== void 0 && typeof service.scope === "function" ? service : void 0;
10646
11040
  }
10647
- function readConversation(ctx) {
10648
- const service = readService(ctx, "conversation");
11041
+ function readConversation$1(ctx) {
11042
+ const service = readService$1(ctx, "conversation");
10649
11043
  return service !== void 0 && typeof service.input?.for === "function" ? service : void 0;
10650
11044
  }
10651
11045
  /** Cordis 直接读取未声明的服务会抛 without-inject;探测统一走这里。 */
10652
- function readService(ctx, name) {
11046
+ function readService$1(ctx, name) {
10653
11047
  try {
10654
11048
  return ctx.get(name);
10655
11049
  } catch {
@@ -10721,6 +11115,20 @@ window.__ModuleLoader__.load({
10721
11115
  return modelServiceTiers(model).find((tier) => tier.id === id);
10722
11116
  }
10723
11117
  /**
11118
+ * 目录是否「声明了档位、但未确认官方订阅」——即档位开关缺席的原因可能只是判定过期。
11119
+ *
11120
+ * 这一状态有两种来源,界面上无法区分:
11121
+ * - 真实第三方中转/纯 API key:档位确实不会生效,应当持续不展示;
11122
+ * - 判定过期:用户在外部工具(cc-switch 等)里刚切回官方订阅,而页面还持有旧目录。
11123
+ *
11124
+ * 因此它只用来决定「值得再探测一次」,不能用来决定「应该展示开关」。重新探测由
11125
+ * Host 的 Provider 配置指纹保证廉价:配置没变时 Host 直接返回缓存,不会重启 CLI。
11126
+ */
11127
+ function needsServiceTierRevalidation(catalog) {
11128
+ if (catalog === null || catalog.officialSubscription === true) return false;
11129
+ return catalog.groups.some((group) => group.models.some((model) => modelServiceTiers(model).length > 0));
11130
+ }
11131
+ /**
10724
11132
  * 在“只改了其它字段”的部分更新里保留当前档位选择。
10725
11133
  *
10726
11134
  * 这是本功能最容易被写坏的地方:档位与模型/思考等级共用同一份会话配置,任何一次
@@ -10748,7 +11156,9 @@ window.__ModuleLoader__.load({
10748
11156
  var cli_slots_exports = /* @__PURE__ */ __exportAll({
10749
11157
  AgentSlot: () => AgentSlot,
10750
11158
  ModelSlot: () => ModelSlot,
10751
- registerCliConversationSlots: () => registerCliConversationSlots
11159
+ cliSessionSelectionRevision: () => cliSessionSelectionRevision,
11160
+ registerCliConversationSlots: () => registerCliConversationSlots,
11161
+ waitForCliSessionSelection: () => waitForCliSessionSelection
10752
11162
  });
10753
11163
  function installComposerStyles() {
10754
11164
  if (typeof document === "undefined" || document.querySelector(`style[data-plugin-css="${CLI_STYLE_ID}"]`) !== null) return;
@@ -10781,6 +11191,20 @@ window.__ModuleLoader__.load({
10781
11191
  ].join("");
10782
11192
  document.head.appendChild(style);
10783
11193
  }
11194
+ /** 等待会话 Agent 选择写入 Host,避免切换后立即打开命令菜单仍读到旧 Agent。 */
11195
+ async function waitForCliSessionSelection(sessionId) {
11196
+ if (sessionId.trim() === "") return;
11197
+ for (;;) {
11198
+ const pending = selectionUpdates.get(sessionId);
11199
+ if (pending === void 0) return;
11200
+ await pending.promise;
11201
+ if (selectionUpdates.get(sessionId) === pending) return;
11202
+ }
11203
+ }
11204
+ /** 读取当前会话 Agent 选择版本,供按会话缓存的目录在切换 Agent 后失效。 */
11205
+ function cliSessionSelectionRevision(sessionId) {
11206
+ return selectionRevisions.get(sessionId) ?? 0;
11207
+ }
10784
11208
  /** 在 Agent 和模型两个 Slot 之间共享当前会话选择。 */
10785
11209
  function useSelection(sessionId, rpc) {
10786
11210
  const [selection, setSelection] = (0, react.useState)(() => sessionId ? selections.get(sessionId) ?? DEFAULT_SELECTION : DEFAULT_SELECTION);
@@ -11084,6 +11508,7 @@ window.__ModuleLoader__.load({
11084
11508
  });
11085
11509
  const catalog = catalogState?.adapterId === selection.adapterId ? catalogState.value : null;
11086
11510
  const loading = selection.adapterId !== "dsh" && catalog === null && refreshingAdapterId === selection.adapterId;
11511
+ const serviceTierRevalidation = needsServiceTierRevalidation(catalog);
11087
11512
  (0, react.useEffect)(() => {
11088
11513
  if (selection.adapterId === "dsh") {
11089
11514
  setCatalogState(null);
@@ -11099,6 +11524,7 @@ window.__ModuleLoader__.load({
11099
11524
  value: cached
11100
11525
  });
11101
11526
  setRefreshingAdapterId(adapterId);
11527
+ if (shouldRevalidateModelCatalog(props.rpc, adapterId, needsServiceTierRevalidation(cached ?? null))) invalidateModelCatalogCache(props.rpc, adapterId);
11102
11528
  loadModelCatalog(props.rpc, adapterId, sessionId).then((value) => {
11103
11529
  if (!active) return;
11104
11530
  setCatalogState({
@@ -11119,7 +11545,7 @@ window.__ModuleLoader__.load({
11119
11545
  if (model.id !== currentSelection.modelId || effort !== currentSelection.effortId || tier !== currentSelection.serviceTierId) update({
11120
11546
  adapterId,
11121
11547
  modelId: model.id,
11122
- ...effort ? { effortId: effort } : {},
11548
+ effortId: effort ?? "default",
11123
11549
  ...tier ? { serviceTierId: tier } : {}
11124
11550
  });
11125
11551
  };
@@ -11149,13 +11575,33 @@ window.__ModuleLoader__.load({
11149
11575
  sessionId,
11150
11576
  catalogRetry
11151
11577
  ]);
11578
+ /**
11579
+ * 档位判定处于「未确认官方订阅」时周期复检。
11580
+ *
11581
+ * 用户在 cc-switch 之类的工具里切回官方订阅不会触发页面事件,唯一能发现变化的
11582
+ * 方式就是再探测一次。这里只负责到点触发一次目录重读,是否真正作废缓存交给主
11583
+ * effect 的节流判定——若在本 effect 里调用节流函数,会先消耗掉窗口,紧接着主
11584
+ * effect 的判定就被挡下,反而不会重新请求。
11585
+ *
11586
+ * 探测本身很廉价:Host 比对配置指纹后,配置没变就直接返回缓存,不会重启 CLI。
11587
+ * 真实第三方接入会长期停在该状态,因此轮询持续存在,代价是每分钟一次缓存命中的
11588
+ * RPC;这换取的是用户切换供应商后无需刷新页面。
11589
+ */
11590
+ (0, react.useEffect)(() => {
11591
+ if (!serviceTierRevalidation) return;
11592
+ const timer = setInterval(() => {
11593
+ setCatalogRetry((value) => value + 1);
11594
+ }, MODEL_CATALOG_REVALIDATE_INTERVAL_MS + 5e3);
11595
+ return () => clearInterval(timer);
11596
+ }, [selection.adapterId, serviceTierRevalidation]);
11152
11597
  (0, react.useEffect)(() => {
11153
11598
  if (selection.adapterId === "dsh") setOpen(false);
11154
11599
  }, [selection.adapterId]);
11155
11600
  if (selection.adapterId === "dsh") return null;
11156
11601
  const model = catalog === null ? void 0 : findModel(catalog, selection.modelId) ?? firstModel(catalog);
11157
11602
  const efforts = model?.efforts ?? [];
11158
- const effortValue = selection.effortId ?? (efforts.length > 0 ? defaultEffort(efforts) : void 0) ?? "default";
11603
+ const storedEffort = selection.effortId;
11604
+ const effortValue = storedEffort !== void 0 && (model === void 0 || efforts.includes(storedEffort)) ? storedEffort : defaultEffort(efforts) ?? "default";
11159
11605
  const modelLabel = model?.name ?? (loading ? t("cli.loadingModel") : t("cli.noModelsAvailable"));
11160
11606
  const effortLabel = effortDisplayName(model, effortValue, t("cli.defaultEffort"));
11161
11607
  const modelUnavailable = model === void 0;
@@ -11166,7 +11612,7 @@ window.__ModuleLoader__.load({
11166
11612
  update({
11167
11613
  adapterId: selection.adapterId,
11168
11614
  modelId: next.id,
11169
- ...nextEffort ? { effortId: nextEffort } : {},
11615
+ effortId: nextEffort ?? "default",
11170
11616
  ...nextTier ? { serviceTierId: nextTier } : {}
11171
11617
  });
11172
11618
  setOpen(false);
@@ -11501,6 +11947,7 @@ window.__ModuleLoader__.load({
11501
11947
  minWidth: 240,
11502
11948
  maxWidth: "min(420px, calc(100vw - 32px))",
11503
11949
  maxHeight: "min(360px, calc(100vh - 96px))",
11950
+ overflowX: "hidden",
11504
11951
  overflowY: "auto",
11505
11952
  padding: 4,
11506
11953
  border: 0,
@@ -11509,6 +11956,7 @@ window.__ModuleLoader__.load({
11509
11956
  nativeMenuCellStyle = {
11510
11957
  width: "100%",
11511
11958
  minHeight: 40,
11959
+ boxSizing: "border-box",
11512
11960
  color: "inherit",
11513
11961
  cursor: "pointer",
11514
11962
  background: "transparent",
@@ -11525,6 +11973,7 @@ window.__ModuleLoader__.load({
11525
11973
  serviceTierRowStyle = {
11526
11974
  width: "100%",
11527
11975
  minHeight: 40,
11976
+ boxSizing: "border-box",
11528
11977
  color: "inherit",
11529
11978
  borderRadius: 10,
11530
11979
  padding: "0 10px",
@@ -12229,7 +12678,7 @@ window.__ModuleLoader__.load({
12229
12678
  ...value,
12230
12679
  defaultProfile: event.currentTarget.value
12231
12680
  }),
12232
- style: fieldStyle$1
12681
+ style: fieldStyle$2
12233
12682
  }, ...profileOptions(value.defaultProfile, hostStatus, t).map((profile) => (0, react.createElement)("option", {
12234
12683
  key: profile.profileId,
12235
12684
  value: profile.profileId,
@@ -12244,7 +12693,7 @@ window.__ModuleLoader__.load({
12244
12693
  ...value,
12245
12694
  bindingScope: event.currentTarget.value === "session" ? "session" : "workspace"
12246
12695
  }),
12247
- style: fieldStyle$1
12696
+ style: fieldStyle$2
12248
12697
  }, (0, react.createElement)("option", { value: "workspace" }, t("terminal.workspaceBinding")), (0, react.createElement)("option", { value: "session" }, t("terminal.sessionBinding")))), (0, react.createElement)("div", { style: dshSettingsSectionHeaderStyle }, (0, react.createElement)("h3", { style: {
12249
12698
  margin: 0,
12250
12699
  color: dshThemeColor.labelPrimary,
@@ -12306,7 +12755,7 @@ window.__ModuleLoader__.load({
12306
12755
  value: appearance.cursorStyle,
12307
12756
  disabled,
12308
12757
  onChange: (event) => updateAppearance({ cursorStyle: parseCursorStyle(event.currentTarget.value) }),
12309
- style: fieldStyle$1
12758
+ style: fieldStyle$2
12310
12759
  }, (0, react.createElement)("option", { value: "block" }, t("terminal.cursorBlock")), (0, react.createElement)("option", { value: "bar" }, t("terminal.cursorBar")), (0, react.createElement)("option", { value: "underline" }, t("terminal.cursorUnderline")))), (0, react.createElement)(NumberField, {
12311
12760
  label: t("terminal.scrollback"),
12312
12761
  value: appearance.scrollback,
@@ -12355,7 +12804,7 @@ window.__ModuleLoader__.load({
12355
12804
  disabled,
12356
12805
  onChange: (event) => onChange(event.currentTarget.value === "" ? null : Number(event.currentTarget.value)),
12357
12806
  style: {
12358
- ...fieldStyle$1,
12807
+ ...fieldStyle$2,
12359
12808
  flex: 1,
12360
12809
  minWidth: 0
12361
12810
  }
@@ -12419,7 +12868,7 @@ window.__ModuleLoader__.load({
12419
12868
  function parseCursorStyle(value) {
12420
12869
  return value === "block" || value === "bar" || value === "underline" ? value : null;
12421
12870
  }
12422
- const fieldStyle$1 = dshSettingsFieldStyle;
12871
+ const fieldStyle$2 = dshSettingsFieldStyle;
12423
12872
  const buttonStyle = {
12424
12873
  ...dshSettingsButtonStyle,
12425
12874
  flex: "0 0 auto",
@@ -12483,17 +12932,17 @@ window.__ModuleLoader__.load({
12483
12932
  */
12484
12933
  function readNativeWorkspaceListStore(uiContext) {
12485
12934
  const store = readNativeService(uiContext, "workspaces", "list");
12486
- if (!isRecord$6(store) || typeof store.getSnapshot !== "function" || typeof store.subscribe !== "function") return void 0;
12935
+ if (!isRecord$7(store) || typeof store.getSnapshot !== "function" || typeof store.subscribe !== "function") return void 0;
12487
12936
  return store;
12488
12937
  }
12489
12938
  /** 读取原生 Workspace 快照;形状不符或服务缺失时返回 undefined,调用方退回原生 Remote。 */
12490
12939
  function readNativeWorkspaceSnapshot(uiContext) {
12491
12940
  const snapshot = readNativeWorkspaceListStore(uiContext)?.getSnapshot();
12492
- const record = isRecord$6(snapshot) ? snapshot : void 0;
12941
+ const record = isRecord$7(snapshot) ? snapshot : void 0;
12493
12942
  if (record === void 0 || !Array.isArray(record.items)) return void 0;
12494
12943
  return {
12495
12944
  items: record.items.flatMap((item) => {
12496
- const value = isRecord$6(item) ? item : void 0;
12945
+ const value = isRecord$7(item) ? item : void 0;
12497
12946
  const workspaceId = readText(value?.workspaceId);
12498
12947
  if (workspaceId === void 0) return [];
12499
12948
  const path = readText(value?.path);
@@ -12516,7 +12965,7 @@ window.__ModuleLoader__.load({
12516
12965
  if (typeof getter !== "function") return void 0;
12517
12966
  const service = getter.call(uiContext, name);
12518
12967
  if (field === void 0) return service;
12519
- return isRecord$6(service) ? service[field] : void 0;
12968
+ return isRecord$7(service) ? service[field] : void 0;
12520
12969
  } catch {
12521
12970
  return;
12522
12971
  }
@@ -12530,7 +12979,7 @@ window.__ModuleLoader__.load({
12530
12979
  return text === void 0 ? [] : [text];
12531
12980
  }) : [];
12532
12981
  }
12533
- function isRecord$6(value) {
12982
+ function isRecord$7(value) {
12534
12983
  return typeof value === "object" && value !== null && !Array.isArray(value);
12535
12984
  }
12536
12985
  //#endregion
@@ -12564,7 +13013,7 @@ window.__ModuleLoader__.load({
12564
13013
  const fiberKey = Object.getOwnPropertyNames(row).find((key) => key.startsWith("__reactFiber$"));
12565
13014
  if (fiberKey === void 0) return void 0;
12566
13015
  let current = carrier[fiberKey];
12567
- for (let depth = 0; depth < 32 && isRecord$5(current); depth += 1) {
13016
+ for (let depth = 0; depth < 32 && isRecord$6(current); depth += 1) {
12568
13017
  const fiber = current;
12569
13018
  const sessionId = sessionIdFromProps(fiber.memoizedProps) ?? sessionIdFromProps(fiber.pendingProps);
12570
13019
  if (sessionId !== void 0) return sessionId;
@@ -12572,15 +13021,15 @@ window.__ModuleLoader__.load({
12572
13021
  }
12573
13022
  }
12574
13023
  function sessionIdFromProps(value) {
12575
- if (!isRecord$5(value)) return void 0;
13024
+ if (!isRecord$6(value)) return void 0;
12576
13025
  for (const key of ["node", "result"]) {
12577
13026
  const candidate = value[key];
12578
- if (!isRecord$5(candidate) || typeof candidate.id !== "string") continue;
13027
+ if (!isRecord$6(candidate) || typeof candidate.id !== "string") continue;
12579
13028
  const sessionId = candidate.id.trim();
12580
13029
  if (sessionId !== "") return sessionId;
12581
13030
  }
12582
13031
  }
12583
- function isRecord$5(value) {
13032
+ function isRecord$6(value) {
12584
13033
  return typeof value === "object" && value !== null && !Array.isArray(value);
12585
13034
  }
12586
13035
  //#endregion
@@ -12947,7 +13396,7 @@ window.__ModuleLoader__.load({
12947
13396
  }
12948
13397
  function findWorkspaceIdInFiber(value) {
12949
13398
  let current = value;
12950
- for (let depth = 0; depth < 24 && isRecord$4(current); depth += 1) {
13399
+ for (let depth = 0; depth < 24 && isRecord$5(current); depth += 1) {
12951
13400
  for (const props of [current.memoizedProps, current.pendingProps]) {
12952
13401
  const id = findWorkspaceIdInProps(props);
12953
13402
  if (id !== void 0) return id;
@@ -12956,7 +13405,7 @@ window.__ModuleLoader__.load({
12956
13405
  }
12957
13406
  }
12958
13407
  function findWorkspaceIdInProps(value) {
12959
- if (!isRecord$4(value)) return void 0;
13408
+ if (!isRecord$5(value)) return void 0;
12960
13409
  for (const key of ["workspaceId", "workspaceID"]) {
12961
13410
  const id = readString$1(value[key]);
12962
13411
  if (id !== void 0) return id;
@@ -13355,7 +13804,7 @@ window.__ModuleLoader__.load({
13355
13804
  dom.querySelector(`[${WORKSPACE_SESSION_ARCHIVE_MODAL_ATTRIBUTE}]`)?.remove();
13356
13805
  }
13357
13806
  function normalizeRemote(value) {
13358
- if (!isRecord$4(value)) return {};
13807
+ if (!isRecord$5(value)) return {};
13359
13808
  const workspace = asRecord$4(readRemoteProperty(value, "workspace")) ?? asRecord$4(readRemoteProperty(value, "workspaceController"));
13360
13809
  const session = asRecord$4(readRemoteProperty(value, "session")) ?? asRecord$4(readRemoteProperty(value, "sessionController"));
13361
13810
  return {
@@ -13381,7 +13830,7 @@ window.__ModuleLoader__.load({
13381
13830
  }) : [];
13382
13831
  }
13383
13832
  function asRecord$4(value) {
13384
- return isRecord$4(value) ? value : void 0;
13833
+ return isRecord$5(value) ? value : void 0;
13385
13834
  }
13386
13835
  /** DSH Remote 的直接调用返回 RemoteResult;测试桩和旧宿主可能直接返回 value。 */
13387
13836
  function unwrapRemoteValue$1(value) {
@@ -13389,7 +13838,7 @@ window.__ModuleLoader__.load({
13389
13838
  if (record === void 0 || typeof record.ok !== "boolean") return value;
13390
13839
  return record.ok ? record.value : void 0;
13391
13840
  }
13392
- function isRecord$4(value) {
13841
+ function isRecord$5(value) {
13393
13842
  return typeof value === "object" && value !== null && !Array.isArray(value);
13394
13843
  }
13395
13844
  //#endregion
@@ -14110,6 +14559,19 @@ window.__ModuleLoader__.load({
14110
14559
  //#region src/client/features/workspace-session-enhancement-panel.ts
14111
14560
  init_theme();
14112
14561
  init_locale();
14562
+ /** 用量查询设置入口的图标按钮:沿用共享按钮表面,只收成方形并居中图标。 */
14563
+ const usageSettingsIconButtonStyle = {
14564
+ ...dshSettingsButtonStyle,
14565
+ flex: "0 0 auto",
14566
+ width: 36,
14567
+ height: 36,
14568
+ minHeight: 36,
14569
+ padding: 0,
14570
+ boxSizing: "border-box",
14571
+ display: "inline-flex",
14572
+ alignItems: "center",
14573
+ justifyContent: "center"
14574
+ };
14113
14575
  /** 工作区会话增强的单列设置面板。 */
14114
14576
  function WorkspaceSessionEnhancementPanel({ services, enabled, snapshot, notify }) {
14115
14577
  const t = useCodingNsTranslator(services.locale);
@@ -14176,6 +14638,25 @@ window.__ModuleLoader__.load({
14176
14638
  display: "block",
14177
14639
  fontSize: 13,
14178
14640
  lineHeight: 1.4
14641
+ } }, t("workspace.showSkillQuickReference")), (0, react.createElement)("span", { style: {
14642
+ display: "block",
14643
+ marginTop: 3,
14644
+ ...dshSettingsHelpStyle
14645
+ } }, t("workspace.skillQuickReferenceDescription"))), (0, react.createElement)("input", {
14646
+ type: "checkbox",
14647
+ role: "switch",
14648
+ "aria-label": t("workspace.showSkillQuickReference"),
14649
+ checked: value.showSkillQuickReference,
14650
+ disabled,
14651
+ onChange: (event) => updateSetting("showSkillQuickReference", event.currentTarget.checked),
14652
+ style: {
14653
+ flex: "0 0 auto",
14654
+ accentColor: dshThemeColor.accent
14655
+ }
14656
+ })), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
14657
+ display: "block",
14658
+ fontSize: 13,
14659
+ lineHeight: 1.4
14179
14660
  } }, t("workspace.showWorkspaceHiding")), (0, react.createElement)("span", { style: {
14180
14661
  display: "block",
14181
14662
  marginTop: 3,
@@ -14222,10 +14703,11 @@ window.__ModuleLoader__.load({
14222
14703
  "aria-haspopup": "dialog",
14223
14704
  "aria-expanded": usageSettingsOpen,
14224
14705
  "aria-label": t("workspace.subscriptionUsageSettings"),
14706
+ title: t("workspace.subscriptionUsageSettings"),
14225
14707
  disabled,
14226
14708
  onClick: () => setUsageSettingsOpen(true),
14227
- style: dshSettingsButtonStyle
14228
- }, t("workspace.subscriptionUsageSettings"))), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
14709
+ style: usageSettingsIconButtonStyle
14710
+ }, createUsageSettingsGearIcon())), (0, react.createElement)("label", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
14229
14711
  display: "block",
14230
14712
  fontSize: 13,
14231
14713
  lineHeight: 1.4
@@ -14289,6 +14771,40 @@ window.__ModuleLoader__.load({
14289
14771
  onClose: () => setUsageSettingsOpen(false)
14290
14772
  }));
14291
14773
  }
14774
+ /**
14775
+ * 用量查询设置入口的齿轮图标。
14776
+ *
14777
+ * 文字收成图形后,可访问名称由按钮自身的 aria-label/title 保留;图标只做装饰,
14778
+ * 对读屏和指针都隐藏,避免重复朗读和吞掉点击。
14779
+ */
14780
+ function createUsageSettingsGearIcon() {
14781
+ return (0, react.createElement)("svg", {
14782
+ width: 17,
14783
+ height: 17,
14784
+ viewBox: "0 0 24 24",
14785
+ fill: "none",
14786
+ focusable: false,
14787
+ "aria-hidden": true,
14788
+ style: {
14789
+ display: "block",
14790
+ pointerEvents: "none"
14791
+ }
14792
+ }, (0, react.createElement)("path", {
14793
+ d: "M19.43 12.98c.04-.32.07-.65.07-.98s-.02-.66-.07-.98l2.11-1.65a.5.5 0 0 0 .12-.64l-2-3.46a.5.5 0 0 0-.61-.22l-2.49 1a7.4 7.4 0 0 0-1.7-.98L14.5 2.42A.49.49 0 0 0 14.01 2h-4a.49.49 0 0 0-.49.42L9.14 5.07c-.61.25-1.18.58-1.7.98l-2.49-1a.5.5 0 0 0-.61.22l-2 3.46a.5.5 0 0 0 .12.64l2.11 1.65c-.04.32-.08.65-.08.98s.03.66.08.98l-2.11 1.65a.5.5 0 0 0-.12.64l2 3.46c.12.21.37.3.61.22l2.49-1c.52.4 1.09.73 1.7.98l.38 2.65c.04.24.25.42.49.42h4c.24 0 .45-.18.49-.42l.38-2.65c.61-.25 1.18-.58 1.7-.98l2.49 1c.24.09.49-.01.61-.22l2-3.46a.5.5 0 0 0-.12-.64l-2.11-1.65Z",
14794
+ fill: "currentColor",
14795
+ fillOpacity: .22,
14796
+ stroke: "currentColor",
14797
+ strokeWidth: 1.35,
14798
+ strokeLinejoin: "round"
14799
+ }), (0, react.createElement)("circle", {
14800
+ cx: 12,
14801
+ cy: 12,
14802
+ r: 3.1,
14803
+ fill: dshThemeColor.accent,
14804
+ stroke: "currentColor",
14805
+ strokeWidth: 1.15
14806
+ }));
14807
+ }
14292
14808
  //#endregion
14293
14809
  //#region src/client/subscription-slot.ts
14294
14810
  init_cli_catalog();
@@ -14404,7 +14920,7 @@ window.__ModuleLoader__.load({
14404
14920
  setProviderId(selection.providerId ?? null);
14405
14921
  }
14406
14922
  const intervalMins = props.getRefreshIntervalMins?.() ?? DEFAULT_SUBSCRIPTION_USAGE_SETTINGS.refreshIntervalMins;
14407
- const cacheKey = `${adapterId}|${selection.providerId ?? ""}`;
14923
+ const cacheKey = `${adapterId}|${selection.providerId ?? ""}|${selection.modelId ?? ""}`;
14408
14924
  const cached = subscriptionUsageCache.get(cacheKey);
14409
14925
  if (cached !== void 0 && cached.usage.provider?.capability !== "new-api" && isSubscriptionUsageFresh(cached.capturedAt, Date.now(), intervalMins)) {
14410
14926
  if (active) setUsage(cached.usage);
@@ -14412,7 +14928,8 @@ window.__ModuleLoader__.load({
14412
14928
  }
14413
14929
  const next = await callCliRpc(props.rpc, "subscription", {
14414
14930
  adapterId,
14415
- ...selection.providerId ? { providerId: selection.providerId } : {}
14931
+ ...selection.providerId ? { providerId: selection.providerId } : {},
14932
+ ...selection.modelId ? { modelId: selection.modelId } : {}
14416
14933
  });
14417
14934
  if (next !== null) subscriptionUsageCache.set(cacheKey, {
14418
14935
  usage: next,
@@ -14649,16 +15166,8 @@ window.__ModuleLoader__.load({
14649
15166
  window: usage.monthly
14650
15167
  }
14651
15168
  ];
14652
- const credits = usage.credits ?? null;
14653
- const creditText = credits === null ? null : credits.unlimited ? t("usage.creditUnlimited") : credits.balance !== null ? formatCreditBalance(credits.balance) : credits.hasCredits ? "0.00" : "--";
14654
- const expiries = usage.resetCredits === null ? [] : usage.resetCredits.credits.map((credit) => credit.expiresAt).filter((expiry) => expiry !== null).sort((left, right) => left - right);
14655
- return (0, react.createElement)("div", {
14656
- className: "codingns4dsh-subscription-popover",
14657
- role: "dialog",
14658
- "aria-label": t("usage.popoverSubscriptionUsage", { provider: providerName }),
14659
- style: subscriptionPopoverStyle
14660
- }, (0, react.createElement)("div", { style: popoverHeadingStyle }, (0, react.createElement)("strong", void 0, t("usage.popoverSubscriptionTitle", { provider: providerName })), usage.planType && (0, react.createElement)("span", { style: { color: dshThemeColor.labelTertiary } }, formatPlanType(usage.planType))), ...windows.map(({ id, label, window }) => window === null ? null : (0, react.createElement)("section", {
14661
- key: id,
15169
+ const renderWindow = (key, label, window) => window === null ? null : (0, react.createElement)("section", {
15170
+ key,
14662
15171
  style: windowStyle
14663
15172
  }, (0, react.createElement)("div", { style: windowHeadingStyle }, (0, react.createElement)("span", void 0, label), (0, react.createElement)("span", void 0, `${formatPercent$1(window.remainingPercent)}%`)), (0, react.createElement)("div", {
14664
15173
  role: "progressbar",
@@ -14673,7 +15182,25 @@ window.__ModuleLoader__.load({
14673
15182
  }, (0, react.createElement)("span", { style: {
14674
15183
  ...barFillStyle,
14675
15184
  width: `${window.remainingPercent}%`
14676
- } })), window.resetsAt !== null && (0, react.createElement)("div", { style: resetStyle }, t("usage.resetsIn", { time: formatCountdown(window.resetsAt, t) })))), (creditText !== null || usage.resetCredits !== null) && (0, react.createElement)("section", { style: resetCreditsSectionStyle }, creditText !== null && (0, react.createElement)("div", { style: resetCreditsRowStyle }, (0, react.createElement)("span", void 0, t("usage.creditBalanceLabel")), (0, react.createElement)("span", { style: resetCreditsValueStyle }, creditText)), usage.resetCredits !== null && (0, react.createElement)("div", { style: resetCreditsRowStyle }, (0, react.createElement)("span", void 0, t("usage.resetCreditsLabel")), (0, react.createElement)("span", { style: resetCreditsValueGroupStyle }, (0, react.createElement)("span", { style: resetCreditsValueStyle }, t("usage.resetCreditsCount", { count: usage.resetCredits.availableCount })), reset !== null && (0, react.createElement)("button", {
15185
+ } })), window.resetsAt !== null && (0, react.createElement)("div", { style: resetStyle }, t("usage.resetsIn", { time: formatCountdown(window.resetsAt, t) })));
15186
+ const quotaGroups = usage.groups ?? [];
15187
+ const windowSections = quotaGroups.length > 0 ? quotaGroups.flatMap((group) => [(0, react.createElement)("div", {
15188
+ key: `group:${group.id}`,
15189
+ style: quotaGroupTitleStyle
15190
+ }, formatSubscriptionGroupLabel(group, t)), ...group.windows.map((entry) => renderWindow(`group:${group.id}:${entry.kind}:${entry.label ?? ""}`, formatSubscriptionGroupWindowLabel(entry, t), entry.window))]) : windows.map(({ id, label, window }) => renderWindow(id, label, window));
15191
+ const credits = usage.credits ?? null;
15192
+ const creditText = credits === null ? null : credits.unlimited ? t("usage.creditUnlimited") : credits.balance !== null ? formatCreditBalance(credits.balance) : credits.hasCredits ? "0.00" : "--";
15193
+ const expiries = usage.resetCredits === null ? [] : usage.resetCredits.credits.map((credit) => credit.expiresAt).filter((expiry) => expiry !== null).sort((left, right) => left - right);
15194
+ const accountName = usage.accountName?.trim() || null;
15195
+ const planLabel = usage.planType === null ? null : formatPlanType(usage.planType);
15196
+ const paidPlanLabel = usage.paidPlanType?.trim() || null;
15197
+ const planText = planLabel === null ? paidPlanLabel : paidPlanLabel === null || paidPlanLabel === planLabel ? planLabel : `${planLabel} · ${paidPlanLabel}`;
15198
+ return (0, react.createElement)("div", {
15199
+ className: "codingns4dsh-subscription-popover",
15200
+ role: "dialog",
15201
+ "aria-label": t("usage.popoverSubscriptionUsage", { provider: providerName }),
15202
+ style: subscriptionPopoverStyle
15203
+ }, (0, react.createElement)("div", { style: popoverHeadingStyle }, (0, react.createElement)("div", { style: popoverHeadingTextStyle }, (0, react.createElement)("strong", void 0, t("usage.popoverSubscriptionTitle", { provider: providerName })), accountName !== null && (0, react.createElement)("span", { style: accountMetaStyle }, accountName)), planText !== null && (0, react.createElement)("span", { style: { color: dshThemeColor.labelTertiary } }, planText)), ...windowSections, (creditText !== null || usage.resetCredits !== null) && (0, react.createElement)("section", { style: resetCreditsSectionStyle }, creditText !== null && (0, react.createElement)("div", { style: resetCreditsRowStyle }, (0, react.createElement)("span", void 0, t("usage.creditBalanceLabel")), (0, react.createElement)("span", { style: resetCreditsValueStyle }, creditText)), usage.resetCredits !== null && (0, react.createElement)("div", { style: resetCreditsRowStyle }, (0, react.createElement)("span", void 0, t("usage.resetCreditsLabel")), (0, react.createElement)("span", { style: resetCreditsValueGroupStyle }, (0, react.createElement)("span", { style: resetCreditsValueStyle }, t("usage.resetCreditsCount", { count: usage.resetCredits.availableCount })), reset !== null && (0, react.createElement)("button", {
14677
15204
  type: "button",
14678
15205
  className: "codingns4dsh-subscription-reset",
14679
15206
  disabled: !reset.enabled,
@@ -14768,6 +15295,19 @@ window.__ModuleLoader__.load({
14768
15295
  if (durationMins % 60 === 0) return t("usage.windowHours", { count: durationMins / 60 });
14769
15296
  return t("usage.windowMinutes", { count: durationMins });
14770
15297
  }
15298
+ /** 上游分组名本地化:只有已知分组用词条,其余原样显示上游名称。 */
15299
+ function formatSubscriptionGroupLabel(group, t) {
15300
+ if (group.id === "gemini") return t("usage.quotaGroupGemini");
15301
+ if (group.id === "third-party") return t("usage.quotaGroupThirdParty");
15302
+ return group.displayName;
15303
+ }
15304
+ /** 窗口文案按类型取词条,未知类型回退到上游标签。 */
15305
+ function formatSubscriptionGroupWindowLabel(entry, t) {
15306
+ if (entry.kind === "five-hour") return t("usage.windowFiveHour");
15307
+ if (entry.kind === "weekly") return t("usage.windowWeekly");
15308
+ if (entry.kind === "monthly") return t("usage.windowMonthly");
15309
+ return entry.label ?? "";
15310
+ }
14771
15311
  /**
14772
15312
  * New-API 的普通 Key 没有 Sub2API 那种统一的完整统计对象。
14773
15313
  * 这里只投影读取器明确拿到的真实数值,隐藏内部 quota、Key 状态和占位文本,
@@ -15047,7 +15587,7 @@ window.__ModuleLoader__.load({
15047
15587
  }
15048
15588
  /** 已具备用量读取契约的适配器才挂载底部订阅入口。 */
15049
15589
  function isSubscriptionAdapter(adapterId) {
15050
- return adapterId === "command-code" || adapterId === "codex" || adapterId === "claude-code" || adapterId === "codebuddy" || adapterId === "codebuddy-cn" || adapterId === "dsh" || adapterId === "grok" || adapterId === "kimi" || adapterId === "opencode" || adapterId === "workbuddy" || adapterId === "zcode";
15590
+ return adapterId === "antigravity" || adapterId === "command-code" || adapterId === "codex" || adapterId === "claude-code" || adapterId === "codebuddy" || adapterId === "codebuddy-cn" || adapterId === "dsh" || adapterId === "grok" || adapterId === "kimi" || adapterId === "opencode" || adapterId === "qoder" || adapterId === "qoder-cn" || adapterId === "workbuddy" || adapterId === "zcode";
15051
15591
  }
15052
15592
  function isRemoteWebContext() {
15053
15593
  return globalThis.__CODINGNS4DSH_REMOTE_WEB_CONTEXT__ === true;
@@ -15067,6 +15607,8 @@ window.__ModuleLoader__.load({
15067
15607
  case "grok": return "Grok";
15068
15608
  case "kimi": return "Kimi Code";
15069
15609
  case "opencode": return "OpenCode";
15610
+ case "qoder": return "Qoder";
15611
+ case "qoder-cn": return "Qoder CN";
15070
15612
  case "zcode": return "ZCode";
15071
15613
  case "codebuddy": return "CodeBuddy";
15072
15614
  case "codebuddy-cn": return "CodeBuddy";
@@ -15390,6 +15932,19 @@ window.__ModuleLoader__.load({
15390
15932
  borderBottom: `1px solid ${dshThemeColor.border}`,
15391
15933
  fontSize: 14
15392
15934
  };
15935
+ const popoverHeadingTextStyle = {
15936
+ display: "grid",
15937
+ gap: 2,
15938
+ minWidth: 0
15939
+ };
15940
+ const accountMetaStyle = {
15941
+ overflow: "hidden",
15942
+ color: dshThemeColor.labelTertiary,
15943
+ fontSize: 11,
15944
+ lineHeight: "16px",
15945
+ textOverflow: "ellipsis",
15946
+ whiteSpace: "nowrap"
15947
+ };
15393
15948
  const upstreamMetaStyle = {
15394
15949
  display: "flex",
15395
15950
  alignItems: "center",
@@ -15446,6 +16001,13 @@ window.__ModuleLoader__.load({
15446
16001
  color: dshThemeColor.labelTertiary,
15447
16002
  fontSize: 12
15448
16003
  };
16004
+ const quotaGroupTitleStyle = {
16005
+ paddingTop: 12,
16006
+ color: dshThemeColor.labelSecondary,
16007
+ fontSize: 12,
16008
+ fontWeight: 600,
16009
+ letterSpacing: ".02em"
16010
+ };
15449
16011
  const subscriptionPopoverStyle = {
15450
16012
  ...dshPopupSurfaceStyle,
15451
16013
  position: "absolute",
@@ -16515,6 +17077,415 @@ window.__ModuleLoader__.load({
16515
17077
  return Math.abs(left - right) < .002;
16516
17078
  }
16517
17079
  //#endregion
17080
+ //#region src/client/skill-reference-dom.ts
17081
+ init_cli_slots();
17082
+ /**
17083
+ * Skill 引用的视觉投影。
17084
+ *
17085
+ * 输入框使用 DSH 原生 ReferenceChip;已经提交到时间线的用户消息仍然是普通文本,
17086
+ * 这里把其中有效的 `/skill-name` 投影成同一套原子元素。只扫描用户气泡和提交回显,
17087
+ * 不改写助手正文、代码块或普通页面文本。
17088
+ */
17089
+ const SKILL_REFERENCE_ATTRIBUTE = "data-codingns-skill-reference";
17090
+ const SKILL_REFERENCE_RAW_ATTRIBUTE = "data-codingns-skill-reference-raw";
17091
+ const SKILL_REFERENCE_STYLE_ID = "codingns4dsh-skill-reference-style";
17092
+ const sessionSkillNames = /* @__PURE__ */ new Map();
17093
+ const catalogListeners = /* @__PURE__ */ new Set();
17094
+ /** 把一个会话最新的 Skill 目录发布给时间线投影和输入框预热使用。 */
17095
+ function publishSkillCatalog(sessionId, catalog) {
17096
+ sessionSkillNames.set(sessionId, new Set(catalog.filter((skill) => skill.enabled).map((skill) => skill.name)));
17097
+ for (const listener of [...catalogListeners]) listener();
17098
+ }
17099
+ /** 返回所有已加载会话的 Skill 名称并集;名称本身区分大小写,匹配时另行兼容大小写。 */
17100
+ function getSkillNames() {
17101
+ const names = /* @__PURE__ */ new Set();
17102
+ for (const sessionNames of sessionSkillNames.values()) for (const name of sessionNames) names.add(name);
17103
+ return names;
17104
+ }
17105
+ function subscribeSkillCatalog(listener) {
17106
+ catalogListeners.add(listener);
17107
+ return () => catalogListeners.delete(listener);
17108
+ }
17109
+ /** 从用户消息中提取有效 Skill 指令;URL、路径中间片段和未索引名称都会被忽略。 */
17110
+ function findSkillReferenceMentions(value, names) {
17111
+ const byLowerName = new Map([...names].map((name) => [name.toLocaleLowerCase(), name]));
17112
+ const matches = [];
17113
+ for (const match of value.matchAll(/\/[A-Za-z0-9][A-Za-z0-9._-]*(?=\s|$)/gu)) {
17114
+ const token = match[0] ?? "";
17115
+ const start = match.index ?? -1;
17116
+ const before = start > 0 ? value[start - 1] : void 0;
17117
+ const name = byLowerName.get(token.slice(1).toLocaleLowerCase());
17118
+ if (start < 0 || name === void 0 || before !== void 0 && !/\s/u.test(before)) continue;
17119
+ matches.push({
17120
+ raw: token,
17121
+ name,
17122
+ start,
17123
+ end: start + token.length
17124
+ });
17125
+ }
17126
+ return matches;
17127
+ }
17128
+ /** 安装 Skill chip 的消息列表投影与统一样式。 */
17129
+ function startSkillReferenceDom(options = {}) {
17130
+ const dom = options.document ?? (typeof document === "undefined" ? void 0 : document);
17131
+ const Observer = options.MutationObserver ?? (typeof MutationObserver === "undefined" ? void 0 : MutationObserver);
17132
+ const getNames = options.getSkillNames ?? getSkillNames;
17133
+ let disposed = false;
17134
+ let scanQueued = false;
17135
+ let observer;
17136
+ const observe = () => {
17137
+ if (disposed || dom === void 0 || observer === void 0) return;
17138
+ observer.observe(dom.documentElement, {
17139
+ childList: true,
17140
+ subtree: true
17141
+ });
17142
+ };
17143
+ const scan = () => {
17144
+ if (disposed || dom === void 0) return;
17145
+ observer?.disconnect();
17146
+ try {
17147
+ decorateTimelineMessages(dom, getNames());
17148
+ } finally {
17149
+ observe();
17150
+ }
17151
+ };
17152
+ const scheduleScan = () => {
17153
+ if (disposed || scanQueued) return;
17154
+ scanQueued = true;
17155
+ queueMicrotask(() => {
17156
+ scanQueued = false;
17157
+ scan();
17158
+ });
17159
+ };
17160
+ if (dom !== void 0) installSkillReferenceStyles(dom);
17161
+ observer = dom === void 0 || Observer === void 0 ? void 0 : new Observer(scheduleScan);
17162
+ observe();
17163
+ const unsubscribeCatalog = subscribeSkillCatalog(scheduleScan);
17164
+ scan();
17165
+ return {
17166
+ refresh: scheduleScan,
17167
+ dispose() {
17168
+ if (disposed) return;
17169
+ disposed = true;
17170
+ observer?.disconnect();
17171
+ unsubscribeCatalog();
17172
+ if (dom !== void 0) removeSkillReferenceNodes(dom);
17173
+ dom?.querySelector(`style[data-plugin-css="${SKILL_REFERENCE_STYLE_ID}"]`)?.remove();
17174
+ }
17175
+ };
17176
+ }
17177
+ function decorateTimelineMessages(dom, names) {
17178
+ const rows = [...dom.querySelectorAll("[class*=\"userRow\"]")];
17179
+ for (const row of dom.querySelectorAll("[data-submission-echo]")) if (!rows.includes(row)) rows.push(row);
17180
+ const byLowerName = new Map([...names].map((name) => [name.toLocaleLowerCase(), name]));
17181
+ if (byLowerName.size === 0) return;
17182
+ for (const row of rows) {
17183
+ const bubble = row.querySelector("[class*=\"bubble\"]") ?? row.querySelector("[class*=\"preview\"]");
17184
+ if (bubble !== null) decorateTimelineNode(bubble, dom, byLowerName);
17185
+ }
17186
+ }
17187
+ function decorateTimelineNode(node, dom, names) {
17188
+ if (node.nodeType === 3) {
17189
+ replaceSkillTextNode(node, dom, names);
17190
+ return;
17191
+ }
17192
+ if (node.nodeType !== 1 || node.hasAttribute("data-codingns-skill-reference") || node.closest("code,pre,[contenteditable=\"true\"]") !== null) return;
17193
+ for (const child of [...node.childNodes]) decorateTimelineNode(child, dom, names);
17194
+ }
17195
+ function replaceSkillTextNode(node, dom, names) {
17196
+ const value = node.nodeValue ?? "";
17197
+ if (value === "" || node.parentElement?.closest("code,pre,[contenteditable=\"true\"]") !== null) return;
17198
+ const matches = findSkillReferenceMentions(value, new Set(names.values()));
17199
+ if (matches.length === 0 || node.parentNode === null) return;
17200
+ const fragment = dom.createDocumentFragment();
17201
+ let cursor = 0;
17202
+ for (const match of matches) {
17203
+ if (match.start < cursor) continue;
17204
+ if (match.start > cursor) fragment.append(dom.createTextNode(value.slice(cursor, match.start)));
17205
+ fragment.append(buildSkillChip(dom, value.slice(match.start, match.end), match.name));
17206
+ cursor = match.end;
17207
+ }
17208
+ if (cursor < value.length) fragment.append(dom.createTextNode(value.slice(cursor)));
17209
+ node.parentNode.replaceChild(fragment, node);
17210
+ }
17211
+ function buildSkillChip(dom, raw, name) {
17212
+ const chip = dom.createElement("span");
17213
+ chip.setAttribute(SKILL_REFERENCE_ATTRIBUTE, name);
17214
+ chip.setAttribute(SKILL_REFERENCE_RAW_ATTRIBUTE, raw);
17215
+ chip.setAttribute("title", `/${name}`);
17216
+ chip.setAttribute("aria-label", `/${name}`);
17217
+ const icon = dom.createElement("span");
17218
+ icon.setAttribute("aria-hidden", "true");
17219
+ icon.textContent = "✦";
17220
+ const label = dom.createElement("span");
17221
+ label.textContent = raw;
17222
+ chip.append(icon, label);
17223
+ return chip;
17224
+ }
17225
+ function removeSkillReferenceNodes(dom) {
17226
+ for (const chip of dom.querySelectorAll(`[${SKILL_REFERENCE_ATTRIBUTE}]`)) {
17227
+ const raw = chip.getAttribute(SKILL_REFERENCE_RAW_ATTRIBUTE);
17228
+ const parent = chip.parentNode;
17229
+ if (raw !== null && parent !== null) parent.replaceChild(dom.createTextNode(raw), chip);
17230
+ }
17231
+ }
17232
+ function installSkillReferenceStyles(dom) {
17233
+ if (dom.querySelector(`style[data-plugin-css="codingns4dsh-skill-reference-style"]`) !== null) return;
17234
+ const style = dom.createElement("style");
17235
+ style.setAttribute("data-plugin", "codingns4dsh");
17236
+ style.setAttribute("data-plugin-css", SKILL_REFERENCE_STYLE_ID);
17237
+ style.textContent = [
17238
+ `[${SKILL_REFERENCE_ATTRIBUTE}], [data-composer-chip="codingns-skills"]{display:inline-flex;align-items:center;vertical-align:baseline;gap:4px;padding:1px 7px;border:1px solid var(--dsw-alias-border-l2,rgba(127,127,127,.22));border-radius:999px;background:var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12));color:var(--dsw-alias-label-primary);white-space:nowrap;font-size:.94em;line-height:1.45;user-select:all}`,
17239
+ `[${SKILL_REFERENCE_ATTRIBUTE}], [data-composer-chip="codingns-skills"]{border-color:color-mix(in srgb,var(--dsw-alias-label-primary,#888) 18%,transparent);background:color-mix(in srgb,var(--dsw-alias-interactive-bg-hover,rgba(127,127,127,.12)) 82%,transparent)}`,
17240
+ `[${SKILL_REFERENCE_ATTRIBUTE}]>span:first-child{color:var(--dsw-alias-brand-primary,var(--dsw-alias-label-primary));font-size:.8em}`,
17241
+ `[${SKILL_REFERENCE_ATTRIBUTE}]{cursor:default}`,
17242
+ `[data-composer-chip="codingns-skills"]>svg{color:var(--dsw-alias-brand-primary,var(--dsw-alias-label-primary))}`
17243
+ ].join("");
17244
+ dom.head.appendChild(style);
17245
+ }
17246
+ //#endregion
17247
+ //#region src/client/skill-command.ts
17248
+ init_cli_catalog();
17249
+ init_debug();
17250
+ const SKILL_COMMAND_NAME = "skills";
17251
+ const SKILL_INPUT_SOURCE = "codingns-skills";
17252
+ const SKILL_CATALOG_CACHE_TTL_MS = 3e4;
17253
+ const skillCatalogCache = /* @__PURE__ */ new Map();
17254
+ /** 把 Codex 原生 Skill 目录接入 DSH `/` 菜单。 */
17255
+ function registerSkillCommand(ctx, options) {
17256
+ const state = {
17257
+ catalogReady: false,
17258
+ disposeFallback: void 0
17259
+ };
17260
+ const markCatalogReady = (catalog) => {
17261
+ if (!catalog.some((skill) => skill.enabled) || state.catalogReady) return;
17262
+ state.catalogReady = true;
17263
+ state.disposeFallback?.();
17264
+ state.disposeFallback = void 0;
17265
+ };
17266
+ const commandFiber = ctx.inject(["commandUi"], (scope) => {
17267
+ if (state.catalogReady) return;
17268
+ const dispose = registerSkillCommandFallback(scope, options, markCatalogReady);
17269
+ state.disposeFallback = dispose;
17270
+ });
17271
+ const triggerFiber = ctx.inject(["inputTriggers"], (scope) => {
17272
+ registerSkillInputTriggerSource(scope, options, markCatalogReady);
17273
+ });
17274
+ return () => {
17275
+ state.disposeFallback?.();
17276
+ commandFiber.dispose();
17277
+ triggerFiber.dispose();
17278
+ };
17279
+ }
17280
+ /** 在没有原生 inputTriggers 时保留旧版 DSH 的可用 Skill 菜单。 */
17281
+ function registerSkillCommandFallback(ctx, options, onCatalogReady) {
17282
+ const commandUi = readCommandUi(ctx);
17283
+ if (commandUi === void 0) {
17284
+ debugWarn("codingns4dsh: /skills 未注册,当前 DSH 未提供 commandUi 服务");
17285
+ return;
17286
+ }
17287
+ const t = options.locale.bind("codingns");
17288
+ try {
17289
+ return ctx.effect(() => commandUi.register({
17290
+ name: SKILL_COMMAND_NAME,
17291
+ label: () => t("skills.label"),
17292
+ description: () => t("skills.description"),
17293
+ available: () => true,
17294
+ ui: {
17295
+ kind: "popupSelect",
17296
+ searchMode: "fuzzy-label",
17297
+ searchLabels: () => ({
17298
+ placeholder: t("skills.searchPlaceholder"),
17299
+ empty: t("skills.searchEmpty"),
17300
+ noResults: t("skills.searchNoResults")
17301
+ }),
17302
+ async options(session, signal) {
17303
+ await waitForCliSessionSelection(session.sessionId);
17304
+ const catalog = await loadSkillCatalog(options.rpc, session.sessionId, signal, true);
17305
+ onCatalogReady(catalog);
17306
+ return catalog.filter((skill) => skill.enabled).map((skill) => ({
17307
+ id: skill.name,
17308
+ label: skill.displayName ?? skill.name,
17309
+ ...skill.description.trim() === "" ? {} : { detail: skill.description }
17310
+ }));
17311
+ },
17312
+ onSelect(option, session) {
17313
+ const input = readSessionInput(ctx, session.sessionId);
17314
+ if (input === void 0 || input.setDraft === void 0 && input.insertReference === void 0) {
17315
+ input?.notify("error", t("skills.draftUnsupported"));
17316
+ return;
17317
+ }
17318
+ const snapshot = input.state.getSnapshot();
17319
+ const draft = snapshot.draft ?? "";
17320
+ const selection = input.caretSpan?.();
17321
+ const span = skillCommandSpan(draft, snapshot.draftRev ?? selection?.draftRev ?? 0, selection);
17322
+ const token = `/${option.id} `;
17323
+ const reference = {
17324
+ source: SKILL_INPUT_SOURCE,
17325
+ ref: option.id,
17326
+ label: option.label,
17327
+ appearance: "session",
17328
+ clipboardText: `/${option.id}`
17329
+ };
17330
+ if (span !== void 0 && input.insertReference?.(reference, span) === true) return;
17331
+ if (input.setDraft === void 0) {
17332
+ input.notify("error", t("skills.draftUnsupported"));
17333
+ return;
17334
+ }
17335
+ if (span !== void 0) {
17336
+ input.setDraft(`${draft.slice(0, span.start)}${token}${draft.slice(span.end)}`);
17337
+ return;
17338
+ }
17339
+ const caret = selection?.end ?? draft.length;
17340
+ input.setDraft(`${draft.slice(0, caret)}${token}${draft.slice(caret)}`);
17341
+ }
17342
+ }
17343
+ }), "codingns4dsh: skills command");
17344
+ } catch (error) {
17345
+ debugWarn("codingns4dsh: /skills 命令注册失败", { error: error instanceof Error ? error.message : String(error) });
17346
+ return;
17347
+ }
17348
+ }
17349
+ /** 注册直接的 `/` Skill 搜索源。 */
17350
+ function registerSkillInputTriggerSource(ctx, options, onCatalogReady) {
17351
+ const inputTriggers = readService(ctx, "inputTriggers");
17352
+ if (inputTriggers === void 0 || typeof inputTriggers.registerSource !== "function") {
17353
+ debugWarn("codingns4dsh: Skill `/` 搜索源未注册,当前 DSH 未提供 inputTriggers 服务");
17354
+ return false;
17355
+ }
17356
+ const source = {
17357
+ trigger: "/",
17358
+ name: SKILL_INPUT_SOURCE,
17359
+ order: 2,
17360
+ showGroupTitle: false,
17361
+ async candidates(session, request) {
17362
+ try {
17363
+ await waitForCliSessionSelection(session.sessionId);
17364
+ const query = request.query.trim().toLocaleLowerCase();
17365
+ const catalog = await loadSkillCatalog(options.rpc, session.sessionId, request.signal, query === "");
17366
+ onCatalogReady(catalog);
17367
+ return catalog.filter((skill) => skill.enabled).filter((skill) => query === "" || [
17368
+ skill.name,
17369
+ skill.displayName,
17370
+ skill.description
17371
+ ].some((value) => value?.toLocaleLowerCase().includes(query) === true)).map((skill) => ({
17372
+ name: skill.name,
17373
+ label: skill.displayName ?? skill.name,
17374
+ value: skill.name,
17375
+ ...skill.description.trim() === "" ? {} : { description: skill.description }
17376
+ }));
17377
+ } catch (error) {
17378
+ debugWarn("codingns4dsh: 读取 Skill `/` 搜索目录失败", { error: error instanceof Error ? error.message : String(error) });
17379
+ return [];
17380
+ }
17381
+ },
17382
+ warm(session) {
17383
+ const controller = new AbortController();
17384
+ waitForCliSessionSelection(session.sessionId).then(() => loadSkillCatalog(options.rpc, session.sessionId, controller.signal, true)).then((catalog) => {
17385
+ onCatalogReady(catalog);
17386
+ }).catch((error) => {
17387
+ debugWarn("codingns4dsh: 预热 Skill `/` 搜索目录失败", { error: error instanceof Error ? error.message : String(error) });
17388
+ });
17389
+ },
17390
+ lexicon(session) {
17391
+ return (skillCatalogCache.get(session.sessionId)?.catalog)?.filter((skill) => skill.enabled).map((skill) => skill.name);
17392
+ },
17393
+ onPick(pick) {
17394
+ const name = pick.candidate.value?.trim() || pick.candidate.name.trim();
17395
+ if (name === "") return { text: "" };
17396
+ return { insert: {
17397
+ source: SKILL_INPUT_SOURCE,
17398
+ ref: name,
17399
+ label: pick.candidate.label?.trim() || name,
17400
+ appearance: "session",
17401
+ clipboardText: `/${name}`
17402
+ } };
17403
+ },
17404
+ codec: {
17405
+ clipboardText(ref) {
17406
+ return `/${ref}`;
17407
+ },
17408
+ async serialize(ref) {
17409
+ return `/${ref}`;
17410
+ }
17411
+ }
17412
+ };
17413
+ try {
17414
+ ctx.effect(() => inputTriggers.registerSource(source), "codingns4dsh: skill slash source");
17415
+ debugInfo("codingns4dsh: Skill `/` 搜索源已注册");
17416
+ return true;
17417
+ } catch (error) {
17418
+ debugWarn("codingns4dsh: Skill `/` 搜索源注册失败", { error: error instanceof Error ? error.message : String(error) });
17419
+ return false;
17420
+ }
17421
+ }
17422
+ /** 按会话与 Agent 选择版本缓存目录,避免输入每个字符都请求 Codex。 */
17423
+ async function loadSkillCatalog(rpc, sessionId, signal, forceReload) {
17424
+ const selectionRevision = cliSessionSelectionRevision(sessionId);
17425
+ const cached = skillCatalogCache.get(sessionId);
17426
+ if (!forceReload && cached !== void 0 && cached.selectionRevision === selectionRevision && Date.now() - cached.loadedAt < SKILL_CATALOG_CACHE_TTL_MS) return cached.catalog;
17427
+ const catalog = await callCliRpc(rpc, "skills", {
17428
+ sessionId,
17429
+ forceReload: true
17430
+ }, signal);
17431
+ skillCatalogCache.set(sessionId, {
17432
+ selectionRevision,
17433
+ loadedAt: Date.now(),
17434
+ catalog
17435
+ });
17436
+ publishSkillCatalog(sessionId, catalog);
17437
+ return catalog;
17438
+ }
17439
+ function skillCommandSpan(draft, draftRev, selection) {
17440
+ const caret = selection?.end ?? draft.length;
17441
+ const token = /\/(?:skills|技能)(?=\s|$)/gu;
17442
+ let match;
17443
+ while ((match = token.exec(draft)) !== null) {
17444
+ const start = match.index;
17445
+ const end = start + match[0].length;
17446
+ const before = draft[start - 1];
17447
+ if (start > 0 && before !== void 0 && !/\s/u.test(before)) continue;
17448
+ if (caret >= start && caret <= end + 1) return {
17449
+ start,
17450
+ end,
17451
+ draftRev
17452
+ };
17453
+ if (caret > end && draft.slice(end, caret).trim() === "") return {
17454
+ start,
17455
+ end,
17456
+ draftRev
17457
+ };
17458
+ }
17459
+ }
17460
+ function readSessionInput(ctx, sessionId) {
17461
+ try {
17462
+ const actx = readSessions(ctx)?.scope(sessionId);
17463
+ if (actx === void 0) return void 0;
17464
+ return readConversation(ctx)?.input.for(actx);
17465
+ } catch {
17466
+ return;
17467
+ }
17468
+ }
17469
+ function readCommandUi(ctx) {
17470
+ const service = readService(ctx, "commandUi");
17471
+ return service !== void 0 && typeof service.register === "function" ? service : void 0;
17472
+ }
17473
+ function readSessions(ctx) {
17474
+ const service = readService(ctx, "sessions");
17475
+ return service !== void 0 && typeof service.scope === "function" ? service : void 0;
17476
+ }
17477
+ function readConversation(ctx) {
17478
+ const service = readService(ctx, "conversation");
17479
+ return service !== void 0 && typeof service.input?.for === "function" ? service : void 0;
17480
+ }
17481
+ function readService(ctx, name) {
17482
+ try {
17483
+ return ctx.get(name);
17484
+ } catch {
17485
+ return;
17486
+ }
17487
+ }
17488
+ //#endregion
16518
17489
  //#region src/client/features/workspace-session-enhancement.ts
16519
17490
  init_session_adapter_cache();
16520
17491
  init_locale();
@@ -16548,6 +17519,8 @@ window.__ModuleLoader__.load({
16548
17519
  let adapterRefreshTimer;
16549
17520
  let disposeSubscription;
16550
17521
  let disposeQuickPhrases;
17522
+ let disposeSkillCommand;
17523
+ let skillReferenceDom;
16551
17524
  let lastSubscriptionUsageSignature = null;
16552
17525
  const disableLogo = () => {
16553
17526
  generation += 1;
@@ -16615,6 +17588,20 @@ window.__ModuleLoader__.load({
16615
17588
  disposeQuickPhrases?.();
16616
17589
  disposeQuickPhrases = void 0;
16617
17590
  };
17591
+ const enableSkillQuickReference = () => {
17592
+ if (skillReferenceDom === void 0) skillReferenceDom = startSkillReferenceDom();
17593
+ if (disposeSkillCommand !== void 0 || context.services.uiContext === void 0) return;
17594
+ disposeSkillCommand = registerSkillCommand(context.services.uiContext, {
17595
+ rpc: context.services.rpc,
17596
+ locale: context.services.locale
17597
+ });
17598
+ };
17599
+ const disableSkillQuickReference = () => {
17600
+ disposeSkillCommand?.();
17601
+ disposeSkillCommand = void 0;
17602
+ skillReferenceDom?.dispose();
17603
+ skillReferenceDom = void 0;
17604
+ };
16618
17605
  const disposeAll = () => {
16619
17606
  disableLogo();
16620
17607
  archiveDom?.dispose();
@@ -16623,6 +17610,7 @@ window.__ModuleLoader__.load({
16623
17610
  disableRightbarMemory();
16624
17611
  disableSubscription();
16625
17612
  disableQuickPhrases();
17613
+ disableSkillQuickReference();
16626
17614
  };
16627
17615
  const enableLogo = () => {
16628
17616
  if (logoDom !== void 0) return;
@@ -16657,6 +17645,8 @@ window.__ModuleLoader__.load({
16657
17645
  else disableSubscription();
16658
17646
  if (workspaceSettings?.showQuickPhrases ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showQuickPhrases) enableQuickPhrases();
16659
17647
  else disableQuickPhrases();
17648
+ if (workspaceSettings?.showSkillQuickReference ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.showSkillQuickReference) enableSkillQuickReference();
17649
+ else disableSkillQuickReference();
16660
17650
  if (workspaceSettings?.rememberConversationRightbarRatio ?? DEFAULT_WORKSPACE_SESSION_ENHANCEMENT_SETTINGS.rememberConversationRightbarRatio) enableRightbarMemory();
16661
17651
  else disableRightbarMemory();
16662
17652
  const subscriptionUsageSignature = JSON.stringify(context.services.settings.getSnapshot().value?.subscriptionUsage ?? null);
@@ -18509,9 +19499,9 @@ window.__ModuleLoader__.load({
18509
19499
  }
18510
19500
  function createProfileForm(value) {
18511
19501
  const field = (label, key, type = "text", placeholder, layout) => (0, react.createElement)("label", { style: {
18512
- ...fieldStyle,
19502
+ ...fieldStyle$1,
18513
19503
  ...layout
18514
- } }, (0, react.createElement)("span", { style: fieldLabelStyle }, label), (0, react.createElement)("input", {
19504
+ } }, (0, react.createElement)("span", { style: fieldLabelStyle$1 }, label), (0, react.createElement)("input", {
18515
19505
  type,
18516
19506
  value: typeof value[key] === "boolean" ? void 0 : value[key],
18517
19507
  placeholder,
@@ -18526,7 +19516,7 @@ window.__ModuleLoader__.load({
18526
19516
  }),
18527
19517
  style: inputStyle
18528
19518
  }));
18529
- const selectField = (label, key, options) => (0, react.createElement)("label", { style: selectFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle }, label), (0, react.createElement)("select", {
19519
+ const selectField = (label, key, options) => (0, react.createElement)("label", { style: selectFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle$1 }, label), (0, react.createElement)("select", {
18530
19520
  value: value[key],
18531
19521
  onChange: (event) => setDraft({
18532
19522
  ...value,
@@ -18559,7 +19549,7 @@ window.__ModuleLoader__.load({
18559
19549
  onClick: () => setDraft(null),
18560
19550
  style: closeButtonStyle,
18561
19551
  "aria-label": t("debug.closeFormAria")
18562
- }, "×")), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionLaunch")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionLaunchHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldName"), "name", "text", t("debug.fieldNamePlaceholder")), field(t("debug.fieldCwd"), "cwdRelative", "text", t("debug.fieldCwdPlaceholder")), field(t("debug.fieldCommand"), "commandLine", "text", t("debug.fieldCommandPlaceholder"), { gridColumn: "1 / -1" }))), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionRuntime")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionRuntimeHint"))), (0, react.createElement)("div", { style: formGridStyle }, selectField(t("debug.fieldShell"), "shellProfileId", shellOptions(terminalStatus, t))), (0, react.createElement)("p", { style: runtimeHintStyle }, t("debug.runtimeHint", { runtime: terminalRuntimeLabel(terminalStatus, t) })), terminalStatus?.runtimeWarning === void 0 ? null : (0, react.createElement)("p", { style: warningHintStyle }, terminalStatus.runtimeWarning)), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionPort")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionPortHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldPort"), "port", "number", "1 - 65535"), (0, react.createElement)("label", { style: proxyFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle }, t("debug.fieldProxy")), (0, react.createElement)("span", { style: proxyToggleStyle }, (0, react.createElement)("span", void 0, t("debug.proxyToggleLabel")), (0, react.createElement)("input", {
19552
+ }, "×")), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionLaunch")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionLaunchHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldName"), "name", "text", t("debug.fieldNamePlaceholder")), field(t("debug.fieldCwd"), "cwdRelative", "text", t("debug.fieldCwdPlaceholder")), field(t("debug.fieldCommand"), "commandLine", "text", t("debug.fieldCommandPlaceholder"), { gridColumn: "1 / -1" }))), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionRuntime")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionRuntimeHint"))), (0, react.createElement)("div", { style: formGridStyle }, selectField(t("debug.fieldShell"), "shellProfileId", shellOptions(terminalStatus, t))), (0, react.createElement)("p", { style: runtimeHintStyle }, t("debug.runtimeHint", { runtime: terminalRuntimeLabel(terminalStatus, t) })), terminalStatus?.runtimeWarning === void 0 ? null : (0, react.createElement)("p", { style: warningHintStyle }, terminalStatus.runtimeWarning)), (0, react.createElement)("section", { style: formSectionStyle }, (0, react.createElement)("div", { style: formSectionHeaderStyle }, (0, react.createElement)("strong", { style: formSectionTitleStyle }, t("debug.sectionPort")), (0, react.createElement)("span", { style: formSectionHintStyle }, t("debug.sectionPortHint"))), (0, react.createElement)("div", { style: formGridStyle }, field(t("debug.fieldPort"), "port", "number", "1 - 65535"), (0, react.createElement)("label", { style: proxyFieldStyle }, (0, react.createElement)("span", { style: fieldLabelStyle$1 }, t("debug.fieldProxy")), (0, react.createElement)("span", { style: proxyToggleStyle }, (0, react.createElement)("span", void 0, t("debug.proxyToggleLabel")), (0, react.createElement)("input", {
18563
19553
  type: "checkbox",
18564
19554
  "aria-label": t("debug.proxyToggleAria"),
18565
19555
  checked: value.proxyEnabled,
@@ -19141,14 +20131,14 @@ window.__ModuleLoader__.load({
19141
20131
  fontSize: 20,
19142
20132
  lineHeight: 1
19143
20133
  };
19144
- const fieldStyle = {
20134
+ const fieldStyle$1 = {
19145
20135
  display: "flex",
19146
20136
  flexDirection: "column",
19147
20137
  gap: 5,
19148
20138
  minWidth: 0
19149
20139
  };
19150
- const selectFieldStyle = { ...fieldStyle };
19151
- const fieldLabelStyle = {
20140
+ const selectFieldStyle = { ...fieldStyle$1 };
20141
+ const fieldLabelStyle$1 = {
19152
20142
  color: dshThemeColor.labelSecondary,
19153
20143
  fontSize: 12,
19154
20144
  fontWeight: 500
@@ -19181,7 +20171,7 @@ window.__ModuleLoader__.load({
19181
20171
  gridTemplateColumns: "repeat(auto-fit, minmax(220px, 1fr))",
19182
20172
  gap: 14
19183
20173
  };
19184
- const proxyFieldStyle = { ...fieldStyle };
20174
+ const proxyFieldStyle = { ...fieldStyle$1 };
19185
20175
  const proxyToggleStyle = {
19186
20176
  display: "flex",
19187
20177
  alignItems: "center",
@@ -56864,7 +57854,7 @@ window.__ModuleLoader__.load({
56864
57854
  close();
56865
57855
  return;
56866
57856
  }
56867
- setMessage(t("peerHost.handshakeFailed", { status: statusLabel(record.status, t) }));
57857
+ setMessage(t("peerHost.handshakeFailed", { status: statusLabel$1(record.status, t) }));
56868
57858
  }).catch((error) => setMessage(error instanceof Error ? error.message : String(error))).finally(() => {
56869
57859
  submit.disabled = false;
56870
57860
  submit.textContent = t("peerHost.addAndLogin");
@@ -56973,7 +57963,7 @@ window.__ModuleLoader__.load({
56973
57963
  lineHeight: "1.3"
56974
57964
  });
56975
57965
  const status = dom.createElement("span");
56976
- status.textContent = statusLabel(record.status, t);
57966
+ status.textContent = statusLabel$1(record.status, t);
56977
57967
  Object.assign(status.style, {
56978
57968
  flex: "0 0 auto",
56979
57969
  padding: "2px 6px",
@@ -57398,7 +58388,7 @@ window.__ModuleLoader__.load({
57398
58388
  reset.style.borderColor = selected === null ? dshThemeColor.accent : dshThemeColor.border;
57399
58389
  }
57400
58390
  }
57401
- function statusLabel(status, t) {
58391
+ function statusLabel$1(status, t) {
57402
58392
  const key = {
57403
58393
  configured: "peerHost.statusConfigured",
57404
58394
  checking: "peerHost.statusChecking",
@@ -57679,7 +58669,7 @@ window.__ModuleLoader__.load({
57679
58669
  const store = readNativeWorkspaceListStore(input.uiContext);
57680
58670
  if (store === void 0) return () => void 0;
57681
58671
  const service = readNativeService(input.uiContext, "workspaces");
57682
- const serviceRecord = isRecord$3(service) ? service : void 0;
58672
+ const serviceRecord = isRecord$4(service) ? service : void 0;
57683
58673
  const sessionStoreValue = readNativeService(input.uiContext, "sessions", "list");
57684
58674
  const sessionStore = isSnapshotStore(sessionStoreValue) ? sessionStoreValue : void 0;
57685
58675
  const ownsGetSnapshot = Object.hasOwn(store, "getSnapshot");
@@ -57738,7 +58728,7 @@ window.__ModuleLoader__.load({
57738
58728
  */
57739
58729
  async function refreshPeerHostNativeSessions(uiContext) {
57740
58730
  const sessions = readNativeService(uiContext, "sessions");
57741
- const refresh = isRecord$3(sessions) ? sessions.refresh : void 0;
58731
+ const refresh = isRecord$4(sessions) ? sessions.refresh : void 0;
57742
58732
  if (typeof refresh !== "function") return;
57743
58733
  try {
57744
58734
  await refresh.call(sessions);
@@ -57837,7 +58827,7 @@ window.__ModuleLoader__.load({
57837
58827
  const byId = state?.byId;
57838
58828
  const workspaceState = asRecord$1(workspaceSnapshot);
57839
58829
  const items = Array.isArray(workspaceState?.items) ? workspaceState.items : [];
57840
- if (state === null || !isRecord$3(byId) || items.length === 0) return snapshot;
58830
+ if (state === null || !isRecord$4(byId) || items.length === 0) return snapshot;
57841
58831
  let changed = false;
57842
58832
  const nextById = { ...byId };
57843
58833
  for (const [id, value] of Object.entries(byId)) {
@@ -57983,13 +58973,13 @@ window.__ModuleLoader__.load({
57983
58973
  return merged.length === local.length ? void 0 : merged;
57984
58974
  }
57985
58975
  function asRecord$1(value) {
57986
- return isRecord$3(value) ? value : null;
58976
+ return isRecord$4(value) ? value : null;
57987
58977
  }
57988
- function isRecord$3(value) {
58978
+ function isRecord$4(value) {
57989
58979
  return typeof value === "object" && value !== null && !Array.isArray(value);
57990
58980
  }
57991
58981
  function isSnapshotStore(value) {
57992
- return isRecord$3(value) && typeof value.getSnapshot === "function" && typeof value.subscribe === "function";
58982
+ return isRecord$4(value) && typeof value.getSnapshot === "function" && typeof value.subscribe === "function";
57993
58983
  }
57994
58984
  //#endregion
57995
58985
  //#region src/client/peer-host-workspace-tag.ts
@@ -58099,7 +59089,7 @@ window.__ModuleLoader__.load({
58099
59089
  const fiberKey = Object.getOwnPropertyNames(row).find((key) => key.startsWith("__reactFiber$"));
58100
59090
  if (fiberKey === void 0) return void 0;
58101
59091
  let current = row[fiberKey];
58102
- for (let depth = 0; depth < 24 && isRecord$2(current); depth += 1) {
59092
+ for (let depth = 0; depth < 24 && isRecord$3(current); depth += 1) {
58103
59093
  for (const props of [current.memoizedProps, current.pendingProps]) {
58104
59094
  const found = readWorkspaceIdFromProps(props);
58105
59095
  if (found !== void 0) return found;
@@ -58108,7 +59098,7 @@ window.__ModuleLoader__.load({
58108
59098
  }
58109
59099
  }
58110
59100
  function readWorkspaceIdFromProps(value) {
58111
- if (!isRecord$2(value)) return void 0;
59101
+ if (!isRecord$3(value)) return void 0;
58112
59102
  for (const key of ["workspaceId", "workspaceID"]) {
58113
59103
  const candidate = value[key];
58114
59104
  if (typeof candidate === "string" && candidate.trim() !== "") return candidate.trim();
@@ -58122,7 +59112,7 @@ window.__ModuleLoader__.load({
58122
59112
  "data"
58123
59113
  ]) {
58124
59114
  const nested = value[key];
58125
- if (!isRecord$2(nested)) continue;
59115
+ if (!isRecord$3(nested)) continue;
58126
59116
  const candidate = nested.workspaceId ?? nested.id;
58127
59117
  if (typeof candidate === "string" && candidate.trim() !== "") return candidate.trim();
58128
59118
  }
@@ -58168,7 +59158,7 @@ window.__ModuleLoader__.load({
58168
59158
  });
58169
59159
  host.append(element);
58170
59160
  }
58171
- function isRecord$2(value) {
59161
+ function isRecord$3(value) {
58172
59162
  return typeof value === "object" && value !== null && !Array.isArray(value);
58173
59163
  }
58174
59164
  //#endregion
@@ -59466,6 +60456,1598 @@ window.__ModuleLoader__.load({
59466
60456
  }, message);
59467
60457
  }
59468
60458
  //#endregion
60459
+ //#region src/client/global-voice-runtime-registry.ts
60460
+ const adapters = /* @__PURE__ */ new WeakMap();
60461
+ function registerGlobalVoiceAdapter(services, adapter) {
60462
+ adapters.set(services, adapter);
60463
+ return () => {
60464
+ if (adapters.get(services) === adapter) adapters.delete(services);
60465
+ };
60466
+ }
60467
+ function getGlobalVoiceAdapter(services) {
60468
+ return adapters.get(services);
60469
+ }
60470
+ //#endregion
60471
+ //#region src/client/features/voice-initialization-dialog.ts
60472
+ init_transport();
60473
+ init_theme();
60474
+ init_locale();
60475
+ /** 全局语音助理初始化页;用户只选择模型,路径和下载由 Host 自动处理。 */
60476
+ function VoiceInitializationDialog({ services, value, onClose }) {
60477
+ const t = useCodingNsTranslator(services.locale);
60478
+ const defaultModelId = ASSISTANT_VOICE_MODEL_CATALOG[0]?.id ?? "";
60479
+ const configuredModelId = value.modelId?.trim() ?? "";
60480
+ const [modelId, setModelId] = (0, react.useState)(() => configuredModelId !== "" && findAssistantVoiceModel(configuredModelId) !== void 0 ? configuredModelId : defaultModelId);
60481
+ const [saving, setSaving] = (0, react.useState)(false);
60482
+ const [error, setError] = (0, react.useState)();
60483
+ const settingsSnapshot = services.settings.getSnapshot();
60484
+ const disabled = saving || settingsSnapshot.status === "loading" || !settingsSnapshot.writable;
60485
+ const selectedModel = findAssistantVoiceModel(modelId);
60486
+ (0, react.useEffect)(() => {
60487
+ const closeOnEscape = (event) => {
60488
+ if (event.key === "Escape" && !saving) onClose();
60489
+ };
60490
+ document.addEventListener("keydown", closeOnEscape);
60491
+ return () => document.removeEventListener("keydown", closeOnEscape);
60492
+ }, [onClose, saving]);
60493
+ const save = async () => {
60494
+ if (selectedModel === void 0) {
60495
+ setError(t("voice.setup.modelRequired"));
60496
+ return;
60497
+ }
60498
+ setSaving(true);
60499
+ setError(void 0);
60500
+ try {
60501
+ const result = await services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/setup", { modelId });
60502
+ if (!result.ok) {
60503
+ setError(result.error.message);
60504
+ return;
60505
+ }
60506
+ onClose();
60507
+ } catch (cause) {
60508
+ setError(cause instanceof Error ? cause.message : String(cause));
60509
+ } finally {
60510
+ setSaving(false);
60511
+ }
60512
+ };
60513
+ return (0, react.createElement)("div", {
60514
+ role: "presentation",
60515
+ onPointerDown: () => {
60516
+ if (!saving) onClose();
60517
+ },
60518
+ style: {
60519
+ position: "fixed",
60520
+ inset: 0,
60521
+ zIndex: 1e4,
60522
+ display: "flex",
60523
+ alignItems: "center",
60524
+ justifyContent: "center",
60525
+ padding: 16,
60526
+ background: dshThemeColor.overlay,
60527
+ boxSizing: "border-box"
60528
+ }
60529
+ }, (0, react.createElement)("div", {
60530
+ role: "dialog",
60531
+ "aria-modal": true,
60532
+ "aria-label": t("voice.setup.title"),
60533
+ onPointerDown: (event) => event.stopPropagation(),
60534
+ style: {
60535
+ display: "flex",
60536
+ flexDirection: "column",
60537
+ gap: 16,
60538
+ width: "min(560px, 100%)",
60539
+ maxHeight: "min(620px, 100%)",
60540
+ overflowY: "auto",
60541
+ padding: 24,
60542
+ color: dshThemeColor.labelPrimary,
60543
+ background: dshThemeColor.menuBackground,
60544
+ border: `1px solid ${dshThemeColor.border}`,
60545
+ borderRadius: 12,
60546
+ boxShadow: dshThemeColor.prominentShadow,
60547
+ boxSizing: "border-box"
60548
+ }
60549
+ }, (0, react.createElement)("div", { style: {
60550
+ display: "grid",
60551
+ gap: 7
60552
+ } }, (0, react.createElement)("strong", { style: {
60553
+ fontSize: 18,
60554
+ lineHeight: 1.4
60555
+ } }, t("voice.setup.title")), (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("voice.setup.description"))), (0, react.createElement)("div", { style: {
60556
+ padding: "11px 13px",
60557
+ borderRadius: 8,
60558
+ background: dshThemeColor.inputBackground,
60559
+ color: dshThemeColor.labelSecondary,
60560
+ fontSize: 13,
60561
+ lineHeight: 1.55
60562
+ } }, t("voice.setup.onlyRealtime")), (0, react.createElement)("label", { style: fieldLabelStyle }, (0, react.createElement)("span", null, t("voice.setup.model")), (0, react.createElement)("select", {
60563
+ value: modelId,
60564
+ disabled,
60565
+ onChange: (event) => setModelId(event.currentTarget.value),
60566
+ style: fieldStyle
60567
+ }, ...ASSISTANT_VOICE_MODEL_CATALOG.map((model) => (0, react.createElement)("option", {
60568
+ key: model.id,
60569
+ value: model.id
60570
+ }, model.label)))), selectedModel === void 0 ? null : (0, react.createElement)("div", { style: {
60571
+ display: "grid",
60572
+ gap: 5,
60573
+ color: dshThemeColor.labelSecondary,
60574
+ fontSize: 13,
60575
+ lineHeight: 1.5
60576
+ } }, (0, react.createElement)("strong", { style: {
60577
+ color: dshThemeColor.labelPrimary,
60578
+ fontSize: 14
60579
+ } }, selectedModel.label), (0, react.createElement)("span", null, selectedModel.description), (0, react.createElement)("span", null, t("voice.setup.downloadHint"))), saving ? (0, react.createElement)("div", {
60580
+ role: "status",
60581
+ style: {
60582
+ color: dshThemeColor.labelSecondary,
60583
+ fontSize: 13
60584
+ }
60585
+ }, t("voice.setup.downloading")) : null, error === void 0 ? null : (0, react.createElement)("div", {
60586
+ role: "alert",
60587
+ style: {
60588
+ color: dshThemeColor.error,
60589
+ fontSize: 13,
60590
+ lineHeight: 1.5
60591
+ }
60592
+ }, error), (0, react.createElement)("div", { style: {
60593
+ display: "flex",
60594
+ justifyContent: "flex-end",
60595
+ gap: 8,
60596
+ paddingTop: 3
60597
+ } }, (0, react.createElement)("button", {
60598
+ type: "button",
60599
+ disabled: saving,
60600
+ onClick: onClose,
60601
+ style: dshSettingsButtonStyle
60602
+ }, t("voice.setup.cancel")), (0, react.createElement)("button", {
60603
+ type: "button",
60604
+ disabled,
60605
+ onClick: () => void save(),
60606
+ style: dshSettingsPrimaryButtonStyle
60607
+ }, t("voice.setup.save")))));
60608
+ }
60609
+ const fieldLabelStyle = {
60610
+ display: "grid",
60611
+ gap: 7,
60612
+ color: dshThemeColor.labelSecondary,
60613
+ fontSize: 12,
60614
+ lineHeight: 1.4,
60615
+ fontWeight: 600
60616
+ };
60617
+ const fieldStyle = {
60618
+ ...dshFieldStyle,
60619
+ width: "100%",
60620
+ minHeight: 40,
60621
+ padding: "9px 11px",
60622
+ borderRadius: 7,
60623
+ boxSizing: "border-box",
60624
+ fontSize: 14
60625
+ };
60626
+ //#endregion
60627
+ //#region src/client/features/assistant-panel.ts
60628
+ init_locale();
60629
+ init_theme();
60630
+ /** 全局智能助理范围设置;默认空范围,工作区必须由用户逐个勾选。 */
60631
+ function AssistantPanel({ services, enabled, snapshot, notify }) {
60632
+ const t = useCodingNsTranslator(services.locale);
60633
+ const [workspaces, setWorkspaces] = (0, react.useState)([]);
60634
+ const [inputDevices, setInputDevices] = (0, react.useState)([]);
60635
+ const [inputDeviceId, setInputDeviceId] = (0, react.useState)("");
60636
+ const [outputDevices, setOutputDevices] = (0, react.useState)([]);
60637
+ const [outputDeviceId, setOutputDeviceId] = (0, react.useState)("");
60638
+ const [outputDeviceSupported, setOutputDeviceSupported] = (0, react.useState)(false);
60639
+ const [voiceSetupOpen, setVoiceSetupOpen] = (0, react.useState)(false);
60640
+ const value = snapshot.value?.assistant ?? DEFAULT_ASSISTANT_SETTINGS;
60641
+ const voice = value.voice ?? DEFAULT_ASSISTANT_VOICE_SETTINGS;
60642
+ const voiceModel = findAssistantVoiceModel(voice.modelId ?? "");
60643
+ const disabled = !enabled || snapshot.status === "loading" || !snapshot.writable;
60644
+ (0, react.useEffect)(() => {
60645
+ const store = readNativeWorkspaceListStore(services.uiContext);
60646
+ const refresh = () => setWorkspaces(readNativeWorkspaceSnapshot(services.uiContext)?.items ?? []);
60647
+ refresh();
60648
+ return store?.subscribe(refresh);
60649
+ }, [services.uiContext]);
60650
+ (0, react.useEffect)(() => {
60651
+ const adapter = getGlobalVoiceAdapter(services);
60652
+ if (adapter === void 0) return void 0;
60653
+ let disposed = false;
60654
+ const refresh = () => {
60655
+ adapter.enumerateInputDevices().then((devices) => {
60656
+ if (disposed) return;
60657
+ setInputDevices(devices.map(({ deviceId, label }) => ({
60658
+ deviceId,
60659
+ label
60660
+ })));
60661
+ setInputDeviceId(adapter.inputDeviceId ?? "");
60662
+ }).catch(() => {
60663
+ if (!disposed) setInputDevices([]);
60664
+ });
60665
+ setOutputDeviceSupported(adapter.outputDeviceSupported);
60666
+ adapter.enumerateOutputDevices().then((devices) => {
60667
+ if (disposed) return;
60668
+ setOutputDevices(devices.map(({ deviceId, label }) => ({
60669
+ deviceId,
60670
+ label
60671
+ })));
60672
+ setOutputDeviceId(adapter.outputDeviceId ?? "");
60673
+ }).catch(() => {
60674
+ if (!disposed) setOutputDevices([]);
60675
+ });
60676
+ };
60677
+ refresh();
60678
+ const mediaDevices = globalThis.navigator?.mediaDevices;
60679
+ const onChange = () => refresh();
60680
+ mediaDevices?.addEventListener?.("devicechange", onChange);
60681
+ return () => {
60682
+ disposed = true;
60683
+ mediaDevices?.removeEventListener?.("devicechange", onChange);
60684
+ };
60685
+ }, [services]);
60686
+ const chooseInputDevice = (deviceId) => {
60687
+ const adapter = getGlobalVoiceAdapter(services);
60688
+ if (adapter === void 0) return;
60689
+ adapter.selectInputDevice(deviceId).then(() => {
60690
+ setInputDeviceId(deviceId);
60691
+ notify({
60692
+ kind: "success",
60693
+ message: t("voice.microphoneUpdated")
60694
+ });
60695
+ }).catch((error) => notify({
60696
+ kind: "error",
60697
+ message: error instanceof Error ? error.message : String(error)
60698
+ }));
60699
+ };
60700
+ const chooseOutputDevice = (deviceId) => {
60701
+ const adapter = getGlobalVoiceAdapter(services);
60702
+ if (adapter === void 0) return;
60703
+ adapter.selectOutputDevice(deviceId).then(() => {
60704
+ setOutputDeviceId(deviceId);
60705
+ notify({
60706
+ kind: "success",
60707
+ message: t("voice.outputUpdated")
60708
+ });
60709
+ }).catch((error) => notify({
60710
+ kind: "error",
60711
+ message: error instanceof Error ? error.message : String(error)
60712
+ }));
60713
+ };
60714
+ const update = (workspaceId, checked) => {
60715
+ const current = new Set(value.managedWorkspaceIds);
60716
+ if (checked) current.add(workspaceId);
60717
+ else current.delete(workspaceId);
60718
+ services.settings.mutate([{
60719
+ op: "set",
60720
+ path: [CODINGNS_ASSISTANT_FIELD, "managedWorkspaceIds"],
60721
+ value: [...current]
60722
+ }]).then((accepted) => {
60723
+ if (!accepted) notify({
60724
+ kind: "error",
60725
+ message: t("settings.moduleWriteRejected")
60726
+ });
60727
+ else notify({
60728
+ kind: "success",
60729
+ message: t("assistant.saved")
60730
+ });
60731
+ }).catch((error) => notify({
60732
+ kind: "error",
60733
+ message: error instanceof Error ? error.message : String(error)
60734
+ }));
60735
+ };
60736
+ return (0, react.createElement)("div", {
60737
+ "aria-disabled": disabled,
60738
+ style: {
60739
+ ...dshFormRootStyle,
60740
+ display: "flex",
60741
+ flexDirection: "column",
60742
+ gap: 12,
60743
+ opacity: disabled ? .5 : 1,
60744
+ pointerEvents: disabled ? "none" : "auto"
60745
+ }
60746
+ }, (0, react.createElement)("p", { style: dshSettingsHelpStyle }, t("assistant.scopeDescription")), (0, react.createElement)("div", { style: dshSettingsListRowStyle }, (0, react.createElement)("span", { style: {
60747
+ minWidth: 0,
60748
+ flex: "1 1 auto"
60749
+ } }, (0, react.createElement)("strong", { style: {
60750
+ display: "block",
60751
+ fontSize: 13,
60752
+ lineHeight: 1.4
60753
+ } }, t("voice.setup.title")), (0, react.createElement)("span", { style: {
60754
+ display: "block",
60755
+ marginTop: 3,
60756
+ ...dshSettingsHelpStyle
60757
+ } }, voice.initialized && voice.provider === "sherpa-onnx" ? voiceModel?.label ?? t("voice.setup.status.sherpa") : t("voice.setup.status.uninitialized"))), (0, react.createElement)("button", {
60758
+ type: "button",
60759
+ disabled,
60760
+ onClick: () => setVoiceSetupOpen(true),
60761
+ style: {
60762
+ ...dshSettingsButtonStyle,
60763
+ flex: "0 0 auto",
60764
+ whiteSpace: "nowrap"
60765
+ }
60766
+ }, t("voice.setup.reconfigure"))), inputDevices.length === 0 ? null : (0, react.createElement)("label", { style: {
60767
+ ...dshSettingsListRowStyle,
60768
+ alignItems: "center"
60769
+ } }, (0, react.createElement)("span", { style: { minWidth: 0 } }, t("voice.inputDevice")), (0, react.createElement)("select", {
60770
+ value: inputDeviceId,
60771
+ disabled,
60772
+ onChange: (event) => chooseInputDevice(event.currentTarget.value),
60773
+ style: {
60774
+ maxWidth: 220,
60775
+ minWidth: 0,
60776
+ flex: "0 1 220px"
60777
+ }
60778
+ }, (0, react.createElement)("option", { value: "" }, t("voice.defaultMicrophone")), ...inputDevices.map((device) => (0, react.createElement)("option", {
60779
+ key: device.deviceId,
60780
+ value: device.deviceId
60781
+ }, device.label || t("voice.unnamedMicrophone"))))), !outputDeviceSupported ? null : (0, react.createElement)("label", { style: {
60782
+ ...dshSettingsListRowStyle,
60783
+ alignItems: "center"
60784
+ } }, (0, react.createElement)("span", { style: { minWidth: 0 } }, t("voice.outputDevice")), outputDevices.length === 0 ? (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("voice.defaultOutput")) : (0, react.createElement)("select", {
60785
+ value: outputDeviceId,
60786
+ disabled,
60787
+ onChange: (event) => chooseOutputDevice(event.currentTarget.value),
60788
+ style: {
60789
+ maxWidth: 220,
60790
+ minWidth: 0,
60791
+ flex: "0 1 220px"
60792
+ }
60793
+ }, (0, react.createElement)("option", { value: "" }, t("voice.defaultOutput")), ...outputDevices.map((device) => (0, react.createElement)("option", {
60794
+ key: device.deviceId,
60795
+ value: device.deviceId
60796
+ }, device.label || t("voice.unnamedOutput"))))), workspaces.length === 0 ? (0, react.createElement)("div", {
60797
+ role: "status",
60798
+ style: dshSettingsHelpStyle
60799
+ }, t("assistant.noWorkspaces")) : workspaces.map((workspace) => (0, react.createElement)("label", {
60800
+ key: workspace.workspaceId,
60801
+ style: dshSettingsListRowStyle
60802
+ }, (0, react.createElement)("span", { style: { minWidth: 0 } }, (0, react.createElement)("strong", { style: {
60803
+ display: "block",
60804
+ fontSize: 13,
60805
+ lineHeight: 1.4
60806
+ } }, workspace.title), workspace.path === void 0 ? null : (0, react.createElement)("span", { style: {
60807
+ display: "block",
60808
+ marginTop: 3,
60809
+ ...dshSettingsHelpStyle
60810
+ } }, workspace.path)), (0, react.createElement)("input", {
60811
+ type: "checkbox",
60812
+ role: "switch",
60813
+ "aria-label": workspace.title,
60814
+ checked: value.managedWorkspaceIds.includes(workspace.workspaceId),
60815
+ disabled,
60816
+ onChange: (event) => update(workspace.workspaceId, event.currentTarget.checked),
60817
+ style: {
60818
+ flex: "0 0 auto",
60819
+ accentColor: dshThemeColor.accent
60820
+ }
60821
+ }))), voiceSetupOpen ? (0, react.createElement)(VoiceInitializationDialog, {
60822
+ services,
60823
+ value: voice,
60824
+ onClose: () => setVoiceSetupOpen(false)
60825
+ }) : null);
60826
+ }
60827
+ //#endregion
60828
+ //#region src/client/voice-security.ts
60829
+ function inspectBrowserVoiceSecurity() {
60830
+ const environment = globalThis;
60831
+ const windowLike = environment.window;
60832
+ const location = windowLike?.location ?? environment.location;
60833
+ const protocol = typeof location?.protocol === "string" ? location.protocol.toLowerCase() : "";
60834
+ const hostname = typeof location?.hostname === "string" ? location.hostname.toLowerCase() : "";
60835
+ const secureValue = typeof windowLike?.isSecureContext === "boolean" ? windowLike.isSecureContext : typeof environment.isSecureContext === "boolean" ? environment.isSecureContext : void 0;
60836
+ if (!(windowLike !== void 0 || environment.document !== void 0 || secureValue !== void 0 || protocol !== "")) return {
60837
+ secure: true,
60838
+ protocol
60839
+ };
60840
+ if (secureValue === false) return {
60841
+ secure: false,
60842
+ protocol,
60843
+ reason: "isSecureContext"
60844
+ };
60845
+ if (protocol === "http:" && !isLoopbackHostname(hostname)) return {
60846
+ secure: false,
60847
+ protocol,
60848
+ reason: "insecureProtocol"
60849
+ };
60850
+ if (secureValue === true || protocol === "https:" || protocol === "wss:" || protocol === "file:" || protocol === "chrome-extension:") return {
60851
+ secure: true,
60852
+ protocol
60853
+ };
60854
+ if (protocol === "http:" && isLoopbackHostname(hostname)) return {
60855
+ secure: true,
60856
+ protocol
60857
+ };
60858
+ return {
60859
+ secure: false,
60860
+ protocol,
60861
+ reason: "unknown"
60862
+ };
60863
+ }
60864
+ function isLoopbackHostname(hostname) {
60865
+ return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]" || hostname === "::1";
60866
+ }
60867
+ //#endregion
60868
+ //#region src/client/voice-device-manager.ts
60869
+ const INPUT_KEY = "codingns4dsh.voice.input-device";
60870
+ const OUTPUT_KEY = "codingns4dsh.voice.output-device";
60871
+ /**
60872
+ * 浏览器 Client 的设备管理器。
60873
+ *
60874
+ * 设备权限、设备 ID 和设备变化都属于当前浏览器来源;Host 只会收到选择后的
60875
+ * PCM 数据,不会接触这里的真实硬件清单。
60876
+ */
60877
+ var BrowserVoiceDeviceManager = class {
60878
+ listeners = /* @__PURE__ */ new Set();
60879
+ current;
60880
+ deviceChangeDispose;
60881
+ disposed = false;
60882
+ constructor() {
60883
+ const security = inspectBrowserVoiceSecurity();
60884
+ this.current = {
60885
+ secureContext: security.secure,
60886
+ permission: "unknown",
60887
+ inputs: [],
60888
+ outputs: [],
60889
+ selectedInputId: readStorage(INPUT_KEY),
60890
+ selectedOutputId: readStorage(OUTPUT_KEY)
60891
+ };
60892
+ const mediaDevices = globalThis.navigator?.mediaDevices;
60893
+ if (mediaDevices !== void 0 && typeof mediaDevices.addEventListener === "function") {
60894
+ const onDeviceChange = () => {
60895
+ this.refresh();
60896
+ };
60897
+ mediaDevices.addEventListener("devicechange", onDeviceChange);
60898
+ this.deviceChangeDispose = () => mediaDevices.removeEventListener("devicechange", onDeviceChange);
60899
+ } else this.deviceChangeDispose = void 0;
60900
+ }
60901
+ snapshot() {
60902
+ return cloneSnapshot(this.current);
60903
+ }
60904
+ subscribe(listener) {
60905
+ this.listeners.add(listener);
60906
+ listener(this.snapshot());
60907
+ return () => this.listeners.delete(listener);
60908
+ }
60909
+ async refresh() {
60910
+ if (this.disposed) return this.snapshot();
60911
+ const security = inspectBrowserVoiceSecurity();
60912
+ const mediaDevices = globalThis.navigator?.mediaDevices;
60913
+ if (!security.secure || mediaDevices === void 0 || typeof mediaDevices.enumerateDevices !== "function") {
60914
+ this.update({
60915
+ secureContext: security.secure,
60916
+ inputs: [],
60917
+ outputs: []
60918
+ });
60919
+ return this.snapshot();
60920
+ }
60921
+ const devices = await mediaDevices.enumerateDevices();
60922
+ const inputs = devices.filter((device) => device.kind === "audioinput").map(toDevice("audioinput"));
60923
+ const outputs = devices.filter((device) => device.kind === "audiooutput").map(toDevice("audiooutput"));
60924
+ const selectedInputId = this.current.selectedInputId !== null && inputs.some((device) => device.deviceId === this.current.selectedInputId) ? this.current.selectedInputId : null;
60925
+ const selectedOutputId = this.current.selectedOutputId !== null && outputs.some((device) => device.deviceId === this.current.selectedOutputId) ? this.current.selectedOutputId : null;
60926
+ if (selectedInputId === null) removeStorage(INPUT_KEY);
60927
+ if (selectedOutputId === null) removeStorage(OUTPUT_KEY);
60928
+ this.update({
60929
+ inputs,
60930
+ outputs,
60931
+ selectedInputId,
60932
+ selectedOutputId
60933
+ });
60934
+ await this.refreshPermission();
60935
+ return this.snapshot();
60936
+ }
60937
+ async requestInputPermission() {
60938
+ if (this.disposed) throw new Error("语音设备管理器已释放");
60939
+ if (!inspectBrowserVoiceSecurity().secure) throw new Error("当前页面不是安全上下文,请改用 HTTPS 或 localhost 后访问麦克风");
60940
+ const mediaDevices = globalThis.navigator?.mediaDevices;
60941
+ if (mediaDevices === void 0 || typeof mediaDevices.getUserMedia !== "function") throw new Error("当前浏览器不支持 getUserMedia");
60942
+ const selected = this.current.selectedInputId;
60943
+ let stream;
60944
+ try {
60945
+ stream = await mediaDevices.getUserMedia({
60946
+ audio: buildInputConstraints(selected),
60947
+ video: false
60948
+ });
60949
+ } catch (error) {
60950
+ if (selected === null || !isMissingInputDeviceError(error)) throw error;
60951
+ removeStorage(INPUT_KEY);
60952
+ this.update({ selectedInputId: null });
60953
+ stream = await mediaDevices.getUserMedia({
60954
+ audio: buildInputConstraints(null),
60955
+ video: false
60956
+ });
60957
+ }
60958
+ for (const track of stream.getTracks()) track.stop();
60959
+ await this.refresh();
60960
+ this.update({ permission: "granted" });
60961
+ return this.snapshot();
60962
+ }
60963
+ async selectInput(deviceId) {
60964
+ const value = deviceId.trim();
60965
+ if (value !== "" && !this.current.inputs.some((device) => device.deviceId === value)) {
60966
+ await this.refresh();
60967
+ if (!this.current.inputs.some((device) => device.deviceId === value)) throw new Error("选择的麦克风设备不存在");
60968
+ }
60969
+ if (value === "") removeStorage(INPUT_KEY);
60970
+ else writeStorage(INPUT_KEY, value);
60971
+ this.update({ selectedInputId: value === "" ? null : value });
60972
+ return this.snapshot();
60973
+ }
60974
+ async selectOutput(deviceId) {
60975
+ const value = deviceId.trim();
60976
+ if (value !== "" && !this.current.outputs.some((device) => device.deviceId === value)) {
60977
+ await this.refresh();
60978
+ if (!this.current.outputs.some((device) => device.deviceId === value)) throw new Error("选择的播放设备不存在");
60979
+ }
60980
+ if (value === "") removeStorage(OUTPUT_KEY);
60981
+ else writeStorage(OUTPUT_KEY, value);
60982
+ this.update({ selectedOutputId: value === "" ? null : value });
60983
+ return this.snapshot();
60984
+ }
60985
+ dispose() {
60986
+ if (this.disposed) return;
60987
+ this.disposed = true;
60988
+ this.deviceChangeDispose?.();
60989
+ this.listeners.clear();
60990
+ }
60991
+ async refreshPermission() {
60992
+ const permissions = globalThis.navigator?.permissions;
60993
+ if (permissions === void 0 || typeof permissions.query !== "function") return;
60994
+ try {
60995
+ const status = await permissions.query({ name: "microphone" });
60996
+ this.update({ permission: status.state === "granted" || status.state === "denied" ? status.state : "prompt" });
60997
+ } catch {}
60998
+ }
60999
+ update(patch) {
61000
+ this.current = {
61001
+ ...this.current,
61002
+ ...patch
61003
+ };
61004
+ const snapshot = this.snapshot();
61005
+ for (const listener of [...this.listeners]) try {
61006
+ listener(snapshot);
61007
+ } catch {}
61008
+ }
61009
+ };
61010
+ function buildInputConstraints(selectedInputId) {
61011
+ return selectedInputId === null ? {
61012
+ echoCancellation: true,
61013
+ noiseSuppression: true,
61014
+ autoGainControl: true
61015
+ } : {
61016
+ deviceId: { exact: selectedInputId },
61017
+ echoCancellation: true,
61018
+ noiseSuppression: true,
61019
+ autoGainControl: true
61020
+ };
61021
+ }
61022
+ function isMissingInputDeviceError(error) {
61023
+ if (typeof error !== "object" || error === null) return false;
61024
+ const name = "name" in error && typeof error.name === "string" ? error.name : "";
61025
+ return name === "NotFoundError" || name === "OverconstrainedError" || name === "DevicesNotFoundError";
61026
+ }
61027
+ function toDevice(kind) {
61028
+ return (device) => ({
61029
+ kind,
61030
+ deviceId: device.deviceId,
61031
+ label: device.label,
61032
+ groupId: device.groupId
61033
+ });
61034
+ }
61035
+ function cloneSnapshot(value) {
61036
+ return {
61037
+ ...value,
61038
+ inputs: [...value.inputs],
61039
+ outputs: [...value.outputs]
61040
+ };
61041
+ }
61042
+ function readStorage(key) {
61043
+ try {
61044
+ return (globalThis.localStorage?.getItem(key))?.trim() || null;
61045
+ } catch {
61046
+ return null;
61047
+ }
61048
+ }
61049
+ function writeStorage(key, value) {
61050
+ try {
61051
+ globalThis.localStorage?.setItem(key, value);
61052
+ } catch {}
61053
+ }
61054
+ function removeStorage(key) {
61055
+ try {
61056
+ globalThis.localStorage?.removeItem(key);
61057
+ } catch {}
61058
+ }
61059
+ //#endregion
61060
+ //#region src/client/voice-capture.ts
61061
+ /** 浏览器端实时 PCM 采集器;设备权限始终由 Client 自己持有。 */
61062
+ var ClientVoiceCapture = class {
61063
+ options;
61064
+ stream;
61065
+ context;
61066
+ source;
61067
+ processor;
61068
+ output;
61069
+ workletUrl;
61070
+ sequence = 0;
61071
+ started = false;
61072
+ inputRate = 16e3;
61073
+ constructor(options) {
61074
+ this.options = options;
61075
+ }
61076
+ get isStarted() {
61077
+ return this.started;
61078
+ }
61079
+ async start() {
61080
+ if (this.started) return;
61081
+ if (!inspectBrowserVoiceSecurity().secure) throw new Error("当前页面不是安全上下文,请改用 HTTPS 或 localhost 后访问麦克风");
61082
+ const mediaDevices = globalThis.navigator?.mediaDevices;
61083
+ if (mediaDevices === void 0 || typeof mediaDevices.getUserMedia !== "function") throw new Error("当前浏览器不支持 getUserMedia");
61084
+ const selected = (await this.options.devices.requestInputPermission()).selectedInputId;
61085
+ const constraints = {
61086
+ audio: selected === null ? {
61087
+ channelCount: 1,
61088
+ echoCancellation: true,
61089
+ noiseSuppression: true,
61090
+ autoGainControl: true
61091
+ } : {
61092
+ deviceId: { exact: selected },
61093
+ channelCount: 1,
61094
+ echoCancellation: true,
61095
+ noiseSuppression: true,
61096
+ autoGainControl: true
61097
+ },
61098
+ video: false
61099
+ };
61100
+ const stream = await mediaDevices.getUserMedia(constraints);
61101
+ this.stream = stream;
61102
+ try {
61103
+ const Context = globalThis.AudioContext ?? globalThis.webkitAudioContext;
61104
+ if (Context === void 0) throw new Error("当前浏览器不支持 AudioContext");
61105
+ const context = new Context({ sampleRate: this.options.targetSampleRate ?? 16e3 });
61106
+ this.context = context;
61107
+ this.inputRate = context.sampleRate;
61108
+ await context.resume();
61109
+ this.source = context.createMediaStreamSource(stream);
61110
+ this.output = context.createGain();
61111
+ this.output.gain.value = 0;
61112
+ this.output.connect(context.destination);
61113
+ this.sequence = 0;
61114
+ if (context.audioWorklet !== void 0 && typeof AudioWorkletNode === "function") await this.startWorklet(context);
61115
+ else this.startScriptProcessor(context);
61116
+ this.started = true;
61117
+ for (const track of stream.getAudioTracks()) track.addEventListener("ended", this.handleEnded, { once: true });
61118
+ } catch (error) {
61119
+ await this.stop();
61120
+ throw error;
61121
+ }
61122
+ }
61123
+ async stop() {
61124
+ const stream = this.stream;
61125
+ const processor = this.processor;
61126
+ this.started = false;
61127
+ this.stream = void 0;
61128
+ this.processor = void 0;
61129
+ this.source?.disconnect();
61130
+ this.source = void 0;
61131
+ processor?.disconnect();
61132
+ this.output?.disconnect();
61133
+ this.output = void 0;
61134
+ if (this.context !== void 0 && this.context.state !== "closed") await this.context.close();
61135
+ this.context = void 0;
61136
+ if (this.workletUrl !== void 0) URL.revokeObjectURL(this.workletUrl);
61137
+ this.workletUrl = void 0;
61138
+ for (const track of stream?.getTracks() ?? []) {
61139
+ track.removeEventListener("ended", this.handleEnded);
61140
+ try {
61141
+ track.stop();
61142
+ } catch {}
61143
+ }
61144
+ }
61145
+ async startWorklet(context) {
61146
+ const source = this.source;
61147
+ const output = this.output;
61148
+ if (source === void 0 || output === void 0 || context.audioWorklet === void 0) throw new Error("音频采集图未初始化");
61149
+ const blob = new Blob([`class CodingNsVoiceProcessor extends AudioWorkletProcessor { process(inputs) { const input = inputs[0] && inputs[0][0]; if (input) this.port.postMessage(new Float32Array(input)); return true; } } registerProcessor('codingns-voice-capture', CodingNsVoiceProcessor);`], { type: "application/javascript" });
61150
+ this.workletUrl = URL.createObjectURL(blob);
61151
+ await context.audioWorklet.addModule(this.workletUrl);
61152
+ const node = new AudioWorkletNode(context, "codingns-voice-capture", {
61153
+ numberOfInputs: 1,
61154
+ numberOfOutputs: 1,
61155
+ outputChannelCount: [1]
61156
+ });
61157
+ node.port.onmessage = (event) => {
61158
+ if (!this.started && this.context !== context) return;
61159
+ const samples = event.data instanceof Float32Array ? event.data : new Float32Array(event.data);
61160
+ this.emitSamples(samples);
61161
+ };
61162
+ source.connect(node);
61163
+ node.connect(output);
61164
+ this.processor = node;
61165
+ }
61166
+ startScriptProcessor(context) {
61167
+ const source = this.source;
61168
+ const output = this.output;
61169
+ if (source === void 0 || output === void 0) throw new Error("音频采集图未初始化");
61170
+ const node = context.createScriptProcessor(1024, 1, 1);
61171
+ node.onaudioprocess = (event) => this.emitSamples(event.inputBuffer.getChannelData(0));
61172
+ source.connect(node);
61173
+ node.connect(output);
61174
+ this.processor = node;
61175
+ }
61176
+ handleEnded = () => {
61177
+ if (!this.started) return;
61178
+ this.options.onEnded?.(/* @__PURE__ */ new Error("麦克风轨道已结束"));
61179
+ this.stop();
61180
+ };
61181
+ emitSamples(samples) {
61182
+ if (!this.started) return;
61183
+ const bytes = resampleFloat32ToPcm16(samples, this.inputRate, this.options.targetSampleRate ?? 16e3);
61184
+ if (bytes.byteLength === 0) return;
61185
+ const frame = {
61186
+ sequence: ++this.sequence,
61187
+ bytes,
61188
+ sampleRate: this.options.targetSampleRate ?? 16e3,
61189
+ channels: 1
61190
+ };
61191
+ Promise.resolve(this.options.onFrame(frame)).catch((error) => this.options.onEnded?.(error instanceof Error ? error : new Error(String(error))));
61192
+ }
61193
+ };
61194
+ /** 无依赖的线性重采样,适合语音 16 kHz 采集;整段音频不会进入内存。 */
61195
+ function resampleFloat32ToPcm16(samples, inputRate, outputRate) {
61196
+ if (samples.length === 0) return /* @__PURE__ */ new Uint8Array();
61197
+ if (!Number.isFinite(inputRate) || inputRate <= 0 || !Number.isFinite(outputRate) || outputRate <= 0) throw new RangeError("采样率必须为正数");
61198
+ const outputLength = Math.max(1, Math.round(samples.length * outputRate / inputRate));
61199
+ const bytes = new Uint8Array(outputLength * 2);
61200
+ const view = new DataView(bytes.buffer);
61201
+ for (let index = 0; index < outputLength; index += 1) {
61202
+ const sourcePosition = index * inputRate / outputRate;
61203
+ const left = Math.min(samples.length - 1, Math.floor(sourcePosition));
61204
+ const right = Math.min(samples.length - 1, left + 1);
61205
+ const fraction = sourcePosition - left;
61206
+ const sample = Math.max(-1, Math.min(1, (samples[left] ?? 0) * (1 - fraction) + (samples[right] ?? 0) * fraction));
61207
+ view.setInt16(index * 2, Math.round(sample * (sample < 0 ? 32768 : 32767)), true);
61208
+ }
61209
+ return bytes;
61210
+ }
61211
+ //#endregion
61212
+ //#region src/client/sherpa-voice-adapter.ts
61213
+ init_transport();
61214
+ /** 浏览器侧 Sherpa 数据面:设备和权限在 Client,识别在 Host。 */
61215
+ var ClientSherpaVoiceAdapter = class {
61216
+ listeners = /* @__PURE__ */ new Set();
61217
+ services;
61218
+ devices;
61219
+ options;
61220
+ capture;
61221
+ uploader;
61222
+ responseAbort;
61223
+ active = false;
61224
+ hostLeaseActive = false;
61225
+ epoch = 0;
61226
+ configuredOwner;
61227
+ selectedInputDeviceId;
61228
+ selectedOutputDeviceId;
61229
+ constructor(options) {
61230
+ this.options = options;
61231
+ this.services = options.services;
61232
+ this.configuredOwner = options.ownerId;
61233
+ this.devices = new BrowserVoiceDeviceManager();
61234
+ const snapshot = this.devices.snapshot();
61235
+ this.selectedInputDeviceId = snapshot.selectedInputId ?? void 0;
61236
+ this.selectedOutputDeviceId = snapshot.selectedOutputId ?? void 0;
61237
+ this.devices.subscribe((next) => {
61238
+ this.selectedInputDeviceId = next.selectedInputId ?? void 0;
61239
+ this.selectedOutputDeviceId = next.selectedOutputId ?? void 0;
61240
+ });
61241
+ }
61242
+ get capabilities() {
61243
+ const supported = this.devices.snapshot().secureContext && typeof globalThis.fetch === "function" && typeof globalThis.ReadableStream === "function";
61244
+ return {
61245
+ realtime: supported,
61246
+ wakeWord: false,
61247
+ streamingInput: supported,
61248
+ streamingOutput: false,
61249
+ bargeIn: supported,
61250
+ speechToText: supported,
61251
+ textToSpeech: typeof globalThis.speechSynthesis !== "undefined"
61252
+ };
61253
+ }
61254
+ get ownerId() {
61255
+ return this.active ? this.configuredOwner : void 0;
61256
+ }
61257
+ get configuredOwnerId() {
61258
+ return this.configuredOwner;
61259
+ }
61260
+ get inputDeviceId() {
61261
+ return this.selectedInputDeviceId;
61262
+ }
61263
+ get outputDeviceId() {
61264
+ return this.selectedOutputDeviceId;
61265
+ }
61266
+ get outputDeviceSupported() {
61267
+ return typeof (globalThis.HTMLMediaElement?.prototype)?.setSinkId === "function";
61268
+ }
61269
+ subscribe(listener) {
61270
+ this.listeners.add(listener);
61271
+ return () => this.listeners.delete(listener);
61272
+ }
61273
+ async enumerateInputDevices() {
61274
+ return (await this.devices.refresh()).inputs;
61275
+ }
61276
+ async enumerateOutputDevices() {
61277
+ return (await this.devices.refresh()).outputs;
61278
+ }
61279
+ async selectInputDevice(deviceId) {
61280
+ await this.devices.selectInput(deviceId);
61281
+ this.selectedInputDeviceId = this.devices.snapshot().selectedInputId ?? void 0;
61282
+ if (this.active) {
61283
+ await this.stop();
61284
+ await this.start(this.configuredOwner);
61285
+ }
61286
+ }
61287
+ async selectOutputDevice(deviceId) {
61288
+ if (!this.outputDeviceSupported) throw new Error("当前浏览器不支持输出设备选择");
61289
+ await this.devices.selectOutput(deviceId);
61290
+ this.selectedOutputDeviceId = this.devices.snapshot().selectedOutputId ?? void 0;
61291
+ await this.applyOutputDevice();
61292
+ }
61293
+ async start(ownerId) {
61294
+ if (this.active) return;
61295
+ this.configuredOwner = ownerId?.trim() || this.configuredOwner;
61296
+ if (this.configuredOwner === "") throw new Error("全局语音租约缺少 ownerId");
61297
+ const lease = await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/start", { ownerId: this.configuredOwner });
61298
+ if (!lease.ok) throw new Error(lease.error.message);
61299
+ if (isRecord$2(lease.value) && lease.value.unavailable === true) throw new Error(typeof lease.value.message === "string" ? lease.value.message : "本地 Sherpa 语音运行时不可用");
61300
+ this.hostLeaseActive = true;
61301
+ if (!(await this.devices.refresh()).secureContext) throw new Error("当前页面不是安全上下文,请改用 HTTPS 或 localhost 后访问麦克风");
61302
+ this.epoch += 1;
61303
+ const epoch = this.epoch;
61304
+ const uploader = new VoiceStreamUploader();
61305
+ const abort = new AbortController();
61306
+ this.uploader = uploader;
61307
+ this.responseAbort = abort;
61308
+ this.active = true;
61309
+ this.emit({
61310
+ type: "state",
61311
+ state: "loading",
61312
+ epoch
61313
+ });
61314
+ try {
61315
+ uploader.enqueue(encodeVoiceStreamOpen({
61316
+ ownerId: this.configuredOwner,
61317
+ clientId: createClientId(),
61318
+ sampleRate: 16e3,
61319
+ channels: 1,
61320
+ encoding: "pcm16le"
61321
+ }));
61322
+ const response = await globalThis.fetch(resolveVoiceStreamUrl(), {
61323
+ method: "POST",
61324
+ body: uploader.stream,
61325
+ signal: abort.signal,
61326
+ credentials: "same-origin",
61327
+ headers: {
61328
+ "content-type": "application/octet-stream",
61329
+ accept: "application/x-ndjson"
61330
+ },
61331
+ duplex: "half"
61332
+ });
61333
+ if (!response.ok || response.body === null) throw new Error(`语音流连接失败(HTTP ${response.status})`);
61334
+ this.readEvents(response.body, epoch);
61335
+ const capture = new ClientVoiceCapture({
61336
+ devices: this.devices,
61337
+ targetSampleRate: 16e3,
61338
+ onFrame: (frame) => this.sendPcmFrame(frame, epoch),
61339
+ onEnded: (error) => this.emit({
61340
+ type: "error",
61341
+ code: "voice_capture_ended",
61342
+ message: error.message,
61343
+ recoverable: true,
61344
+ epoch
61345
+ })
61346
+ });
61347
+ this.capture = capture;
61348
+ await capture.start();
61349
+ this.emit({
61350
+ type: "state",
61351
+ state: "listening",
61352
+ epoch
61353
+ });
61354
+ } catch (error) {
61355
+ await this.stop().catch(() => void 0);
61356
+ throw error;
61357
+ }
61358
+ }
61359
+ async stop() {
61360
+ if (!this.active && this.capture === void 0 && !this.hostLeaseActive) return;
61361
+ this.active = false;
61362
+ let captureError;
61363
+ try {
61364
+ await this.capture?.stop();
61365
+ } catch (error) {
61366
+ captureError = error;
61367
+ }
61368
+ this.capture = void 0;
61369
+ this.uploader?.enqueue(encodeVoiceStreamMessage({
61370
+ type: "close",
61371
+ epoch: this.epoch
61372
+ }));
61373
+ this.uploader?.close();
61374
+ this.uploader = void 0;
61375
+ this.responseAbort?.abort();
61376
+ this.responseAbort = void 0;
61377
+ if (this.hostLeaseActive) {
61378
+ this.hostLeaseActive = false;
61379
+ await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/stop", { ownerId: this.configuredOwner }).catch(() => void 0);
61380
+ }
61381
+ this.epoch += 1;
61382
+ this.emit({
61383
+ type: "state",
61384
+ state: "disabled",
61385
+ epoch: this.epoch
61386
+ });
61387
+ if (captureError !== void 0) throw captureError;
61388
+ }
61389
+ async interrupt() {
61390
+ this.epoch += 1;
61391
+ globalThis.speechSynthesis?.cancel();
61392
+ this.uploader?.enqueue(encodeVoiceStreamMessage({
61393
+ type: "interrupt",
61394
+ epoch: this.epoch
61395
+ }));
61396
+ await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/interrupt", { ownerId: this.configuredOwner }).catch(() => void 0);
61397
+ this.emit({
61398
+ type: "barge-in",
61399
+ epoch: this.epoch
61400
+ });
61401
+ }
61402
+ sendPcm(frame, epoch) {
61403
+ this.sendPcmFrame(frame, epoch);
61404
+ }
61405
+ async speak(text, epoch) {
61406
+ if (epoch !== this.epoch || text.trim() === "") return;
61407
+ const synthesis = globalThis.speechSynthesis;
61408
+ if (synthesis === void 0 || typeof globalThis.SpeechSynthesisUtterance !== "function") throw new Error("当前浏览器不支持语音播报");
61409
+ synthesis.cancel();
61410
+ await new Promise((resolve) => {
61411
+ const utterance = new SpeechSynthesisUtterance(text.trim());
61412
+ utterance.lang = "zh-CN";
61413
+ utterance.onend = () => resolve();
61414
+ utterance.onerror = () => resolve();
61415
+ synthesis.speak(utterance);
61416
+ });
61417
+ }
61418
+ dispose() {
61419
+ this.stop();
61420
+ this.devices.dispose();
61421
+ this.listeners.clear();
61422
+ }
61423
+ sendPcmFrame(frame, epoch) {
61424
+ if (!this.active || this.uploader === void 0 || epoch !== this.epoch) return;
61425
+ this.uploader.enqueue(encodeVoiceStreamMessage({
61426
+ type: "pcm",
61427
+ sequence: frame.sequence,
61428
+ sampleRate: frame.sampleRate,
61429
+ channels: 1,
61430
+ epoch,
61431
+ byteLength: frame.bytes.byteLength
61432
+ }));
61433
+ this.uploader.enqueue(frame.bytes);
61434
+ }
61435
+ async readEvents(body, streamEpoch) {
61436
+ const reader = body.getReader();
61437
+ const decoder = new TextDecoder();
61438
+ let text = "";
61439
+ try {
61440
+ while (true) {
61441
+ const next = await reader.read();
61442
+ if (next.done) break;
61443
+ text += decoder.decode(next.value, { stream: true });
61444
+ let newline = text.indexOf("\n");
61445
+ while (newline >= 0) {
61446
+ const line = text.slice(0, newline).trim();
61447
+ text = text.slice(newline + 1);
61448
+ if (line !== "") await this.handleEvent(JSON.parse(line), streamEpoch);
61449
+ newline = text.indexOf("\n");
61450
+ }
61451
+ }
61452
+ } catch (error) {
61453
+ if (this.active) this.emit({
61454
+ type: "error",
61455
+ code: "voice_stream_response_failed",
61456
+ message: error instanceof Error ? error.message : String(error),
61457
+ recoverable: true,
61458
+ epoch: streamEpoch
61459
+ });
61460
+ }
61461
+ }
61462
+ async handleEvent(value, streamEpoch) {
61463
+ if (streamEpoch !== this.epoch || typeof value.type !== "string") return;
61464
+ if (value.type === "state" && typeof value.state === "string") this.emit({
61465
+ type: "state",
61466
+ state: value.state,
61467
+ epoch: readEpoch(value, streamEpoch)
61468
+ });
61469
+ else if (value.type === "partial" && typeof value.text === "string") this.emit({
61470
+ type: "partial",
61471
+ text: value.text,
61472
+ epoch: readEpoch(value, streamEpoch)
61473
+ });
61474
+ else if (value.type === "final" && typeof value.text === "string") {
61475
+ const eventEpoch = readEpoch(value, streamEpoch);
61476
+ this.emit({
61477
+ type: "final",
61478
+ text: value.text,
61479
+ epoch: eventEpoch
61480
+ });
61481
+ const result = await this.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/text", {
61482
+ ownerId: this.configuredOwner,
61483
+ text: value.text,
61484
+ requestId: createClientId()
61485
+ });
61486
+ if (result.ok && isRecord$2(result.value) && typeof result.value.speechText === "string") await this.speak(result.value.speechText, eventEpoch);
61487
+ } else if (value.type === "barge-in") this.emit({
61488
+ type: "barge-in",
61489
+ epoch: readEpoch(value, streamEpoch)
61490
+ });
61491
+ else if (value.type === "error") this.emit({
61492
+ type: "error",
61493
+ code: String(value.code ?? "voice_stream_error"),
61494
+ message: String(value.message ?? value.code ?? ""),
61495
+ recoverable: value.recoverable !== false,
61496
+ epoch: readEpoch(value, streamEpoch)
61497
+ });
61498
+ }
61499
+ async applyOutputDevice() {
61500
+ if (!this.outputDeviceSupported) return;
61501
+ for (const media of [...document.querySelectorAll("audio,video")]) {
61502
+ const sink = media.setSinkId;
61503
+ if (typeof sink === "function") await sink.call(media, this.selectedOutputDeviceId ?? "");
61504
+ }
61505
+ }
61506
+ emit(event) {
61507
+ for (const listener of [...this.listeners]) try {
61508
+ listener(event);
61509
+ } catch {}
61510
+ }
61511
+ };
61512
+ var VoiceStreamUploader = class {
61513
+ stream;
61514
+ controller;
61515
+ closed = false;
61516
+ constructor() {
61517
+ this.stream = new ReadableStream({ start: (controller) => {
61518
+ this.controller = controller;
61519
+ } });
61520
+ }
61521
+ enqueue(bytes) {
61522
+ if (!this.closed) try {
61523
+ this.controller?.enqueue(bytes.slice());
61524
+ } catch {
61525
+ this.closed = true;
61526
+ }
61527
+ }
61528
+ close() {
61529
+ if (this.closed) return;
61530
+ this.closed = true;
61531
+ try {
61532
+ this.controller?.close();
61533
+ } catch {}
61534
+ }
61535
+ };
61536
+ function resolveVoiceStreamUrl() {
61537
+ return new URL("/api/codingns/assistant/voice/stream", globalThis.location?.origin ?? "http://localhost").toString();
61538
+ }
61539
+ function createClientId() {
61540
+ try {
61541
+ return globalThis.crypto?.randomUUID?.() ?? `${Date.now()}-${Math.random()}`;
61542
+ } catch {
61543
+ return `${Date.now()}-${Math.random()}`;
61544
+ }
61545
+ }
61546
+ function readEpoch(value, fallback) {
61547
+ return typeof value.epoch === "number" && Number.isInteger(value.epoch) ? value.epoch : fallback;
61548
+ }
61549
+ function isRecord$2(value) {
61550
+ return typeof value === "object" && value !== null && !Array.isArray(value);
61551
+ }
61552
+ //#endregion
61553
+ //#region src/client/features/voice-conversation-dialog.ts
61554
+ init_theme();
61555
+ /** 全局语音助理独立对话窗口;打开窗口不会自动申请麦克风。 */
61556
+ function VoiceConversationDialog({ t, active, pending, state, message, partialText, transcript, realtimeAvailable, unavailableMessage, onStart, onStop, onClose, onClear }) {
61557
+ const status = statusLabel(t, state, active);
61558
+ const canStart = !active && !pending;
61559
+ const transcriptRef = (0, react.useRef)(null);
61560
+ (0, react.useEffect)(() => {
61561
+ const element = transcriptRef.current;
61562
+ if (element !== null) element.scrollTop = element.scrollHeight;
61563
+ }, [partialText, transcript.length]);
61564
+ return (0, react.createElement)("div", {
61565
+ role: "presentation",
61566
+ onPointerDown: () => {
61567
+ if (!pending) onClose();
61568
+ },
61569
+ style: {
61570
+ position: "fixed",
61571
+ inset: 0,
61572
+ zIndex: 1e4,
61573
+ display: "flex",
61574
+ alignItems: "center",
61575
+ justifyContent: "center",
61576
+ padding: 16,
61577
+ background: dshThemeColor.overlay,
61578
+ boxSizing: "border-box"
61579
+ }
61580
+ }, (0, react.createElement)("div", {
61581
+ role: "dialog",
61582
+ "aria-modal": true,
61583
+ "aria-label": t("voice.dialog.title"),
61584
+ onPointerDown: (event) => event.stopPropagation(),
61585
+ style: {
61586
+ display: "flex",
61587
+ flexDirection: "column",
61588
+ gap: 14,
61589
+ width: "min(620px, 100%)",
61590
+ maxHeight: "min(700px, 100%)",
61591
+ overflowY: "auto",
61592
+ padding: 24,
61593
+ color: dshThemeColor.labelPrimary,
61594
+ background: dshThemeColor.menuBackground,
61595
+ border: `1px solid ${dshThemeColor.border}`,
61596
+ borderRadius: 12,
61597
+ boxShadow: dshThemeColor.prominentShadow,
61598
+ boxSizing: "border-box"
61599
+ }
61600
+ }, (0, react.createElement)("div", { style: {
61601
+ display: "flex",
61602
+ alignItems: "flex-start",
61603
+ justifyContent: "space-between",
61604
+ gap: 16
61605
+ } }, (0, react.createElement)("div", { style: {
61606
+ display: "grid",
61607
+ gap: 6,
61608
+ minWidth: 0
61609
+ } }, (0, react.createElement)("strong", { style: {
61610
+ fontSize: 18,
61611
+ lineHeight: 1.4
61612
+ } }, t("voice.dialog.title")), (0, react.createElement)("span", { style: dshSettingsHelpStyle }, t("voice.dialog.description"))), (0, react.createElement)("span", {
61613
+ role: "status",
61614
+ style: {
61615
+ flex: "0 0 auto",
61616
+ color: active ? dshThemeColor.success : dshThemeColor.labelSecondary,
61617
+ fontSize: 12
61618
+ }
61619
+ }, status)), (0, react.createElement)("div", { style: {
61620
+ padding: "10px 12px",
61621
+ borderRadius: 8,
61622
+ background: dshThemeColor.surfaceSubtle,
61623
+ color: dshThemeColor.labelSecondary,
61624
+ fontSize: 12,
61625
+ lineHeight: 1.5
61626
+ } }, realtimeAvailable ? t("voice.dialog.realtimeHint") : unavailableMessage ?? t("voice.dialog.unavailable")), (0, react.createElement)("div", {
61627
+ "aria-live": "polite",
61628
+ ref: transcriptRef,
61629
+ style: {
61630
+ display: "flex",
61631
+ flexDirection: "column",
61632
+ gap: 8,
61633
+ minHeight: 180,
61634
+ maxHeight: 340,
61635
+ overflowY: "auto",
61636
+ padding: 14,
61637
+ border: `1px solid ${dshThemeColor.border}`,
61638
+ borderRadius: 8,
61639
+ background: dshThemeColor.inputBackground,
61640
+ boxSizing: "border-box"
61641
+ }
61642
+ }, (0, react.createElement)("div", { style: {
61643
+ color: dshThemeColor.labelTertiary,
61644
+ fontSize: 12,
61645
+ fontWeight: 600
61646
+ } }, t("voice.dialog.liveText")), transcript.length === 0 && partialText === "" ? (0, react.createElement)("div", { style: {
61647
+ color: dshThemeColor.labelTertiary,
61648
+ fontSize: 14,
61649
+ lineHeight: 1.6
61650
+ } }, t("voice.dialog.empty")) : null, ...transcript.map((text, index) => (0, react.createElement)("div", {
61651
+ key: `${index}-${text}`,
61652
+ style: {
61653
+ color: dshThemeColor.labelPrimary,
61654
+ fontSize: 15,
61655
+ lineHeight: 1.65
61656
+ }
61657
+ }, text)), partialText === "" ? null : (0, react.createElement)("div", { style: {
61658
+ color: dshThemeColor.accent,
61659
+ fontSize: 15,
61660
+ lineHeight: 1.65
61661
+ } }, partialText)), message === void 0 || message === "" || state !== "error" ? null : (0, react.createElement)("div", {
61662
+ role: "alert",
61663
+ style: {
61664
+ color: dshThemeColor.error,
61665
+ fontSize: 13,
61666
+ lineHeight: 1.5
61667
+ }
61668
+ }, message), (0, react.createElement)("div", { style: {
61669
+ display: "flex",
61670
+ justifyContent: "space-between",
61671
+ gap: 8,
61672
+ paddingTop: 3
61673
+ } }, (0, react.createElement)("button", {
61674
+ type: "button",
61675
+ disabled: transcript.length === 0 && partialText === "",
61676
+ onClick: onClear,
61677
+ style: dshSettingsButtonStyle
61678
+ }, t("voice.dialog.clear")), (0, react.createElement)("div", { style: {
61679
+ display: "flex",
61680
+ justifyContent: "flex-end",
61681
+ gap: 8
61682
+ } }, (0, react.createElement)("button", {
61683
+ type: "button",
61684
+ disabled: pending,
61685
+ onClick: onClose,
61686
+ style: dshSettingsButtonStyle
61687
+ }, t("voice.dialog.close")), active ? (0, react.createElement)("button", {
61688
+ type: "button",
61689
+ onClick: onStop,
61690
+ style: dshSettingsButtonStyle
61691
+ }, t("voice.dialog.stop")) : (0, react.createElement)("button", {
61692
+ type: "button",
61693
+ disabled: !canStart || !realtimeAvailable,
61694
+ onClick: onStart,
61695
+ style: dshSettingsPrimaryButtonStyle
61696
+ }, t("voice.dialog.start"))))));
61697
+ }
61698
+ function statusLabel(t, state, active) {
61699
+ if (state === "loading") return t("voice.dialog.status.loading");
61700
+ if (state === "listening") return t("voice.dialog.status.listening");
61701
+ if (state === "recording") return t("voice.dialog.status.recording");
61702
+ if (state === "error") return t("voice.dialog.status.error");
61703
+ return active ? t("voice.dialog.status.active") : t("voice.dialog.status.idle");
61704
+ }
61705
+ //#endregion
61706
+ //#region src/client/features/global-voice-assistant.ts
61707
+ init_transport();
61708
+ init_locale();
61709
+ const GLOBAL_ASSISTANT_BUTTON_SIZE = 38;
61710
+ /** Client 根级语音入口;使用 shell.overlay,绝不绑定当前 session。 */
61711
+ const globalVoiceAssistantFeature = {
61712
+ descriptor: {
61713
+ name: "globalVoiceAssistant",
61714
+ version: "0.1.0",
61715
+ enabledByDefault: false,
61716
+ dependencies: [],
61717
+ runtime: "client",
61718
+ ui: {
61719
+ label: "Global smart assistant (Testing)",
61720
+ description: "Choose the workspaces that the global assistant may read and control.",
61721
+ labelKey: "feature.assistant.label",
61722
+ descriptionKey: "feature.assistant.description",
61723
+ order: 30,
61724
+ defaultOpen: false
61725
+ }
61726
+ },
61727
+ start(context) {
61728
+ const ui = context.services.uiContext;
61729
+ if (ui === void 0) return;
61730
+ const actionOwnerId = createVoiceLeaseOwnerId();
61731
+ const adapter = new ClientSherpaVoiceAdapter({
61732
+ ownerId: actionOwnerId,
61733
+ services: context.services
61734
+ });
61735
+ context.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/register-client", {
61736
+ ownerId: actionOwnerId,
61737
+ capabilities: adapter.capabilities,
61738
+ secureContext: inspectBrowserVoiceSecurity().secure
61739
+ }).catch(() => void 0);
61740
+ const disposeAdapter = registerGlobalVoiceAdapter(context.services, adapter);
61741
+ context.resources.add(() => {
61742
+ disposeAdapter();
61743
+ adapter.dispose();
61744
+ context.services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/unregister-client", { ownerId: actionOwnerId }).catch(() => void 0);
61745
+ });
61746
+ const disposer = ui.slots.inject("shell.overlay", () => ui.slots.register({
61747
+ name: "shell.overlay",
61748
+ id: "codingns4dsh-global-voice-assistant",
61749
+ order: 40,
61750
+ inject: () => ({ services: context.services })
61751
+ }, GlobalVoiceOverlay));
61752
+ context.resources.add(disposer);
61753
+ },
61754
+ settingsPanel: AssistantPanel
61755
+ };
61756
+ function GlobalVoiceOverlay({ services }) {
61757
+ const [snapshot, setSnapshot] = (0, react.useState)({ state: "disabled" });
61758
+ const [setupOpen, setSetupOpen] = (0, react.useState)(false);
61759
+ const [conversationOpen, setConversationOpen] = (0, react.useState)(false);
61760
+ const [conversationPending, setConversationPending] = (0, react.useState)(false);
61761
+ const [partialText, setPartialText] = (0, react.useState)("");
61762
+ const [transcript, setTranscript] = (0, react.useState)([]);
61763
+ const [settingsValue, setSettingsValue] = (0, react.useState)(() => services.settings.getSnapshot().value);
61764
+ const ownerIdRef = (0, react.useRef)(void 0);
61765
+ const eventSequenceRef = (0, react.useRef)(0);
61766
+ if (ownerIdRef.current === void 0) ownerIdRef.current = createVoiceLeaseOwnerId();
61767
+ const t = useCodingNsTranslator(services.locale);
61768
+ const nativeAdapter = getGlobalVoiceAdapter(services);
61769
+ const ownerId = nativeAdapter?.configuredOwnerId ?? ownerIdRef.current;
61770
+ const browserVoiceSecurity = inspectBrowserVoiceSecurity();
61771
+ const voiceSettings = settingsValue?.assistant?.voice ?? DEFAULT_ASSISTANT_VOICE_SETTINGS;
61772
+ const needsSetup = !isVoiceSettingsReady(voiceSettings);
61773
+ const adapter = nativeAdapter?.capabilities.realtime === true && browserVoiceSecurity.secure && !needsSetup && voiceSettings.provider === "sherpa-onnx" ? nativeAdapter : void 0;
61774
+ const realtimeUnavailableMessage = !browserVoiceSecurity.secure ? t("voice.secureContextRequired") : nativeAdapter?.capabilities.realtime !== true ? t("voice.dialog.unavailable") : voiceSettings.provider !== "sherpa-onnx" ? t("voice.dialog.unavailable") : void 0;
61775
+ (0, react.useEffect)(() => {
61776
+ const refresh = () => setSettingsValue(services.settings.getSnapshot().value);
61777
+ refresh();
61778
+ return services.settings.subscribe(refresh);
61779
+ }, [services.settings]);
61780
+ (0, react.useEffect)(() => {
61781
+ if (adapter !== void 0) {
61782
+ setSnapshot({
61783
+ state: "disabled",
61784
+ active: false
61785
+ });
61786
+ const unsubscribe = adapter.subscribe((event) => {
61787
+ if (event.type === "state" || event.type === "wake" || event.type === "barge-in" || event.type === "error") services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/event", {
61788
+ ownerId,
61789
+ event,
61790
+ sequence: ++eventSequenceRef.current
61791
+ }).catch(() => void 0);
61792
+ if (event.type === "partial") setPartialText(event.text);
61793
+ else if (event.type === "final" && event.text.trim() !== "") {
61794
+ setTranscript((previous) => [...previous, event.text.trim()]);
61795
+ setPartialText("");
61796
+ }
61797
+ if (event.type === "state") setSnapshot({
61798
+ state: event.state,
61799
+ active: event.state !== "disabled",
61800
+ ownerId
61801
+ });
61802
+ else if (event.type === "barge-in") setSnapshot({
61803
+ state: "interrupted",
61804
+ active: true,
61805
+ ownerId
61806
+ });
61807
+ else if (event.type === "error") {
61808
+ const message = event.code === "voice_empty_transcript" ? t("voice.emptyTranscript") : event.code === "voice_invalid_transcript" ? t("voice.invalidTranscript") : event.message;
61809
+ setSnapshot({
61810
+ state: "error",
61811
+ active: true,
61812
+ ownerId,
61813
+ message
61814
+ });
61815
+ if (!event.recoverable) adapter.stop().catch(() => void 0);
61816
+ }
61817
+ });
61818
+ const onPageHide = () => {
61819
+ adapter.stop().catch(() => void 0);
61820
+ };
61821
+ if (typeof window !== "undefined") window.addEventListener("pagehide", onPageHide);
61822
+ return () => {
61823
+ unsubscribe();
61824
+ if (typeof window !== "undefined") window.removeEventListener("pagehide", onPageHide);
61825
+ adapter.stop().catch(() => void 0);
61826
+ };
61827
+ }
61828
+ setSnapshot({
61829
+ state: "error",
61830
+ active: false,
61831
+ ownerId,
61832
+ ...realtimeUnavailableMessage === void 0 ? {} : { message: realtimeUnavailableMessage }
61833
+ });
61834
+ }, [
61835
+ adapter,
61836
+ ownerId,
61837
+ realtimeUnavailableMessage,
61838
+ services,
61839
+ t
61840
+ ]);
61841
+ (0, react.useEffect)(() => {
61842
+ if (adapter === void 0) return void 0;
61843
+ const timer = setInterval(() => {
61844
+ if (adapter.ownerId === void 0) return;
61845
+ services.rpc.call(CODINGNS_RPC_CHANNEL, "assistant/voice/heartbeat", { ownerId }).catch(() => void 0);
61846
+ }, 1e4);
61847
+ return () => clearInterval(timer);
61848
+ }, [
61849
+ adapter,
61850
+ ownerId,
61851
+ services
61852
+ ]);
61853
+ const startConversation = async () => {
61854
+ if (conversationPending || snapshot.active === true) return;
61855
+ setConversationPending(true);
61856
+ setPartialText("");
61857
+ setSnapshot((previous) => ({
61858
+ ...previous,
61859
+ state: "loading",
61860
+ active: false,
61861
+ ownerId
61862
+ }));
61863
+ try {
61864
+ if (adapter === void 0) throw new Error(realtimeUnavailableMessage ?? t("voice.dialog.unavailable"));
61865
+ await adapter.start(ownerId);
61866
+ } catch (error) {
61867
+ setSnapshot({
61868
+ state: "error",
61869
+ active: false,
61870
+ ownerId,
61871
+ message: error instanceof Error ? error.message : String(error)
61872
+ });
61873
+ } finally {
61874
+ setConversationPending(false);
61875
+ }
61876
+ };
61877
+ const stopConversation = async () => {
61878
+ if (!conversationPending && snapshot.active !== true) return;
61879
+ setConversationPending(true);
61880
+ try {
61881
+ if (adapter !== void 0) await adapter.stop();
61882
+ setPartialText("");
61883
+ } catch (error) {
61884
+ setSnapshot({
61885
+ state: "error",
61886
+ active: false,
61887
+ ownerId,
61888
+ message: error instanceof Error ? error.message : String(error)
61889
+ });
61890
+ } finally {
61891
+ setConversationPending(false);
61892
+ }
61893
+ };
61894
+ const closeConversation = () => {
61895
+ if (snapshot.active === true) {
61896
+ stopConversation().finally(() => setConversationOpen(false));
61897
+ return;
61898
+ }
61899
+ setConversationOpen(false);
61900
+ };
61901
+ const openConversation = () => {
61902
+ if (needsSetup) {
61903
+ setSetupOpen(true);
61904
+ return;
61905
+ }
61906
+ setConversationOpen(true);
61907
+ };
61908
+ (0, react.useEffect)(() => {
61909
+ const doc = typeof document === "undefined" ? void 0 : document;
61910
+ if (doc === void 0) return void 0;
61911
+ const assistantButtonSize = `${GLOBAL_ASSISTANT_BUTTON_SIZE}px`;
61912
+ let mountedNative;
61913
+ let mountedRow;
61914
+ let mountedParent;
61915
+ let mountedNextSibling;
61916
+ let mountedNativeInlineStyle;
61917
+ const mount = () => {
61918
+ const buttons = [...doc.querySelectorAll("button")];
61919
+ const native = buttons.find((candidate) => {
61920
+ return (typeof candidate.className === "string" ? candidate.className.split(/\s+/u) : []).some((name) => name.endsWith("_newSession"));
61921
+ }) ?? buttons.find((candidate) => {
61922
+ const aria = candidate.getAttribute("aria-label") ?? "";
61923
+ return /新建会话|新建对话|new session|new conversation/iu.test(aria);
61924
+ });
61925
+ if (native === void 0 || native.parentElement === null || native.parentElement.querySelector("[data-codingns-global-voice-button]") !== null) return;
61926
+ const button = doc.createElement("button");
61927
+ button.type = "button";
61928
+ button.setAttribute("data-codingns-global-voice-button", "true");
61929
+ const label = needsSetup ? t("voice.setup.open") : t("voice.globalAssistant");
61930
+ button.setAttribute("aria-label", label);
61931
+ button.setAttribute("aria-description", [
61932
+ t("voice.microphoneNotice"),
61933
+ t("voice.audioNotice"),
61934
+ snapshot.message
61935
+ ].filter(Boolean).join(" "));
61936
+ button.setAttribute("aria-haspopup", "dialog");
61937
+ button.setAttribute("aria-expanded", String(conversationOpen || setupOpen));
61938
+ button.setAttribute("data-codingns-voice-state", snapshot.state ?? "disabled");
61939
+ button.title = label;
61940
+ button.innerHTML = "<svg aria-hidden=\"true\" viewBox=\"0 0 24 24\" width=\"18\" height=\"18\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.8\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"m12 2 1.45 5.55L19 9l-5.55 1.45L12 16l-1.45-5.55L5 9l5.55-1.45L12 2Z\"/><path d=\"m5 15 .7 2.3L8 18l-2.3.7L5 21l-.7-2.3L2 18l2.3-.7L5 15Z\"/><path d=\"m19 14 .45 1.55L21 16l-1.55.45L19 18l-.45-1.55L17 16l1.55-.45L19 14Z\"/></svg>";
61941
+ button.style.cssText = `display:inline-flex;align-items:center;justify-content:center;padding:0;border:0;border-radius:8px;cursor:pointer;background:var(--ds-color-fill-tertiary,rgba(127,127,127,.12));flex:0 1 ${assistantButtonSize};min-width:${assistantButtonSize};width:${assistantButtonSize};max-width:${assistantButtonSize};height:${assistantButtonSize};aspect-ratio:1 / 1;box-sizing:border-box;white-space:nowrap;margin:0`;
61942
+ button.addEventListener("click", openConversation);
61943
+ const parent = native.parentElement;
61944
+ const row = doc.createElement("div");
61945
+ row.setAttribute("data-codingns-global-voice-row", "true");
61946
+ row.style.cssText = "display:flex;flex:0 1 auto;flex-flow:row nowrap;align-items:center;gap:4px;min-width:0;width:calc(100% - 4px);max-width:100%;box-sizing:border-box;overflow:hidden;margin:0 2px 12px";
61947
+ mountedNative = native;
61948
+ mountedRow = row;
61949
+ mountedParent = parent;
61950
+ mountedNextSibling = native.nextSibling;
61951
+ mountedNativeInlineStyle = {
61952
+ flex: native.style.flex,
61953
+ minWidth: native.style.minWidth,
61954
+ width: native.style.width,
61955
+ maxWidth: native.style.maxWidth,
61956
+ boxSizing: native.style.boxSizing,
61957
+ whiteSpace: native.style.whiteSpace,
61958
+ margin: native.style.margin
61959
+ };
61960
+ parent.insertBefore(row, native);
61961
+ row.append(native, button);
61962
+ native.style.flex = "1 1 auto";
61963
+ native.style.minWidth = "0";
61964
+ native.style.width = "auto";
61965
+ native.style.maxWidth = "100%";
61966
+ native.style.boxSizing = "border-box";
61967
+ native.style.whiteSpace = "nowrap";
61968
+ native.style.margin = "0";
61969
+ button.style.flex = `0 1 ${assistantButtonSize}`;
61970
+ button.style.width = assistantButtonSize;
61971
+ button.style.minWidth = assistantButtonSize;
61972
+ button.style.maxWidth = assistantButtonSize;
61973
+ button.style.height = assistantButtonSize;
61974
+ };
61975
+ mount();
61976
+ const observer = typeof MutationObserver === "undefined" ? void 0 : new MutationObserver(mount);
61977
+ observer?.observe(doc.body ?? doc.documentElement, {
61978
+ childList: true,
61979
+ subtree: true
61980
+ });
61981
+ return () => {
61982
+ observer?.disconnect();
61983
+ if (mountedNative !== void 0 && mountedNativeInlineStyle !== void 0) {
61984
+ mountedNative.style.flex = mountedNativeInlineStyle.flex;
61985
+ mountedNative.style.minWidth = mountedNativeInlineStyle.minWidth;
61986
+ mountedNative.style.width = mountedNativeInlineStyle.width;
61987
+ mountedNative.style.maxWidth = mountedNativeInlineStyle.maxWidth;
61988
+ mountedNative.style.boxSizing = mountedNativeInlineStyle.boxSizing;
61989
+ mountedNative.style.whiteSpace = mountedNativeInlineStyle.whiteSpace;
61990
+ mountedNative.style.margin = mountedNativeInlineStyle.margin;
61991
+ }
61992
+ if (mountedNative !== void 0 && mountedParent !== void 0 && mountedParent.isConnected) {
61993
+ if (mountedNextSibling !== null && mountedNextSibling !== void 0 && mountedNextSibling.parentNode === mountedParent) mountedParent.insertBefore(mountedNative, mountedNextSibling);
61994
+ else mountedParent.appendChild(mountedNative);
61995
+ }
61996
+ mountedRow?.remove();
61997
+ doc.querySelectorAll("[data-codingns-global-voice-button]").forEach((node) => node.remove());
61998
+ };
61999
+ }, [
62000
+ adapter,
62001
+ conversationOpen,
62002
+ needsSetup,
62003
+ openConversation,
62004
+ setSetupOpen,
62005
+ setupOpen,
62006
+ snapshot.state,
62007
+ t
62008
+ ]);
62009
+ return (0, react.createElement)("span", {
62010
+ "data-codingns-global-voice": "true",
62011
+ ...setupOpen || conversationOpen ? {} : { "aria-hidden": "true" }
62012
+ }, setupOpen ? (0, react.createElement)(VoiceInitializationDialog, {
62013
+ services,
62014
+ value: voiceSettings,
62015
+ onClose: () => setSetupOpen(false)
62016
+ }) : conversationOpen ? (0, react.createElement)(VoiceConversationDialog, {
62017
+ t,
62018
+ active: snapshot.active === true,
62019
+ pending: conversationPending,
62020
+ state: snapshot.state,
62021
+ message: snapshot.message,
62022
+ partialText,
62023
+ transcript,
62024
+ realtimeAvailable: adapter !== void 0,
62025
+ unavailableMessage: realtimeUnavailableMessage,
62026
+ onStart: () => {
62027
+ startConversation();
62028
+ },
62029
+ onStop: () => {
62030
+ stopConversation();
62031
+ },
62032
+ onClose: closeConversation,
62033
+ onClear: () => {
62034
+ setTranscript([]);
62035
+ setPartialText("");
62036
+ }
62037
+ }) : null);
62038
+ }
62039
+ function createVoiceLeaseOwnerId() {
62040
+ try {
62041
+ const randomUUID = globalThis.crypto?.randomUUID;
62042
+ if (typeof randomUUID === "function") return `codingns4dsh:global-voice:${randomUUID.call(globalThis.crypto)}`;
62043
+ } catch {}
62044
+ return `codingns4dsh:global-voice:${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
62045
+ }
62046
+ function isVoiceSettingsReady(settings) {
62047
+ if (!settings.initialized || settings.provider !== "sherpa-onnx") return false;
62048
+ return settings.asrEncoder.trim() !== "" && settings.asrDecoder.trim() !== "" && settings.asrJoiner.trim() !== "" && settings.asrTokens.trim() !== "";
62049
+ }
62050
+ //#endregion
59469
62051
  //#region src/client/peer-host-scoped-client.ts
59470
62052
  init_transport();
59471
62053
  /** 根据当前 Host 自有端点生成作用域 WebSocket 工厂;不接受目标 Host URL。 */
@@ -60060,7 +62642,8 @@ window.__ModuleLoader__.load({
60060
62642
  debugFeature,
60061
62643
  gitManagementFeature,
60062
62644
  fileManagementFeature,
60063
- peerHostFeature
62645
+ peerHostFeature,
62646
+ globalVoiceAssistantFeature
60064
62647
  ];
60065
62648
  /**
60066
62649
  * 汇总需要在设置页显示的模块,按 ui.order 升序。
@@ -60754,7 +63337,7 @@ window.__ModuleLoader__.load({
60754
63337
  Time.template = template;
60755
63338
  })(Time || (Time = {}));
60756
63339
  //#endregion
60757
- //#region node_modules/.pnpm/@deepseek-ai+cordis@4.0.4_@deepseek-ai+cordis-plugin-include@1.0.9_@deepseek-ai+cordis-plugin-loader@1.0.5/node_modules/@deepseek-ai/cordis/lib/index.js
63340
+ //#region node_modules/.pnpm/@deepseek-ai+cordis@4.0.4_@deepseek-ai+cordis-plugin-include@1.0.9_@deepseek-ai+cordis-plugin-loader@1.0.6-alpha.1/node_modules/@deepseek-ai/cordis/lib/index.js
60758
63341
  /** Ordered collection of disposable values with O(1) deletion by value. */
60759
63342
  var DisposableList = class {
60760
63343
  sn = 0;
@@ -62659,7 +65242,7 @@ window.__ModuleLoader__.load({
62659
65242
  this.initialStateConsumed = true;
62660
65243
  if (initial.info.state === "running") this.connect();
62661
65244
  else this.refresh();
62662
- } else this.refresh();
65245
+ } else if (!this.resumeFollow()) this.refresh();
62663
65246
  }
62664
65247
  let active = true;
62665
65248
  return () => {
@@ -62669,9 +65252,20 @@ window.__ModuleLoader__.load({
62669
65252
  if (this.mounted === 0 && !this.keepAlive) this.detach();
62670
65253
  };
62671
65254
  }
62672
- async refresh() {
62673
- if (this.loading !== void 0) return this.loading;
65255
+ /**
65256
+ * 重新加载终端环境与库存。
65257
+ *
65258
+ * 默认复用仍然活跃的 follow 连接:只有连接确实不存在时才向 Host 重新发起
65259
+ * environment/list。`force` 供“重新连接/刷新”这类显式动作使用,会先释放
65260
+ * 现有 follow 再重建,避免保活连接变成无法恢复的僵尸订阅。
65261
+ */
65262
+ async refresh(options) {
62674
65263
  if (this.lifetime.signal.aborted) return;
65264
+ if (this.loading !== void 0) return this.loading;
65265
+ if (options?.force === true) {
65266
+ this.clearReconnect();
65267
+ this.releaseFollow();
65268
+ } else if (this.resumeFollow()) return;
62675
65269
  this.loading = this.load().finally(() => {
62676
65270
  this.loading = void 0;
62677
65271
  });
@@ -62687,6 +65281,7 @@ window.__ModuleLoader__.load({
62687
65281
  if (this.loading !== void 0) return this.loading;
62688
65282
  this.clearReconnect();
62689
65283
  this.reconnectAttempts = 0;
65284
+ this.releaseFollow();
62690
65285
  this.patch({
62691
65286
  phase: "loading",
62692
65287
  writable: false
@@ -62700,17 +65295,17 @@ window.__ModuleLoader__.load({
62700
65295
  async loadWithRebuild() {
62701
65296
  try {
62702
65297
  const remote = resolveRemote(this.remote);
62703
- const environment = unwrap(await remote.environment(this.sessionId, this.lifetime.signal));
65298
+ const environment = unwrap(await this.request(remote.environment(this.sessionId, this.lifetime.signal)));
62704
65299
  this.patch({
62705
65300
  phase: "creating",
62706
65301
  environment
62707
65302
  });
62708
- const info = unwrap(await remote.create(this.sessionId, {
65303
+ const info = unwrap(await this.request(remote.create(this.sessionId, {
62709
65304
  id: this.id,
62710
65305
  ...this.shellPath === void 0 ? {} : { shellPath: this.shellPath },
62711
65306
  cols: Math.min(80, environment.maxCols),
62712
65307
  rows: Math.min(24, environment.maxRows)
62713
- }, this.lifetime.signal));
65308
+ }, this.lifetime.signal)));
62714
65309
  debugInfo("codingns4dsh: client terminal rebuilt", {
62715
65310
  sessionId: this.sessionId,
62716
65311
  terminalId: info.id
@@ -62722,7 +65317,7 @@ window.__ModuleLoader__.load({
62722
65317
  info,
62723
65318
  title: info.title
62724
65319
  });
62725
- if (this.mounted > 0) this.connect();
65320
+ this.finishLoad();
62726
65321
  } catch (error) {
62727
65322
  if (!this.lifetime.signal.aborted) this.fail(error);
62728
65323
  }
@@ -62747,7 +65342,7 @@ window.__ModuleLoader__.load({
62747
65342
  unwrap(await resolveRemote(this.remote).write(this.sessionId, this.id, attachmentId, data));
62748
65343
  }).catch((error) => this.fail(error));
62749
65344
  }
62750
- resize(cols, rows) {
65345
+ resize(cols, rows, options) {
62751
65346
  const state = this.store.getSnapshot();
62752
65347
  const attachmentId = this.attachmentId;
62753
65348
  if (!state.writable || attachmentId === void 0) return;
@@ -62758,7 +65353,7 @@ window.__ModuleLoader__.load({
62758
65353
  cols: nextCols,
62759
65354
  rows: nextRows
62760
65355
  };
62761
- if (sameResize(this.lastResize, request)) return;
65356
+ if (options?.force !== true && sameResize(this.lastResize, request)) return;
62762
65357
  this.lastResize = request;
62763
65358
  this.writes = this.writes.then(async () => {
62764
65359
  unwrap(await resolveRemote(this.remote).resize(this.sessionId, this.id, attachmentId, nextCols, nextRows));
@@ -62817,7 +65412,7 @@ window.__ModuleLoader__.load({
62817
65412
  });
62818
65413
  try {
62819
65414
  const remote = resolveRemote(this.remote);
62820
- const environment = unwrap(await remote.environment(this.sessionId, this.lifetime.signal));
65415
+ const environment = unwrap(await this.request(remote.environment(this.sessionId, this.lifetime.signal)));
62821
65416
  debugInfo("codingns4dsh: client terminal environment", {
62822
65417
  sessionId: this.sessionId,
62823
65418
  workspaceId: environment.workspaceId,
@@ -62837,7 +65432,7 @@ window.__ModuleLoader__.load({
62837
65432
  createWhenMissing = createWhenMissing && !binding.existing;
62838
65433
  }
62839
65434
  }
62840
- const listed = unwrap(await remote.list(this.sessionId));
65435
+ const listed = unwrap(await this.request(remote.list(this.sessionId)));
62841
65436
  debugInfo("codingns4dsh: client terminal list", {
62842
65437
  sessionId: this.sessionId,
62843
65438
  workspaceId: environment.workspaceId,
@@ -62851,12 +65446,12 @@ window.__ModuleLoader__.load({
62851
65446
  phase: "creating",
62852
65447
  environment
62853
65448
  });
62854
- info = unwrap(await remote.create(this.sessionId, {
65449
+ info = unwrap(await this.request(remote.create(this.sessionId, {
62855
65450
  id: this.id,
62856
65451
  ...this.shellPath === void 0 ? {} : { shellPath: this.shellPath },
62857
65452
  cols: Math.min(80, environment.maxCols),
62858
65453
  rows: Math.min(24, environment.maxRows)
62859
- }, this.lifetime.signal));
65454
+ }, this.lifetime.signal)));
62860
65455
  debugInfo("codingns4dsh: client terminal created", {
62861
65456
  sessionId: this.sessionId,
62862
65457
  workspaceId: environment.workspaceId,
@@ -62869,13 +65464,42 @@ window.__ModuleLoader__.load({
62869
65464
  info,
62870
65465
  title: info.title
62871
65466
  });
62872
- if (this.mounted > 0) this.connect();
65467
+ this.finishLoad();
62873
65468
  } catch (error) {
62874
65469
  if (!this.lifetime.signal.aborted) this.fail(error);
62875
65470
  }
62876
65471
  }
65472
+ /**
65473
+ * 加载阶段的 Host 请求等待上限。
65474
+ *
65475
+ * 页面被浏览器冻结(切到后台)期间发出的请求可能永远不会返回;没有上限时
65476
+ * 视图会永久停在“正在读取终端环境”,用户点重试也只会复用同一个挂起 Promise。
65477
+ */
65478
+ request(task) {
65479
+ return withLoadTimeout(task, TERMINAL_LOAD_TIMEOUT_MS, this.t("terminalView.loadTimeout"));
65480
+ }
65481
+ /**
65482
+ * 加载完成后的状态收敛。
65483
+ *
65484
+ * 可见时建立或复用 follow;不可见且没有保活连接时不能停在 loading,否则
65485
+ * 切回会话的瞬间会先渲染成“正在读取终端环境”。
65486
+ */
65487
+ finishLoad() {
65488
+ if (this.mounted > 0) {
65489
+ this.connect();
65490
+ return;
65491
+ }
65492
+ if (this.followController === void 0 && this.store.getSnapshot().phase === "loading") this.patch({
65493
+ phase: "disconnected",
65494
+ writable: false
65495
+ });
65496
+ }
62877
65497
  connect() {
62878
- if (this.followController !== void 0 || this.lifetime.signal.aborted || this.mounted === 0) return;
65498
+ if (this.followController !== void 0) {
65499
+ this.resumeFollow();
65500
+ return;
65501
+ }
65502
+ if (this.lifetime.signal.aborted || this.mounted === 0) return;
62879
65503
  this.clearReconnect();
62880
65504
  const controller = new AbortController();
62881
65505
  this.followController = controller;
@@ -62887,6 +65511,41 @@ window.__ModuleLoader__.load({
62887
65511
  });
62888
65512
  this.consume(controller.signal);
62889
65513
  }
65514
+ /**
65515
+ * 复用仍然活跃的 follow 连接,并恢复可见状态。
65516
+ *
65517
+ * 聚合视图在后台保活期间连接不会释放,重新可见只是显示层切换,不需要重新
65518
+ * 读取环境或库存;把它降级成 loading 会让终端变成只读,且只能等下一次
65519
+ * 输出帧才能恢复。
65520
+ *
65521
+ * @returns 是否复用了已有连接;false 表示必须重新加载。
65522
+ */
65523
+ resumeFollow() {
65524
+ const controller = this.followController;
65525
+ if (controller === void 0 || controller.signal.aborted) return false;
65526
+ const state = this.store.getSnapshot();
65527
+ if (state.phase === "closed" || state.phase === "failed") return false;
65528
+ this.clearReconnect();
65529
+ if (state.render !== void 0) {
65530
+ if (state.phase !== "connected" || !state.writable) this.patch({
65531
+ phase: "connected",
65532
+ writable: true
65533
+ });
65534
+ } else if (state.phase !== "connecting") this.patch({
65535
+ phase: "connecting",
65536
+ writable: false
65537
+ });
65538
+ return true;
65539
+ }
65540
+ /** 只释放 follow 连接与附件,不改变对外状态;detach、重建和强制刷新共用。 */
65541
+ releaseFollow() {
65542
+ this.pendingRender?.resolve();
65543
+ this.pendingRender = void 0;
65544
+ this.followController?.abort(/* @__PURE__ */ new Error("终端视图重新连接"));
65545
+ this.followController = void 0;
65546
+ this.attachmentId = void 0;
65547
+ this.lastResize = void 0;
65548
+ }
62890
65549
  async consume(signal) {
62891
65550
  try {
62892
65551
  const attachmentId = this.attachmentId;
@@ -62934,6 +65593,7 @@ window.__ModuleLoader__.load({
62934
65593
  revision: this.revision,
62935
65594
  resolve: resolveWaiting
62936
65595
  };
65596
+ const recoveredControl = this.store.getSnapshot().error?.includes("当前 attach 没有终端输入控制权") === true;
62937
65597
  this.patch({
62938
65598
  phase: "connected",
62939
65599
  writable: true,
@@ -62942,16 +65602,12 @@ window.__ModuleLoader__.load({
62942
65602
  frame
62943
65603
  }
62944
65604
  });
65605
+ if (recoveredControl) this.clearError();
62945
65606
  await waiting;
62946
65607
  }
62947
65608
  detach() {
62948
65609
  this.clearReconnect();
62949
- this.pendingRender?.resolve();
62950
- this.pendingRender = void 0;
62951
- this.followController?.abort(/* @__PURE__ */ new Error("终端视图已 detach"));
62952
- this.followController = void 0;
62953
- this.attachmentId = void 0;
62954
- this.lastResize = void 0;
65610
+ this.releaseFollow();
62955
65611
  if (!this.lifetime.signal.aborted && this.store.getSnapshot().phase !== "closed") {
62956
65612
  const { render: _render, ...state } = this.store.getSnapshot();
62957
65613
  this.store.set({
@@ -63017,6 +65673,10 @@ window.__ModuleLoader__.load({
63017
65673
  ...patch
63018
65674
  });
63019
65675
  }
65676
+ clearError() {
65677
+ const { error: _error, ...state } = this.store.getSnapshot();
65678
+ this.store.set(state);
65679
+ }
63020
65680
  fail(error) {
63021
65681
  this.patch({
63022
65682
  phase: "failed",
@@ -63360,6 +66020,37 @@ window.__ModuleLoader__.load({
63360
66020
  }
63361
66021
  }
63362
66022
  };
66023
+ /** 加载阶段单次 Host 请求的等待上限;超过后按失败处理,用户可以显式重试。 */
66024
+ const TERMINAL_LOAD_TIMEOUT_MS = 2e4;
66025
+ /**
66026
+ * 给加载阶段的请求加等待上限。
66027
+ *
66028
+ * 页面被浏览器冻结(切到后台)期间发出的请求可能永远不会返回;没有上限时
66029
+ * 视图会永久停在“正在读取终端环境”,重试也只会复用同一个挂起的 Promise。
66030
+ * 底层请求不保证可取消,因此这里只结束等待,并先给原请求挂上拒绝处理器,
66031
+ * 避免它稍后失败时产生 unhandled rejection。
66032
+ */
66033
+ async function withLoadTimeout(task, timeoutMs, message) {
66034
+ const guarded = task.then((value) => ({
66035
+ state: "value",
66036
+ value
66037
+ }), (error) => ({
66038
+ state: "error",
66039
+ error
66040
+ }));
66041
+ let timer;
66042
+ const expired = new Promise((resolve) => {
66043
+ timer = setTimeout(() => resolve({ state: "timeout" }), timeoutMs);
66044
+ });
66045
+ try {
66046
+ const outcome = await Promise.race([guarded, expired]);
66047
+ if (outcome.state === "value") return outcome.value;
66048
+ if (outcome.state === "error") throw outcome.error;
66049
+ throw new Error(message);
66050
+ } finally {
66051
+ if (timer !== void 0) clearTimeout(timer);
66052
+ }
66053
+ }
63363
66054
  function unwrap(result) {
63364
66055
  if (typeof result !== "object" || result === null || !("ok" in result)) throw new Error("终端服务返回了无效响应");
63365
66056
  if (result.ok) return result.value;
@@ -63670,8 +66361,16 @@ window.__ModuleLoader__.load({
63670
66361
  titleInput: "codingns4dsh-terminal-title-input",
63671
66362
  root: "codingns4dsh-terminal-root",
63672
66363
  aggregateRoot: "codingns4dsh-terminal-aggregate-root",
66364
+ listDock: "codingns4dsh-terminal-list-dock",
63673
66365
  list: "codingns4dsh-terminal-list",
66366
+ listTabs: "codingns4dsh-terminal-list-tabs",
66367
+ listActions: "codingns4dsh-terminal-list-actions",
63674
66368
  newButton: "codingns4dsh-terminal-new-button",
66369
+ refreshButton: "codingns4dsh-terminal-refresh-button",
66370
+ toolsButton: "codingns4dsh-terminal-tools-button",
66371
+ toolsPanel: "codingns4dsh-terminal-tools-panel",
66372
+ toolAction: "codingns4dsh-terminal-tool-action",
66373
+ toolModifierActive: "codingns4dsh-terminal-tool-modifier-active",
63675
66374
  listRow: "codingns4dsh-terminal-list-row",
63676
66375
  listRowSelected: "codingns4dsh-terminal-list-row-selected",
63677
66376
  listSelect: "codingns4dsh-terminal-list-select",
@@ -63709,8 +66408,20 @@ window.__ModuleLoader__.load({
63709
66408
  .${terminalClass.titleInput}{width:120px;min-width:48px;max-width:100%;color:inherit;font:inherit;background:var(--dsw-alias-bg-l1);border:.5px solid var(--dsw-alias-border-l3);border-radius:4px;outline:none;padding:0 4px}
63710
66409
  .${terminalClass.root}{box-sizing:border-box;width:0;height:100%;min-width:0;min-height:0;color:var(--dsw-alias-label-primary);flex-direction:column;flex:1 1 auto;padding-top:8px;font-size:13px;display:flex;overflow:hidden}
63711
66410
  .${terminalClass.aggregateRoot}{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;display:flex;flex-direction:column;flex:1 1 auto;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);overflow:hidden}
63712
- .${terminalClass.list}{box-sizing:border-box;width:100%;height:40px;min-width:0;min-height:40px;display:flex;align-items:center;gap:2px;padding:3px 8px;background:var(--dsw-alias-bg-layer-1);border-bottom:.5px solid var(--dsw-alias-border-l3);overflow-x:auto;overflow-y:hidden;flex:none}
66411
+ .${terminalClass.listDock}{box-sizing:border-box;width:100%;min-width:0;flex:none;background:var(--dsw-alias-bg-layer-1);border-bottom:.5px solid var(--dsw-alias-border-l3)}
66412
+ .${terminalClass.list}{box-sizing:border-box;width:100%;height:40px;min-width:0;min-height:40px;display:flex;align-items:center;gap:2px;padding:3px 8px;background:inherit;overflow-x:auto;overflow-y:hidden;flex:none}
66413
+ .${terminalClass.listTabs}{box-sizing:border-box;min-width:0;height:34px;display:flex;align-items:center;gap:2px;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;scrollbar-width:none}
66414
+ .${terminalClass.listTabs}::-webkit-scrollbar{display:none}
66415
+ .${terminalClass.listActions}{box-sizing:border-box;display:flex;align-items:center;gap:2px;flex:none;margin-left:4px;padding-left:4px;border-left:.5px solid var(--dsw-alias-border-l3);background:inherit}
63713
66416
  .${terminalClass.newButton}{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:none;align-self:center;width:30px;min-width:30px;height:30px;min-height:30px;max-height:30px;margin:0 0 0 4px;padding:0;white-space:nowrap}
66417
+ .${terminalClass.refreshButton},.${terminalClass.toolsButton}{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:none;width:30px;min-width:30px;height:30px;min-height:30px;max-height:30px;margin:0;padding:0;color:var(--dsw-alias-label-secondary)}
66418
+ .${terminalClass.refreshButton}:hover, .${terminalClass.toolsButton}:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
66419
+ .${terminalClass.toolsButton}[aria-expanded="true"]{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
66420
+ .${terminalClass.toolsPanel}{box-sizing:border-box;width:100%;min-width:0;display:flex;align-items:center;gap:4px;padding:4px 8px 6px;border-top:.5px solid var(--dsw-alias-border-l3);overflow-x:auto;overflow-y:hidden;scrollbar-width:thin;overscroll-behavior:contain}
66421
+ .${terminalClass.toolsPanel}::-webkit-scrollbar{height:5px}
66422
+ .${terminalClass.toolsPanel}::-webkit-scrollbar-thumb{background:var(--dsw-alias-label-tertiary,#777);border-radius:3px}
66423
+ .${terminalClass.toolAction}{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;flex:none;width:38px;min-width:38px;height:28px;min-height:28px;padding:0!important;border-radius:var(--dsw-radius-sm,6px)}
66424
+ .${terminalClass.toolAction}[aria-pressed="true"],.${terminalClass.toolModifierActive}{background:var(--dsw-alias-button-ghost-active-fill,var(--dsw-alias-interactive-bg-active));box-shadow:inset 0 0 0 .5px var(--dsw-alias-button-ghost-active-border,var(--dsw-alias-border-l3))}
63714
66425
  .${terminalClass.listRow}{box-sizing:border-box;display:flex;align-items:center;height:28px;min-width:110px;max-width:220px;border-radius:var(--dsw-radius-sm,6px);background:transparent;flex:none}
63715
66426
  .${terminalClass.listRowSelected}{background:var(--dsw-alias-interactive-bg-hover);box-shadow:var(--dsw-elevation-soft,0 1px 2px rgba(0,0,0,.08))}
63716
66427
  .${terminalClass.listSelect}{box-sizing:border-box;min-width:0;height:28px;min-height:28px;flex:1;text-align:left;color:inherit;border:0;background:transparent;cursor:pointer;padding:0 8px;display:flex;align-items:center;gap:5px;font:inherit;line-height:20px}
@@ -63721,12 +66432,13 @@ window.__ModuleLoader__.load({
63721
66432
  .${terminalClass.listClose}:hover{color:var(--dsw-alias-label-primary);background:var(--dsw-alias-interactive-bg-hover)}
63722
66433
  .${terminalClass.content}{box-sizing:border-box;width:100%;min-width:0;min-height:0;display:flex;flex:1 1 auto;overflow:hidden}
63723
66434
  .${terminalClass.empty}{display:flex;flex:1;min-height:0;align-items:center;justify-content:center;flex-direction:column;gap:10px;color:var(--dsw-alias-label-secondary);text-align:center;padding:24px}
63724
- .${terminalClass.screen}{box-sizing:border-box;width:100%;min-width:0;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);flex:1 1 auto;padding:8px;overflow:hidden}
66435
+ .${terminalClass.screen}{box-sizing:border-box;width:100%;min-width:0;min-height:0;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-base);flex:1 1 auto;padding:0;overflow:hidden}
63725
66436
  .${terminalClass.status}{background:var(--dsw-alias-bg-l2);flex-wrap:wrap;align-items:center;gap:8px;padding:5px 12px;display:flex}
63726
66437
  .${terminalClass.error}{overflow-wrap:anywhere;color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-l2);margin:0;padding:10px 12px}
63727
66438
  .${terminalClass.cleanupStack}{pointer-events:auto;gap:8px;max-width:min(440px,calc(100vw - 40px));display:grid;position:fixed;bottom:20px;right:20px}
63728
66439
  .${terminalClass.cleanupNotice}{border:.5px solid var(--dsw-alias-border-l3);color:var(--dsw-alias-label-primary);background:var(--dsw-alias-bg-l1);overflow-wrap:anywhere;border-radius:8px;align-items:center;gap:12px;padding:12px 16px;font-size:13px;display:flex;box-shadow:0 4px 20px #0002}
63729
66440
  .${terminalClass.cleanupNotice} button{border:.5px solid var(--dsw-alias-border-l3);color:inherit;cursor:pointer;background:transparent;border-radius:4px;flex-shrink:0;padding:4px 8px}
66441
+ @media (max-width:640px){.${terminalClass.list}{padding-left:6px;padding-right:6px}.${terminalClass.listActions}{margin-left:2px;padding-left:2px}.${terminalClass.toolsPanel}{padding-left:6px;padding-right:6px}}
63730
66442
  `;
63731
66443
  //#endregion
63732
66444
  //#region node_modules/.pnpm/@xterm+addon-fit@0.11.0/node_modules/@xterm/addon-fit/lib/addon-fit.js
@@ -76779,6 +79491,12 @@ window.__ModuleLoader__.load({
76779
79491
  const TERMINAL_TOUCH_MOMENTUM_MAX_DURATION_MS = 3600;
76780
79492
  const TERMINAL_TOUCH_MOMENTUM_MAX_IDLE_FRAMES = 3;
76781
79493
  const TERMINAL_TOUCH_MOMENTUM_RELEASE_IDLE_MS = 100;
79494
+ /** 视图夺回终端尺寸的最小间隔,避免可见性抖动时反复重发。 */
79495
+ const TERMINAL_SIZE_CLAIM_INTERVAL_MS = 1e3;
79496
+ /** 终端文本与可视容器边缘之间至少保留 5px 的安全距离。 */
79497
+ const TERMINAL_CONTENT_EDGE_GAP = 5;
79498
+ /** 终端滚动条停止滚动后的自动隐藏延迟。 */
79499
+ const TERMINAL_SCROLLBAR_HIDE_DELAY_MS = 3e3;
76782
79500
  /** 使用与 DSH 内置终端相同的布局、状态条和 xterm 默认参数。 */
76783
79501
  function CodingNsXtermView({ view, settings, themeRevision, onNewTerminal, active = true, visible = true, t: injectedTranslator }) {
76784
79502
  const t = injectedTranslator ?? resolveCodingNsTranslator();
@@ -76787,6 +79505,8 @@ window.__ModuleLoader__.load({
76787
79505
  const fitRef = (0, react.useRef)(null);
76788
79506
  const scheduleReflowRef = (0, react.useRef)(null);
76789
79507
  const lastRevision = (0, react.useRef)(0);
79508
+ const sizeClaimAtRef = (0, react.useRef)(0);
79509
+ const previousActiveRef = (0, react.useRef)(active);
76790
79510
  const state = (0, react.useSyncExternalStore)(view.state.subscribe.bind(view.state), view.state.getSnapshot.bind(view.state));
76791
79511
  const appearance = (0, react.useSyncExternalStore)(settings.subscribe.bind(settings), settings.getSnapshot.bind(settings)).value?.terminalEnhancement.appearance ?? DEFAULT_TERMINAL_ENHANCEMENT_SETTINGS.appearance;
76792
79512
  const hasTerminal = state.info !== void 0;
@@ -76944,11 +79664,34 @@ window.__ModuleLoader__.load({
76944
79664
  };
76945
79665
  const xtermRoot = terminal.element;
76946
79666
  if (xtermRoot === void 0) return;
76947
- const scrollTarget = xtermRoot?.querySelector(".xterm-viewport") ?? xtermRoot ?? container;
79667
+ const scrollTarget = xtermRoot?.querySelector(".xterm-scrollable-element") ?? xtermRoot ?? container;
76948
79668
  const interactionTarget = xtermRoot ?? container;
76949
- const syncScrollbar = () => {
79669
+ let scrollbarHideTimer;
79670
+ let lastViewportY = terminal.buffer.active.viewportY;
79671
+ const hideScrollbar = () => {
79672
+ if (scrollbarHideTimer !== void 0) {
79673
+ window.clearTimeout(scrollbarHideTimer);
79674
+ scrollbarHideTimer = void 0;
79675
+ }
79676
+ xtermRoot.dataset.codingnsScrollbar = "hidden";
79677
+ };
79678
+ const revealScrollbar = () => {
79679
+ if (terminal.buffer.active.baseY <= 0) {
79680
+ hideScrollbar();
79681
+ return;
79682
+ }
79683
+ xtermRoot.dataset.codingnsScrollbar = "visible";
79684
+ if (scrollbarHideTimer !== void 0) window.clearTimeout(scrollbarHideTimer);
79685
+ scrollbarHideTimer = window.setTimeout(() => {
79686
+ scrollbarHideTimer = void 0;
79687
+ xtermRoot.dataset.codingnsScrollbar = "hidden";
79688
+ }, TERMINAL_SCROLLBAR_HIDE_DELAY_MS);
79689
+ };
79690
+ const syncScrollbarState = () => {
76950
79691
  const hasScrollback = terminal.buffer.active.baseY > 0;
76951
79692
  xtermRoot.dataset.codingnsScrollback = hasScrollback ? "true" : "false";
79693
+ if (!hasScrollback) hideScrollbar();
79694
+ else if (xtermRoot.dataset.codingnsScrollbar === void 0) xtermRoot.dataset.codingnsScrollbar = "hidden";
76952
79695
  debugInfo("codingns4dsh: client terminal scrollbar state", {
76953
79696
  terminalId: view.id,
76954
79697
  hasScrollback,
@@ -76956,7 +79699,7 @@ window.__ModuleLoader__.load({
76956
79699
  viewportY: terminal.buffer.active.viewportY
76957
79700
  });
76958
79701
  };
76959
- syncScrollbar();
79702
+ syncScrollbarState();
76960
79703
  scrollTarget.style.touchAction = "pan-y";
76961
79704
  scrollTarget.style.overscrollBehavior = "contain";
76962
79705
  if ("webkitOverflowScrolling" in scrollTarget.style) scrollTarget.style.webkitOverflowScrolling = "touch";
@@ -76965,7 +79708,10 @@ window.__ModuleLoader__.load({
76965
79708
  interactionTarget.addEventListener("touchend", touchEnd, { passive: true });
76966
79709
  interactionTarget.addEventListener("touchcancel", touchCancel, { passive: true });
76967
79710
  const scroll = terminal.onScroll((viewportY) => {
76968
- syncScrollbar();
79711
+ const didScroll = viewportY !== lastViewportY;
79712
+ lastViewportY = viewportY;
79713
+ syncScrollbarState();
79714
+ if (didScroll) revealScrollbar();
76969
79715
  debugInfo("codingns4dsh: client terminal viewport scroll", {
76970
79716
  terminalId: view.id,
76971
79717
  viewportY,
@@ -76977,9 +79723,10 @@ window.__ModuleLoader__.load({
76977
79723
  if (!preserveHostTitle) view.rename(value);
76978
79724
  });
76979
79725
  const measure = () => {
79726
+ syncTerminalViewport(host);
76980
79727
  if (host.clientWidth === 0 || host.clientHeight === 0) return;
76981
79728
  fitTerminal(terminal, fit, view);
76982
- syncScrollbar();
79729
+ syncScrollbarState();
76983
79730
  };
76984
79731
  let measureFrame;
76985
79732
  const scheduleMeasure = () => {
@@ -77009,17 +79756,20 @@ window.__ModuleLoader__.load({
77009
79756
  resize.observe(host);
77010
79757
  resize.observe(container);
77011
79758
  const visualViewport = window.visualViewport;
77012
- const handleViewportResize = () => schedulePostAttachReflow();
77013
- visualViewport?.addEventListener("resize", handleViewportResize);
77014
- window.addEventListener("resize", handleViewportResize);
79759
+ const handleViewportChange = () => schedulePostAttachReflow();
79760
+ visualViewport?.addEventListener("resize", handleViewportChange);
79761
+ visualViewport?.addEventListener("scroll", handleViewportChange);
79762
+ window.addEventListener("resize", handleViewportChange);
77015
79763
  schedulePostAttachReflow();
77016
79764
  return () => {
77017
79765
  resize.disconnect();
77018
- visualViewport?.removeEventListener("resize", handleViewportResize);
77019
- window.removeEventListener("resize", handleViewportResize);
79766
+ visualViewport?.removeEventListener("resize", handleViewportChange);
79767
+ visualViewport?.removeEventListener("scroll", handleViewportChange);
79768
+ window.removeEventListener("resize", handleViewportChange);
77020
79769
  if (measureFrame !== void 0) window.cancelAnimationFrame(measureFrame);
77021
79770
  if (reflowFrame !== void 0) window.cancelAnimationFrame(reflowFrame);
77022
79771
  if (reflowTimer !== void 0) window.clearTimeout(reflowTimer);
79772
+ if (scrollbarHideTimer !== void 0) window.clearTimeout(scrollbarHideTimer);
77023
79773
  scheduleReflowRef.current = null;
77024
79774
  input.dispose();
77025
79775
  scroll.dispose();
@@ -77032,17 +79782,24 @@ window.__ModuleLoader__.load({
77032
79782
  terminal.dispose();
77033
79783
  terminalRef.current = null;
77034
79784
  fitRef.current = null;
79785
+ host.style.height = "100%";
77035
79786
  root.replaceChildren();
77036
79787
  };
77037
79788
  }, [hasTerminal, view]);
77038
79789
  (0, react.useEffect)(() => {
79790
+ const becameActive = active && !previousActiveRef.current;
79791
+ previousActiveRef.current = active;
77039
79792
  const terminal = terminalRef.current;
77040
79793
  const host = hostRef.current;
77041
79794
  if (terminal === null || host === null) return;
77042
79795
  applyAppearance(terminal, appearance, host, state.environment?.scrollback ?? 1e3);
77043
79796
  terminal.options.disableStdin = !state.writable;
79797
+ if (active) syncTerminalViewport(host);
77044
79798
  if (state.writable && host.clientWidth > 0 && host.clientHeight > 0) {
77045
- fitTerminal(terminal, fitRef.current, view);
79799
+ const now = Date.now();
79800
+ const claim = becameActive && now - sizeClaimAtRef.current >= TERMINAL_SIZE_CLAIM_INTERVAL_MS;
79801
+ if (claim) sizeClaimAtRef.current = now;
79802
+ fitTerminal(terminal, fitRef.current, view, claim ? { force: true } : void 0);
77046
79803
  terminal.focus();
77047
79804
  } else if (state.info !== void 0) terminal.resize(state.info.cols, state.info.rows);
77048
79805
  }, [
@@ -77055,6 +79812,26 @@ window.__ModuleLoader__.load({
77055
79812
  themeRevision,
77056
79813
  view
77057
79814
  ]);
79815
+ (0, react.useEffect)(() => {
79816
+ if (!active || !hasTerminal) return;
79817
+ const onVisibilityChange = () => {
79818
+ if (document.visibilityState !== "visible") return;
79819
+ const terminal = terminalRef.current;
79820
+ const host = hostRef.current;
79821
+ if (terminal === null || host === null) return;
79822
+ if (host.clientWidth === 0 || host.clientHeight === 0) return;
79823
+ const now = Date.now();
79824
+ if (now - sizeClaimAtRef.current < TERMINAL_SIZE_CLAIM_INTERVAL_MS) return;
79825
+ sizeClaimAtRef.current = now;
79826
+ fitTerminal(terminal, fitRef.current, view, { force: true });
79827
+ };
79828
+ document.addEventListener("visibilitychange", onVisibilityChange);
79829
+ return () => document.removeEventListener("visibilitychange", onVisibilityChange);
79830
+ }, [
79831
+ active,
79832
+ hasTerminal,
79833
+ view
79834
+ ]);
77058
79835
  (0, react.useEffect)(() => {
77059
79836
  const terminal = terminalRef.current;
77060
79837
  const host = hostRef.current;
@@ -77072,8 +79849,12 @@ window.__ModuleLoader__.load({
77072
79849
  fitTerminal(terminal, fitRef.current, view);
77073
79850
  scheduleReflowRef.current?.();
77074
79851
  }
77075
- if (terminal.element !== void 0) terminal.element.dataset.codingnsScrollback = terminal.buffer.active.baseY > 0 ? "true" : "false";
77076
- const viewport = terminal.element?.querySelector(".xterm-viewport");
79852
+ if (terminal.element !== void 0) {
79853
+ const hasScrollback = terminal.buffer.active.baseY > 0;
79854
+ terminal.element.dataset.codingnsScrollback = hasScrollback ? "true" : "false";
79855
+ if (!hasScrollback) terminal.element.dataset.codingnsScrollbar = "hidden";
79856
+ }
79857
+ const viewport = terminal.element?.querySelector(".xterm-scrollable-element");
77077
79858
  debugInfo("codingns4dsh: client terminal frame rendered", {
77078
79859
  terminalId: view.id,
77079
79860
  frameType: render.frame.type,
@@ -77121,7 +79902,7 @@ window.__ModuleLoader__.load({
77121
79902
  variant: "outline",
77122
79903
  size: "sm",
77123
79904
  onClick: () => {
77124
- view.refresh();
79905
+ view.refresh({ force: true });
77125
79906
  }
77126
79907
  }, state.phase === "disconnected" ? t("terminalView.reconnect") : t("terminal.retry")) : null, lost ? (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
77127
79908
  variant: "outline",
@@ -77189,31 +79970,80 @@ window.__ModuleLoader__.load({
77189
79970
  selectionInactiveBackground: foreground
77190
79971
  };
77191
79972
  }
77192
- function fitTerminal(terminal, fit, view) {
79973
+ function fitTerminal(terminal, fit, view, options) {
77193
79974
  if (fit === null) return;
77194
79975
  const dimensions = resolveTerminalDimensions(terminal, fit);
77195
79976
  if (dimensions === void 0) return;
77196
79977
  if (terminal.cols !== dimensions.cols || terminal.rows !== dimensions.rows) terminal.resize(dimensions.cols, dimensions.rows);
77197
- view.resize(terminal.cols, terminal.rows);
79978
+ view.resize(terminal.cols, terminal.rows, options);
79979
+ }
79980
+ /**
79981
+ * 让终端宿主节点的底边停在当前可视视口之内。
79982
+ *
79983
+ * 移动端软键盘出现时,布局视口通常仍保持原高度,只有 `visualViewport.height`
79984
+ * 变小。只调用 FitAddon 不够,因为它读取到的父节点高度没有变化;这里按屏幕
79985
+ * 实际可见底边收缩宿主,键盘收起后再恢复 `100%`。
79986
+ */
79987
+ function syncTerminalViewport(host) {
79988
+ const screen = host.parentElement;
79989
+ if (screen === null) return;
79990
+ const hostRect = host.getBoundingClientRect();
79991
+ const screenRect = screen.getBoundingClientRect();
79992
+ if (screenRect.width <= 0 || screenRect.height <= 0 || hostRect.width <= 0) return;
79993
+ const screenStyle = getComputedStyle(screen);
79994
+ const contentBottom = screenRect.bottom - parseCssPixels(screenStyle.paddingBottom) - parseCssPixels(screenStyle.borderBottomWidth);
79995
+ const viewportHeight = window.visualViewport?.height ?? window.innerHeight;
79996
+ if (!Number.isFinite(contentBottom) || !Number.isFinite(viewportHeight) || viewportHeight <= 0) return;
79997
+ const visibleBottom = Math.min(contentBottom, viewportHeight);
79998
+ const normalHeight = Math.max(1, Math.floor(contentBottom - hostRect.top));
79999
+ if (visibleBottom >= contentBottom - 1) {
80000
+ if (host.style.height !== "100%") host.style.height = "100%";
80001
+ return;
80002
+ }
80003
+ const visibleHeight = Math.max(1, Math.floor(visibleBottom - hostRect.top - 1));
80004
+ const nextHeight = `${Math.min(normalHeight, visibleHeight)}px`;
80005
+ if (host.style.height !== nextHeight) host.style.height = nextHeight;
77198
80006
  }
77199
80007
  function resolveTerminalDimensions(terminal, fit) {
77200
80008
  const fallback = fit.proposeDimensions();
77201
80009
  const root = terminal.element;
77202
80010
  if (root === void 0 || root.clientWidth <= 0 || root.clientHeight <= 0) return fallback;
77203
- const measure = root.querySelector(".xterm-char-measure-element");
77204
- const row = root.querySelector(".xterm-rows > div");
77205
- const measureWidth = measure?.getBoundingClientRect().width ?? 0;
77206
- const cellWidth = measureWidth > 0 ? measureWidth / 32 : 0;
77207
- const cellHeight = row?.getBoundingClientRect().height ?? 0;
80011
+ const cell = resolveRendererCell(terminal);
80012
+ if (cell === void 0) return fallback === void 0 ? void 0 : {
80013
+ ...fallback,
80014
+ cols: Math.max(2, fallback.cols - 1)
80015
+ };
80016
+ const HOST_PADDING_LEFT = 8;
80017
+ const HOST_PADDING_RIGHT = 8;
77208
80018
  const computed = getComputedStyle(root);
77209
- const paddingX = parseCssPixels(computed.paddingLeft) + parseCssPixels(computed.paddingRight);
77210
80019
  const paddingY = parseCssPixels(computed.paddingTop) + parseCssPixels(computed.paddingBottom);
77211
- const width = root.clientWidth - paddingX - (terminal.options.scrollback === 0 ? 0 : 14);
80020
+ const rootRect = root.getBoundingClientRect();
80021
+ const scrollable = root.querySelector(".xterm-scrollable-element");
80022
+ const scrollbarRect = (terminal.options.scrollback === 0 ? void 0 : scrollable?.querySelector(".scrollbar.vertical"))?.getBoundingClientRect();
80023
+ scrollable?.getBoundingClientRect();
80024
+ const scrollbarInset = scrollbarRect !== void 0 && scrollbarRect.width > 0 ? Math.max(0, rootRect.right - scrollbarRect.left) : 0;
80025
+ const safeEdgeGap = Math.max(0, TERMINAL_CONTENT_EDGE_GAP - scrollbarInset);
80026
+ const width = rootRect.width - HOST_PADDING_LEFT - HOST_PADDING_RIGHT - scrollbarInset - safeEdgeGap;
77212
80027
  const height = root.clientHeight - paddingY;
77213
- if (cellWidth <= 0 || cellHeight <= 0 || width <= 0 || height <= 0) return fallback;
80028
+ if (width <= 0 || height <= 0) return fallback;
80029
+ return {
80030
+ cols: Math.max(2, Math.floor(width / cell.width)),
80031
+ rows: Math.max(1, Math.floor(height / cell.height))
80032
+ };
80033
+ }
80034
+ /**
80035
+ * 读取 xterm 渲染服务的单元格尺寸。`.xterm-char-measure-element` 会被 DOM renderer
80036
+ * 的宽度缓存复用,其文本内容会随最近一次测量的字符变化(例如中文全角字符)。
80037
+ * 用它除以 32 推算列宽会把列数按该字符宽度计算,宽字符时列数近乎减半,表现为
80038
+ * 文本提前换行、右侧留出大片空白。这里改用官方 FitAddon 依赖的 renderService
80039
+ * cell 尺寸(CharSizeService 固定按 32 个 'W' 测量),列宽不再受终端内容影响。
80040
+ */
80041
+ function resolveRendererCell(terminal) {
80042
+ const cell = (terminal._core?._renderService?.dimensions)?.css?.cell;
80043
+ if (cell === void 0 || !(cell.width > 0) || !(cell.height > 0)) return void 0;
77214
80044
  return {
77215
- cols: Math.max(2, Math.floor(width / cellWidth)),
77216
- rows: Math.max(1, Math.floor(height / cellHeight))
80045
+ width: cell.width,
80046
+ height: cell.height
77217
80047
  };
77218
80048
  }
77219
80049
  function parseCssPixels(value) {
@@ -77239,28 +80069,37 @@ window.__ModuleLoader__.load({
77239
80069
  return normalized === "" || normalized === "rgba(0, 0, 0, 0)" ? fallback : normalized;
77240
80070
  }
77241
80071
  const terminalHostStyle = {
80072
+ boxSizing: "border-box",
77242
80073
  width: "100%",
77243
80074
  height: "100%",
77244
80075
  minWidth: 0,
77245
80076
  minHeight: 0,
80077
+ paddingLeft: 8,
80078
+ paddingRight: 8,
77246
80079
  color: "var(--dsw-alias-label-primary)",
77247
80080
  background: "var(--dsw-alias-bg-base)"
77248
80081
  };
77249
80082
  const shadowCss = `${_codingns4dsh_xterm_css_default}
77250
80083
  :host{display:block;width:100%;height:100%;min-width:0;min-height:0;color:inherit;background:inherit}
77251
- .codingns-xterm{width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
77252
- .xterm{width:100%;height:100%;min-width:0;min-height:0}
77253
- .xterm-scrollable-element{min-width:0;max-width:100%}
77254
- .xterm-viewport{background:var(--dsw-alias-bg-base);touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;overflow-y:scroll;scrollbar-gutter:stable;scrollbar-width:auto;scrollbar-color:var(--dsw-alias-label-tertiary,#777) transparent}
77255
- .xterm-viewport::-webkit-scrollbar{width:10px}
77256
- .xterm-viewport::-webkit-scrollbar-track{background:transparent}
77257
- .xterm-viewport::-webkit-scrollbar-thumb{background:var(--dsw-alias-label-tertiary,#777);border-radius:5px}
77258
- .xterm-viewport::-webkit-scrollbar-thumb:hover{background:var(--dsw-alias-label-secondary,#aaa)}
77259
- .xterm .xterm-scrollable-element>.scrollbar.vertical{width:14px!important;pointer-events:auto!important}
77260
- .xterm[data-codingns-scrollback="false"] .xterm-scrollable-element>.scrollbar.vertical{opacity:0!important;pointer-events:none!important}
77261
- .xterm[data-codingns-scrollback="true"] .xterm-scrollable-element>.scrollbar.vertical{opacity:1!important;pointer-events:auto!important;background:rgba(128,128,128,.22)}
77262
- .xterm[data-codingns-scrollback="true"] .xterm-scrollable-element>.scrollbar.vertical>.slider{background:var(--dsw-alias-label-secondary,#aaa)!important;border-radius:7px;min-height:24px}
80084
+ .codingns-xterm{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0;overflow:hidden;touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch}
80085
+ .xterm{box-sizing:border-box;width:100%;height:100%;min-width:0;min-height:0}
80086
+ .xterm-scrollable-element{box-sizing:border-box;width:100%;min-width:0;max-width:100%}
80087
+ /* xterm 6 已由 xterm-scrollable-element 接管滚动;隐藏兼容 viewport,避免产生第二个滚动条槽位。 */
80088
+ .xterm-viewport{background:var(--dsw-alias-bg-base);touch-action:pan-y;overscroll-behavior:contain;-webkit-overflow-scrolling:touch;overflow:hidden;scrollbar-gutter:stable;scrollbar-width:none}
80089
+ .xterm-viewport::-webkit-scrollbar{display:none}
80090
+ .xterm-viewport::-webkit-scrollbar-track{display:none}
80091
+ .xterm-viewport::-webkit-scrollbar-thumb{display:none}
80092
+ .xterm-viewport::-webkit-scrollbar-thumb:hover{display:none}
80093
+ .xterm .xterm-scrollable-element>.scrollbar.vertical{width:6px!important;right:2px!important;pointer-events:none!important;opacity:0!important;background:transparent!important;transition:opacity 180ms ease!important}
80094
+ .xterm[data-codingns-scrollbar="visible"] .xterm-scrollable-element>.scrollbar.vertical{opacity:.7!important;pointer-events:auto!important}
80095
+ .xterm .xterm-scrollable-element>.scrollbar.vertical>.slider{width:6px!important;left:0!important;min-height:24px;border-radius:999px;background:rgba(170,178,190,.72)!important;box-shadow:0 1px 4px rgba(0,0,0,.28);transition:background-color 180ms ease,box-shadow 180ms ease!important}
80096
+ .xterm[data-codingns-scrollbar="visible"] .xterm-scrollable-element>.scrollbar.vertical>.slider:hover{background:rgba(224,229,237,.9)!important;box-shadow:0 1px 6px rgba(0,0,0,.4)}
77263
80097
  .xterm .xterm-scrollable-element>.scrollbar.horizontal{display:none!important}
80098
+ /* xterm 的字宽缓存会把单个字符重复 32 次测量宽度,Chrome 对连续的全角标点默认做
80099
+ 标点压缩(「。」从 13px 压到 6.72px),导致 letter-spacing 在每处标点被多补约
80100
+ 6.3px,行内标点密集时渲染内容向右累积溢出网格宽度,行尾一两个字被裁剪。
80101
+ space-all 关闭标点压缩,让字宽测量与单字实际渲染一致,全角标点按两格渲染。 */
80102
+ .xterm-width-cache-measure-container,.xterm-rows{text-spacing-trim:space-all}
77264
80103
  `;
77265
80104
  //#endregion
77266
80105
  //#region src/client/terminal/ui.ts
@@ -77354,8 +80193,12 @@ window.__ModuleLoader__.load({
77354
80193
  const [loading, setLoading] = (0, react.useState)(true);
77355
80194
  const [creating, setCreating] = (0, react.useState)(false);
77356
80195
  const [createError, setCreateError] = (0, react.useState)();
80196
+ const [refreshing, setRefreshing] = (0, react.useState)(false);
80197
+ const [toolsOpen, setToolsOpen] = (0, react.useState)(false);
80198
+ const [heldModifiers, setHeldModifiers] = (0, react.useState)(EMPTY_HELD_MODIFIERS);
77357
80199
  /** React 状态更新前可能收到连续点击;用同步锁保证只发出一个 create 请求。 */
77358
80200
  const creatingRef = (0, react.useRef)(false);
80201
+ const refreshingRef = (0, react.useRef)(false);
77359
80202
  const autoCreatedFor = (0, react.useRef)(void 0);
77360
80203
  const reloadSequence = (0, react.useRef)(0);
77361
80204
  const reload = (0, react.useCallback)(async () => {
@@ -77427,10 +80270,32 @@ window.__ModuleLoader__.load({
77427
80270
  terminals.length
77428
80271
  ]);
77429
80272
  const activeId = terminals.find((item) => item.id === selectedId)?.id ?? terminals[0]?.id;
77430
- const navigation = (0, react.createElement)("nav", {
80273
+ const activeView = activeId === void 0 ? void 0 : webTerminals.viewForTerminal(String(sessionId), activeId, terminals.find((item) => item.id === activeId)?.shell.path);
80274
+ const activePlatform = (0, react.useSyncExternalStore)(activeView?.state.subscribe.bind(activeView.state) ?? noSubscribe, () => activeView?.state.getSnapshot().environment?.platform, () => activeView?.state.getSnapshot().environment?.platform);
80275
+ const refreshTerminal = (0, react.useCallback)(async () => {
80276
+ if (refreshingRef.current) return;
80277
+ refreshingRef.current = true;
80278
+ setRefreshing(true);
80279
+ try {
80280
+ await Promise.all([reload(), activeView?.refresh({ force: true })]);
80281
+ } catch (cause) {
80282
+ debugWarn("codingns4dsh: client terminal toolbar refresh failed", {
80283
+ sessionId: String(sessionId),
80284
+ error: messageOf(cause)
80285
+ });
80286
+ } finally {
80287
+ refreshingRef.current = false;
80288
+ setRefreshing(false);
80289
+ }
80290
+ }, [
80291
+ activeView,
80292
+ reload,
80293
+ sessionId
80294
+ ]);
80295
+ const navigation = (0, react.createElement)("div", { className: terminalClass.listDock }, (0, react.createElement)("nav", {
77431
80296
  className: terminalClass.list,
77432
80297
  "aria-label": t("terminal.title")
77433
- }, [...terminals.map((item) => (0, react.createElement)(TerminalListRow, {
80298
+ }, (0, react.createElement)("div", { className: terminalClass.listTabs }, ...terminals.map((item) => (0, react.createElement)(TerminalListRow, {
77434
80299
  key: item.id,
77435
80300
  item,
77436
80301
  selected: item.id === activeId,
@@ -77454,7 +80319,37 @@ window.__ModuleLoader__.load({
77454
80319
  onClick: () => {
77455
80320
  createNewTerminal();
77456
80321
  }
77457
- })]);
80322
+ })), (0, react.createElement)("div", { className: terminalClass.listActions }, (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
80323
+ variant: "ghost",
80324
+ size: "sm",
80325
+ className: terminalClass.refreshButton,
80326
+ icon: (0, react.createElement)(resolveRefreshIcon(), { size: 16 }),
80327
+ "aria-label": t("terminal.refresh"),
80328
+ title: t("terminal.refresh"),
80329
+ disabled: refreshing,
80330
+ onClick: () => {
80331
+ refreshTerminal();
80332
+ }
80333
+ }), (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
80334
+ variant: "ghost",
80335
+ size: "sm",
80336
+ className: terminalClass.toolsButton,
80337
+ icon: (0, react.createElement)(resolveToolIcon(), { size: 16 }),
80338
+ "aria-label": t("terminal.tools"),
80339
+ title: t("terminal.tools"),
80340
+ "aria-haspopup": "true",
80341
+ "aria-expanded": toolsOpen,
80342
+ onClick: () => setToolsOpen((value) => !value)
80343
+ }))), toolsOpen ? (0, react.createElement)(TerminalToolsPanel, {
80344
+ view: activeView,
80345
+ platform: activePlatform,
80346
+ heldModifiers,
80347
+ onToggleModifier: (modifier) => setHeldModifiers((current) => ({
80348
+ ...current,
80349
+ [modifier]: !current[modifier]
80350
+ })),
80351
+ t
80352
+ }) : null);
77458
80353
  const content = (0, react.createElement)("div", { className: terminalClass.content }, terminals.length === 0 ? (0, react.createElement)("div", {
77459
80354
  role: "status",
77460
80355
  className: terminalClass.empty
@@ -77478,6 +80373,88 @@ window.__ModuleLoader__.load({
77478
80373
  })));
77479
80374
  return (0, react.createElement)("div", { className: `${terminalClass.aggregateRoot} ${terminalClass.content}` }, navigation, content);
77480
80375
  }
80376
+ const EMPTY_HELD_MODIFIERS = {
80377
+ ctrl: false,
80378
+ alt: false,
80379
+ win: false
80380
+ };
80381
+ function TerminalToolsPanel({ view, platform, heldModifiers, onToggleModifier, t }) {
80382
+ const modifier = (kind, label) => (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
80383
+ key: kind,
80384
+ variant: "toolbar",
80385
+ size: "sm",
80386
+ className: `${terminalClass.toolAction} ${heldModifiers[kind] ? terminalClass.toolModifierActive : ""}`,
80387
+ "aria-label": label,
80388
+ title: label,
80389
+ "aria-pressed": heldModifiers[kind],
80390
+ disabled: view === void 0,
80391
+ onClick: () => onToggleModifier(kind)
80392
+ }, (0, react.createElement)(resolveShortcutKeys(), { keys: [terminalModifierKey(kind, platform)] }));
80393
+ const command = (kind, label) => (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.Button, {
80394
+ key: kind,
80395
+ variant: "toolbar",
80396
+ size: "sm",
80397
+ className: terminalClass.toolAction,
80398
+ "aria-label": label,
80399
+ title: label,
80400
+ disabled: view === void 0,
80401
+ onClick: () => {
80402
+ if (view !== void 0) view.write(terminalShortcutData(kind, heldModifiers));
80403
+ }
80404
+ }, terminalToolControl(kind));
80405
+ return (0, react.createElement)("div", {
80406
+ className: terminalClass.toolsPanel,
80407
+ role: "toolbar",
80408
+ "aria-label": t("terminal.tools")
80409
+ }, modifier("ctrl", terminalModifierLabel("ctrl", platform, t)), modifier("alt", terminalModifierLabel("alt", platform, t)), modifier("win", terminalModifierLabel("win", platform, t)), command("tab", t("terminal.shortcutTab")), command("escape", t("terminal.shortcutEscape")), command("enter", t("terminal.shortcutEnter")), command("backspace", t("terminal.shortcutBackspace")), command("up", t("terminal.shortcutUp")), command("down", t("terminal.shortcutDown")), command("left", t("terminal.shortcutLeft")), command("right", t("terminal.shortcutRight")), command("c", t("terminal.shortcutC")), command("d", t("terminal.shortcutD")), command("l", t("terminal.shortcutL")), command("z", t("terminal.shortcutZ")));
80410
+ }
80411
+ function terminalModifierKey(modifier, platform) {
80412
+ if (modifier === "alt" && platform === "darwin") return "Option";
80413
+ if (modifier !== "win") return modifier === "ctrl" ? "Ctrl" : "Alt";
80414
+ if (platform === "darwin") return "Cmd";
80415
+ if (platform === "linux") return "Super";
80416
+ return "Win";
80417
+ }
80418
+ function terminalModifierLabel(modifier, platform, t) {
80419
+ if (modifier === "ctrl") return t("terminal.holdCtrl");
80420
+ if (modifier === "alt" && platform === "darwin") return t("terminal.holdOption");
80421
+ if (modifier === "win" && platform === "darwin") return t("terminal.holdCommand");
80422
+ if (modifier === "win" && platform === "linux") return t("terminal.holdSuper");
80423
+ if (modifier === "alt") return t("terminal.holdAlt");
80424
+ return t("terminal.holdWin");
80425
+ }
80426
+ function terminalToolControl(command) {
80427
+ if (command === "up" || command === "down" || command === "left" || command === "right") return (0, react.createElement)(resolveTerminalArrowIcon(command), { size: 16 });
80428
+ return (0, react.createElement)(resolveShortcutKeys(), { keys: [{
80429
+ tab: "Tab",
80430
+ escape: "Esc",
80431
+ enter: "Enter",
80432
+ backspace: "⌫",
80433
+ c: "C",
80434
+ d: "D",
80435
+ l: "L",
80436
+ z: "Z"
80437
+ }[command]] });
80438
+ }
80439
+ function terminalShortcutData(command, modifiers) {
80440
+ let value = {
80441
+ tab: " ",
80442
+ escape: "\x1B",
80443
+ enter: "\r",
80444
+ backspace: "",
80445
+ up: "\x1B[A",
80446
+ down: "\x1B[B",
80447
+ left: "\x1B[D",
80448
+ right: "\x1B[C",
80449
+ c: "c",
80450
+ d: "d",
80451
+ l: "l",
80452
+ z: "z"
80453
+ }[command];
80454
+ if (modifiers.ctrl && /^[a-z]$/u.test(command)) value = String.fromCharCode(command.charCodeAt(0) - 96);
80455
+ if (modifiers.alt || modifiers.win) value = `\x1b${value}`;
80456
+ return value;
80457
+ }
77481
80458
  function TerminalTitle({ useTabInfo, locale }) {
77482
80459
  const t = useCodingNsTranslator(locale);
77483
80460
  useTabInfo();
@@ -83840,6 +86817,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
83840
86817
  const environmentSchema = object({
83841
86818
  cwd: string().readonly(),
83842
86819
  workspaceId: string().readonly().optional(),
86820
+ platform: union([
86821
+ literal("darwin"),
86822
+ literal("linux"),
86823
+ literal("win32"),
86824
+ literal("unsupported")
86825
+ ]).readonly().optional(),
83843
86826
  maxInputBytes: number().readonly(),
83844
86827
  maxCols: number().readonly(),
83845
86828
  maxRows: number().readonly(),