@jingyi0605/codingns4dsh 0.1.0 → 0.1.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 (216) hide show
  1. package/README.en.md +306 -0
  2. package/README.md +190 -993
  3. package/data/build/dist/client/account-bar.d.ts +2 -2
  4. package/data/build/dist/client/account-bar.js +96 -39
  5. package/data/build/dist/client/bundle.js +13722 -3611
  6. package/data/build/dist/client/cli-catalog.js +20 -3
  7. package/data/build/dist/client/cli-slots.d.ts +5 -0
  8. package/data/build/dist/client/cli-slots.js +57 -7
  9. package/data/build/dist/client/debug/ui.d.ts +1 -1
  10. package/data/build/dist/client/debug/ui.js +62 -32
  11. package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
  12. package/data/build/dist/client/dsh-runtime-version.js +24 -2
  13. package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
  14. package/data/build/dist/client/features/cli-adapters.js +19 -16
  15. package/data/build/dist/client/features/debug.js +2 -2
  16. package/data/build/dist/client/features/index.d.ts +2 -1
  17. package/data/build/dist/client/features/index.js +3 -1
  18. package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
  19. package/data/build/dist/client/features/lan-access-panel.js +8 -10
  20. package/data/build/dist/client/features/lan-access.js +1 -1
  21. package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
  22. package/data/build/dist/client/features/login-protection-panel.js +7 -9
  23. package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
  24. package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
  25. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
  26. package/data/build/dist/client/features/reverse-proxy.js +19 -18
  27. package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
  28. package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
  29. package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
  30. package/data/build/dist/client/features/terminal-enhancement.js +3 -3
  31. package/data/build/dist/client/features/types.d.ts +14 -3
  32. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
  33. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
  34. package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
  35. package/data/build/dist/client/git-management.d.ts +16 -0
  36. package/data/build/dist/client/git-management.js +757 -0
  37. package/data/build/dist/client/index.d.ts +3 -2
  38. package/data/build/dist/client/index.js +152 -27
  39. package/data/build/dist/client/locale.js +91 -29
  40. package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
  41. package/data/build/dist/client/quick-phrase-layout.js +95 -0
  42. package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
  43. package/data/build/dist/client/quick-phrase-slot.js +370 -0
  44. package/data/build/dist/client/remote-web-context.js +1 -1
  45. package/data/build/dist/client/settings-bridge.d.ts +9 -6
  46. package/data/build/dist/client/settings-bridge.js +51 -27
  47. package/data/build/dist/client/settings-section.d.ts +3 -2
  48. package/data/build/dist/client/settings-section.js +33 -19
  49. package/data/build/dist/client/subscription-slot.d.ts +3 -0
  50. package/data/build/dist/client/subscription-slot.js +108 -16
  51. package/data/build/dist/client/terminal/model.d.ts +14 -2
  52. package/data/build/dist/client/terminal/model.js +109 -21
  53. package/data/build/dist/client/terminal/recovery.d.ts +50 -0
  54. package/data/build/dist/client/terminal/recovery.js +101 -0
  55. package/data/build/dist/client/terminal/ui.d.ts +2 -2
  56. package/data/build/dist/client/terminal/ui.js +49 -11
  57. package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
  58. package/data/build/dist/client/terminal/xterm-view.js +2 -1
  59. package/data/build/dist/client/theme.d.ts +6 -1
  60. package/data/build/dist/client/theme.js +32 -1
  61. package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
  62. package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
  63. package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
  64. package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
  65. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
  66. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
  67. package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
  68. package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
  69. package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
  70. package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
  71. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
  72. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
  73. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
  74. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
  75. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
  76. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
  77. package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
  78. package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
  79. package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
  80. package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
  81. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
  82. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
  83. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
  84. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
  85. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
  86. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
  87. package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
  88. package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
  89. package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
  90. package/data/build/dist/dsh-capabilities/index.js +6 -0
  91. package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
  92. package/data/build/dist/dsh-capabilities/matrix.js +32 -0
  93. package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
  94. package/data/build/dist/dsh-capabilities/registry.js +196 -0
  95. package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
  96. package/data/build/dist/dsh-capabilities/routes.js +34 -0
  97. package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
  98. package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
  99. package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
  100. package/data/build/dist/dsh-capabilities/types.js +23 -0
  101. package/data/build/dist/features/registry.d.ts +8 -2
  102. package/data/build/dist/features/registry.js +104 -1
  103. package/data/build/dist/host/auth-session.d.ts +8 -1
  104. package/data/build/dist/host/auth-session.js +28 -6
  105. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
  106. package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
  107. package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
  108. package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
  109. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
  110. package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
  111. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  112. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
  113. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
  114. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
  115. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
  116. package/data/build/dist/host/cli-adapters/feature.js +74 -8
  117. package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
  118. package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
  119. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
  120. package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
  121. package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
  122. package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
  123. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
  124. package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
  125. package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
  126. package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
  127. package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
  128. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
  129. package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
  130. package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
  131. package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
  132. package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
  133. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
  134. package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
  135. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
  136. package/data/build/dist/host/cli-adapters/registry.js +170 -13
  137. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
  138. package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
  139. package/data/build/dist/host/cli-adapters/session-store.js +39 -1
  140. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  141. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
  142. package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
  143. package/data/build/dist/host/control-api-client.d.ts +3 -3
  144. package/data/build/dist/host/control-api-client.js +2 -1
  145. package/data/build/dist/host/credential-store.js +11 -1
  146. package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
  147. package/data/build/dist/host/dsh-device-runtime.js +20 -9
  148. package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
  149. package/data/build/dist/host/dsh-runtime-version.js +71 -2
  150. package/data/build/dist/host/features/auth.js +8 -5
  151. package/data/build/dist/host/features/debug.js +1 -1
  152. package/data/build/dist/host/features/git-management.d.ts +5 -0
  153. package/data/build/dist/host/features/git-management.js +290 -0
  154. package/data/build/dist/host/features/host-status.js +1 -1
  155. package/data/build/dist/host/features/index.d.ts +1 -0
  156. package/data/build/dist/host/features/index.js +3 -0
  157. package/data/build/dist/host/features/terminal-process.js +1 -1
  158. package/data/build/dist/host/features/terminal-status.d.ts +3 -1
  159. package/data/build/dist/host/features/terminal-status.js +3 -1
  160. package/data/build/dist/host/index.d.ts +7 -3
  161. package/data/build/dist/host/index.js +79 -15
  162. package/data/build/dist/host/lan-access-dsh.js +18 -5
  163. package/data/build/dist/host/native-session-bridge.d.ts +23 -1
  164. package/data/build/dist/host/native-session-bridge.js +157 -10
  165. package/data/build/dist/host/rpc-table.d.ts +1 -1
  166. package/data/build/dist/host/rpc.js +60 -10
  167. package/data/build/dist/host/session-migration-repair.d.ts +19 -0
  168. package/data/build/dist/host/session-migration-repair.js +395 -0
  169. package/data/build/dist/host/settings.d.ts +8 -1
  170. package/data/build/dist/host/settings.js +94 -3
  171. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
  172. package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
  173. package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
  174. package/data/build/dist/host/terminal/controller-factory.js +26 -7
  175. package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
  176. package/data/build/dist/host/terminal/runtime-manager.js +3 -0
  177. package/data/build/dist/host/terminal/startup.d.ts +2 -0
  178. package/data/build/dist/host/terminal/startup.js +2 -1
  179. package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
  180. package/data/build/dist/host/terminal/terminal-controller.js +101 -15
  181. package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
  182. package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
  183. package/data/build/dist/host/terminal/terminal-service.js +10 -0
  184. package/data/build/dist/index.d.ts +6 -2
  185. package/data/build/dist/index.js +8 -2
  186. package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
  187. package/data/build/dist/shared/contracts/config.d.ts +31 -1
  188. package/data/build/dist/shared/contracts/config.js +25 -0
  189. package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
  190. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  191. package/data/build/dist/shared/contracts/feature.d.ts +6 -0
  192. package/data/build/dist/shared/contracts/git.d.ts +82 -0
  193. package/data/build/dist/shared/contracts/git.js +3 -0
  194. package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
  195. package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
  196. package/data/build/dist/shared/contracts/version.d.ts +2 -2
  197. package/data/build/dist/shared/contracts/version.js +4 -6
  198. package/data/build/dist/shared/debug.d.ts +14 -0
  199. package/data/build/dist/shared/debug.js +53 -0
  200. package/data/build/dist/shared/index.d.ts +7 -4
  201. package/data/build/dist/shared/index.js +2 -1
  202. package/data/build/dist/transport/debug.d.ts +1 -1
  203. package/data/build/dist/transport/debug.js +3 -30
  204. package/data/build/dist/transport/dsh-session.js +3 -3
  205. package/data/build/dist/typert.host.d.ts +1 -1
  206. package/data/build/dist/typert.host.js +6 -3
  207. package/data/build/dist/typert.remote-client.d.ts +11 -0
  208. package/data/build/dist/typert.remote-client.js +7 -0
  209. package/dsh.bundle.patch +1 -1
  210. package/package.json +13 -5
  211. package/profile/README.md +4 -4
  212. package/profile/package.json +6 -4
  213. package/profile/scripts/check-dsh-install.mjs +2 -2
  214. package/profile/version.json +2 -2
  215. package/scripts/check-dsh-install.mjs +2 -2
  216. package/version.json +2 -2
@@ -1,22 +1,38 @@
1
1
  import { CODINGNS_RPC_CHANNEL } from '../shared/contracts/transport.js';
2
+ import { debugInfo, debugWarn } from '../shared/debug.js';
2
3
  /** Client 侧访问 Host CLI 命名空间的统一入口。 */
3
4
  export async function callCliRpc(rpc, action, payload) {
4
5
  let response;
5
6
  try {
7
+ debugInfo('codingns4dsh: cli rpc request', { channel: CODINGNS_RPC_CHANNEL, action });
6
8
  response = await rpc.call(CODINGNS_RPC_CHANNEL, `cli/${action}`, payload);
7
9
  }
8
10
  catch (error) {
9
11
  const message = error instanceof Error ? error.message : String(error);
10
12
  if (!/HTTP (?:404|405)\b/u.test(message))
11
13
  throw error;
12
- response = await rpc.call('/api', `codingns/cli/${action}`, payload);
14
+ debugWarn('codingns4dsh: cli rpc fallback', { action, error: message });
15
+ try {
16
+ response = await rpc.call('/api', `codingns/cli/${action}`, payload);
17
+ }
18
+ catch (fallbackError) {
19
+ console.error('codingns4dsh: cli rpc fallback failed', { action, error: fallbackError });
20
+ throw fallbackError;
21
+ }
13
22
  }
14
- if (!response.ok)
23
+ if (!response.ok) {
24
+ console.error('codingns4dsh: cli rpc response error', { action, error: response.error });
15
25
  throw new Error(response.error.message);
26
+ }
27
+ debugInfo('codingns4dsh: cli rpc response success', { action });
16
28
  return response.value;
17
29
  }
18
30
  export function adapterCatalogWithDsh(catalog) {
19
- return [{ id: 'dsh', name: 'DeepSeek Harness', installed: true, enabled: true, version: null, command: null }, ...catalog];
31
+ // 对话框只展示实际可用的适配器;未安装或已停用的适配器由设置页管理。
32
+ return [
33
+ { id: 'dsh', name: 'DeepSeek Harness', installed: true, enabled: true, version: null, command: null },
34
+ ...catalog.filter((adapter) => adapter.installed && adapter.enabled),
35
+ ];
20
36
  }
21
37
  export function firstModel(catalog) {
22
38
  for (const group of catalog.groups) {
@@ -58,6 +74,7 @@ export async function restoreCliSession(rpc, record) {
58
74
  adapterId: record.adapterId,
59
75
  ...(record.modelId ? { modelId: record.modelId } : {}),
60
76
  ...(record.effortId ? { effortId: record.effortId } : {}),
77
+ ...(record.providerId ? { providerId: record.providerId } : {}),
61
78
  ...(record.providerSessionId ? { providerSessionId: record.providerSessionId } : {}),
62
79
  });
63
80
  navigateToDshSession(record.dshSessionId);
@@ -4,6 +4,7 @@ import type { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client';
4
4
  import { type CodingNsLocale } from './locale.js';
5
5
  interface SessionSnapshot {
6
6
  readonly sessionId?: string;
7
+ readonly modelSelection?: unknown;
7
8
  readonly blank?: boolean;
8
9
  readonly promptAttempted?: boolean;
9
10
  readonly running?: boolean;
@@ -13,6 +14,10 @@ type SessionSelector = <Selected>(selector: (session: SessionSnapshot) => Select
13
14
  /** DSH Web 当前版本的对话工具栏 Slot 契约。Slot 包没有预声明这些业务名称,插件在此补齐类型。 */
14
15
  declare module '@deepseek-ai/dsh-client-ui-slots' {
15
16
  interface SlotMap {
17
+ 'conversation.input.left': {
18
+ kind: 'list';
19
+ scope: 'session';
20
+ };
16
21
  'conversation.input.right': {
17
22
  kind: 'list';
18
23
  scope: 'session';
@@ -1,4 +1,4 @@
1
- import { createElement, useEffect, useState } from 'react';
1
+ import { createElement, useEffect, useRef, useState } from 'react';
2
2
  import { adapterCatalogWithDsh, callCliRpc, findModel, firstModel } from './cli-catalog.js';
3
3
  import { providerIconUrl } from './provider-icons.js';
4
4
  import { publishSessionAdapter } from './session-adapter-cache.js';
@@ -15,12 +15,23 @@ function installComposerStyles() {
15
15
  style.textContent = [
16
16
  'html[data-codingns-agent]:not([data-codingns-agent="dsh"]) [data-slot="conversation.input.model"],',
17
17
  'body[data-codingns-agent]:not([data-codingns-agent="dsh"]) [data-slot="conversation.input.model"]{display:none!important}',
18
+ // DSH 原生输入栏默认允许工具行换行;选择器过长时保持单行并让右侧区域收缩。
19
+ '[data-composer-card] > div:has([data-slot="conversation.input.right"]){flex-wrap:nowrap!important}',
20
+ '[data-composer-card] > div:has([data-slot="conversation.input.right"]) > div:has(> [data-slot="conversation.input.right"]){min-width:0;width:0;flex:1 1 0}',
21
+ '[data-composer-card] > div:has([data-slot="conversation.input.right"]) [data-slot="conversation.input.right"] > .codingns4dsh-model-root{min-width:0;max-width:min(360px,45cqw);flex:1 1 min(360px,45cqw)}',
22
+ '[data-composer-card] > div:has([data-slot="conversation.input.right"]) [data-slot="conversation.input.model"] > select{width:100%;min-width:0;max-width:min(150px,45cqw);flex:1 1 min(150px,45cqw);overflow:hidden;white-space:nowrap}',
23
+ '[data-composer-card] > div:has([data-slot="conversation.input.right"]) [data-slot="conversation.input.model"] > button{width:100%;min-width:0;max-width:min(360px,45cqw);overflow:hidden;white-space:nowrap}',
24
+ // ContextMeter 在 pressure 尚未合并时会暂时返回 null;dock 保留同样的行高,数值回来时只更新内容。
25
+ '[data-composer-card] + div{box-sizing:border-box;min-height:26px;align-items:center}',
26
+ '[data-composer-card] + div svg[viewBox="0 0 14 14"] circle:last-child{transition:stroke-dasharray .18s ease,stroke .18s ease}',
18
27
  '@keyframes codingns4dsh-cli-spin{to{transform:rotate(360deg)}}',
28
+ '@keyframes codingns4dsh-cli-model-scroll{0%,18%{transform:translateX(0)}82%,100%{transform:translateX(var(--codingns4dsh-model-scroll-offset))}}',
19
29
  '.codingns4dsh-cli-spinner{animation:codingns4dsh-cli-spin .8s linear infinite}',
30
+ '.codingns4dsh-model-name[data-overflow="true"]>span{animation:codingns4dsh-cli-model-scroll 6s ease-in-out infinite alternate;will-change:transform}',
20
31
  '.codingns4dsh-agent-trigger:hover:not(:disabled){background:color-mix(in srgb,currentColor 7%,transparent)}',
21
32
  '.codingns4dsh-agent-option:hover:not(:disabled){background:color-mix(in srgb,currentColor 7%,transparent)!important}',
22
33
  '.codingns4dsh-agent-option[data-selected="true"]{background:color-mix(in srgb,currentColor 10%,transparent)!important}',
23
- '@media (prefers-reduced-motion:reduce){.codingns4dsh-cli-spinner{animation-duration:1.6s}}',
34
+ '@media (prefers-reduced-motion:reduce){.codingns4dsh-cli-spinner{animation-duration:1.6s}.codingns4dsh-model-name[data-overflow="true"]>span{animation:none;transform:translateX(0)}}',
24
35
  ].join('');
25
36
  document.head.appendChild(style);
26
37
  }
@@ -174,10 +185,10 @@ function NativeDropdownChevron({ open, locked = false }) {
174
185
  fill: 'currentColor',
175
186
  }));
176
187
  }
177
- const agentRootStyle = { position: 'relative', minWidth: 0, display: 'inline-flex' };
178
- const agentTriggerStyle = { height: 30, maxWidth: 220, minWidth: 0, color: dshThemeColor.labelPrimary, border: 0, borderRadius: 8, padding: '0 6px', background: 'transparent', display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 14, lineHeight: '20px' };
188
+ const agentRootStyle = { position: 'relative', minWidth: 0, flex: '0 0 auto', display: 'inline-flex' };
189
+ const agentTriggerStyle = { height: 30, minWidth: 0, color: dshThemeColor.labelPrimary, border: 0, borderRadius: 8, padding: '0 6px', background: 'transparent', display: 'inline-flex', alignItems: 'center', gap: 7, fontSize: 14, lineHeight: '20px', whiteSpace: 'nowrap' };
179
190
  const agentTriggerIconStyle = { width: 20, height: 20, flex: '0 0 20px', objectFit: 'contain' };
180
- const agentTriggerLabelStyle = { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
191
+ const agentTriggerLabelStyle = { flex: '0 0 auto', whiteSpace: 'nowrap' };
181
192
  const nativeDropdownChevronStyle = { display: 'block', flex: '0 0 14px', color: dshThemeColor.labelCaption, transformOrigin: 'center' };
182
193
  const agentMenuStyle = { ...dshPopupSurfaceStyle, position: 'absolute', zIndex: 1100, bottom: 'calc(100% + 8px)', left: 0, minWidth: 238, maxWidth: 'min(320px, calc(100vw - 32px))', maxHeight: 'min(400px, calc(100vh - 96px))', overflowY: 'auto', padding: 5, border: 0, borderRadius: 8 };
183
194
  const agentOptionStyle = { width: '100%', minHeight: 40, color: 'inherit', border: 0, borderRadius: 6, padding: '5px 8px 5px 4px', background: 'transparent', display: 'flex', alignItems: 'center', gap: 8, textAlign: 'left', fontSize: 14, lineHeight: '20px' };
@@ -186,6 +197,44 @@ const agentOptionIconStyle = { width: 22, height: 22, flex: '0 0 22px', objectFi
186
197
  const agentOptionLabelStyle = { flex: '1 1 auto', minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' };
187
198
  const agentStatusStyle = { flex: '0 0 auto', color: dshThemeColor.labelTertiary, fontSize: 12, whiteSpace: 'nowrap' };
188
199
  const agentFallbackIconStyle = { flexGrow: 0, flexShrink: 0, borderRadius: 5, color: '#fff', background: dshThemeColor.labelTertiary, display: 'inline-flex', alignItems: 'center', justifyContent: 'center', fontSize: 11, fontWeight: 600, lineHeight: 1 };
200
+ /** 模型名称超出 150px 时,在固定视口内往返滚动展示完整文本。 */
201
+ function ModelName({ label, loading }) {
202
+ const viewportRef = useRef(null);
203
+ const contentRef = useRef(null);
204
+ const [scrollDistance, setScrollDistance] = useState(0);
205
+ useEffect(() => {
206
+ const updateOverflow = () => {
207
+ const viewport = viewportRef.current;
208
+ const content = contentRef.current;
209
+ if (viewport === null || content === null)
210
+ return;
211
+ const nextDistance = Math.max(0, Math.ceil(content.getBoundingClientRect().width - viewport.clientWidth));
212
+ setScrollDistance((current) => current === nextDistance ? current : nextDistance);
213
+ };
214
+ updateOverflow();
215
+ if (typeof ResizeObserver === 'undefined')
216
+ return;
217
+ const observer = new ResizeObserver(updateOverflow);
218
+ if (viewportRef.current !== null)
219
+ observer.observe(viewportRef.current);
220
+ if (contentRef.current !== null)
221
+ observer.observe(contentRef.current);
222
+ return () => observer.disconnect();
223
+ }, [label]);
224
+ const contentStyle = {
225
+ display: 'inline-block',
226
+ whiteSpace: 'nowrap',
227
+ '--codingns4dsh-model-scroll-offset': `-${scrollDistance}px`,
228
+ };
229
+ return createElement('span', {
230
+ ref: viewportRef,
231
+ className: 'codingns4dsh-model-name',
232
+ 'data-overflow': String(scrollDistance > 0),
233
+ role: loading ? 'status' : undefined,
234
+ 'aria-live': loading ? 'polite' : undefined,
235
+ style: modelNameStyle,
236
+ }, createElement('span', { ref: contentRef, style: contentStyle }, label));
237
+ }
189
238
  function ModelSlot(props) {
190
239
  // 语言词典中的中文值仍保留“正在加载模型列表…”语义,切换语言时由 t() 取值。
191
240
  const t = useCodingNsTranslator(props.locale);
@@ -275,9 +324,10 @@ function ModelSlot(props) {
275
324
  createElement('div', { key: 'title', style: nativeGroupTitleStyle }, `${t('cli.thinking')}(${modelLabel})`),
276
325
  ...(efforts.length > 0 ? efforts : ['default']).map((effort) => createElement('button', { key: effort, type: 'button', role: 'menuitemradio', 'aria-checked': effort === effortValue, onClick: () => chooseEffort(effort), style: nativeOptionStyle }, createElement('span', { style: { flex: '1 1 auto' } }, effort === 'default' ? 'Default' : effort), effort === effortValue && createElement('span', { 'aria-hidden': true }, '✓'))),
277
326
  ];
278
- return createElement('div', { style: { position: 'relative', minWidth: 0, display: 'inline-flex' } }, createElement('button', { type: 'button', disabled: triggerDisabled, 'aria-label': t('cli.chooseModel', { model: modelLabel, effort: effortLabel }), 'aria-busy': loading, 'aria-haspopup': 'menu', 'aria-expanded': open, onClick: () => { setPane('root'); setOpen((value) => !value); }, style: nativeTriggerStyle }, loading && createElement('span', { className: 'codingns4dsh-cli-spinner', 'aria-hidden': true, style: modelSpinnerStyle }), createElement('span', { role: loading ? 'status' : undefined, 'aria-live': loading ? 'polite' : undefined, style: { minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' } }, modelLabel), !loading && createElement('span', { style: { color: dshThemeColor.labelCaption, whiteSpace: 'nowrap' } }, effortLabel), !loading && createElement(NativeDropdownChevron, { open })), open && createElement('div', { role: 'menu', 'aria-label': t('cli.chooseModelMenu'), style: nativeMenuStyle }, ...menu));
327
+ return createElement('div', { className: 'codingns4dsh-model-root', style: { position: 'relative', minWidth: 0, maxWidth: '100%', flex: '1 1 min(360px, 45cqw)', display: 'inline-flex' } }, createElement('button', { type: 'button', disabled: triggerDisabled, 'aria-label': t('cli.chooseModel', { model: modelLabel, effort: effortLabel }), 'aria-busy': loading, 'aria-haspopup': 'menu', 'aria-expanded': open, onClick: () => { setPane('root'); setOpen((value) => !value); }, style: nativeTriggerStyle }, loading && createElement('span', { className: 'codingns4dsh-cli-spinner', 'aria-hidden': true, style: modelSpinnerStyle }), createElement(ModelName, { label: modelLabel, loading }), !loading && createElement('span', { style: { color: dshThemeColor.labelCaption, whiteSpace: 'nowrap' } }, effortLabel), !loading && createElement(NativeDropdownChevron, { open })), open && createElement('div', { role: 'menu', 'aria-label': t('cli.chooseModelMenu'), style: nativeMenuStyle }, ...menu));
279
328
  }
280
- const nativeTriggerStyle = { minWidth: 0, maxWidth: 'min(360px, 45cqw)', height: 28, color: dshThemeColor.labelSecondary, cursor: 'pointer', background: 'transparent', border: 0, borderRadius: 24, padding: '0 4px 0 8px', display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 13, lineHeight: '20px' };
329
+ const nativeTriggerStyle = { width: '100%', minWidth: 0, maxWidth: 'min(360px, 45cqw)', height: 28, color: dshThemeColor.labelSecondary, cursor: 'pointer', background: 'transparent', border: 0, borderRadius: 24, padding: '0 4px 0 8px', display: 'inline-flex', alignItems: 'center', gap: 4, fontSize: 13, lineHeight: '20px' };
330
+ const modelNameStyle = { minWidth: 0, maxWidth: 150, flex: '0 1 150px', display: 'block', overflow: 'hidden', whiteSpace: 'nowrap' };
281
331
  const nativeMenuStyle = { ...dshPopupSurfaceStyle, position: 'absolute', zIndex: 1100, right: 0, bottom: 'calc(100% + 8px)', minWidth: 240, maxWidth: 'min(420px, calc(100vw - 32px))', maxHeight: 'min(360px, calc(100vh - 96px))', overflowY: 'auto', padding: 4, border: 0, borderRadius: 20 };
282
332
  const nativeMenuCellStyle = { width: '100%', minHeight: 40, color: 'inherit', cursor: 'pointer', background: 'transparent', border: 0, borderRadius: 10, padding: '0 10px', display: 'flex', alignItems: 'center', gap: 8, textAlign: 'left', fontSize: 14, lineHeight: '22px' };
283
333
  const nativeMenuLabelStyle = { flex: 'none', whiteSpace: 'nowrap' };
@@ -3,7 +3,7 @@ import type { CodingNsRpcClient } from '../features/types.js';
3
3
  export declare const DEBUG_KIND = "debug";
4
4
  export declare const DEBUG_PROVIDER_ID = "codingns4dsh/debug";
5
5
  /** 注册最小 Debug 页面;页面只负责展示和发送用户意图。 */
6
- export declare function registerDebugUi(ctx: Context, rpc: CodingNsRpcClient, remote: unknown): () => void;
6
+ export declare function registerDebugUi(ctx: Context, rpc: CodingNsRpcClient, remote: unknown, terminalRemote?: () => unknown): () => void;
7
7
  declare module '@deepseek-ai/dsh-client-ui-sidebar-right/client' {
8
8
  interface SidebarRightTabParamsMap {
9
9
  debug: undefined;
@@ -1,28 +1,43 @@
1
1
  import { createElement, useEffect, useState } from 'react';
2
2
  import { CODINGNS_RPC_CHANNEL } from '../../shared/contracts/transport.js';
3
+ import { debugWarn } from '../../shared/debug.js';
3
4
  import { dshButtonStyle, dshFieldStyle, dshFormRootStyle, dshThemeColor } from '../theme.js';
4
5
  export const DEBUG_KIND = 'debug';
5
6
  export const DEBUG_PROVIDER_ID = 'codingns4dsh/debug';
6
7
  const PORT_CHECK_INTERVAL_MS = 5_000;
7
8
  /** 注册最小 Debug 页面;页面只负责展示和发送用户意图。 */
8
- export function registerDebugUi(ctx, rpc, remote) {
9
+ export function registerDebugUi(ctx, rpc, remote, terminalRemote) {
9
10
  const disposers = [];
10
- disposers.push(ctx.sidebarRightTabs.register({
11
- id: DEBUG_PROVIDER_ID,
12
- kind: DEBUG_KIND,
13
- multiple: false,
14
- priority: 'extension',
15
- title: () => '调试',
16
- guide: [{ id: 'debug', order: 30, title: () => '调试', description: () => '启动工作区命令、检查端口并访问服务', icon: DebugIcon }],
17
- }));
18
- disposers.push(ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
19
- name: 'sidebar.right.pane.tab', key: DEBUG_PROVIDER_ID,
20
- inject: () => ({ rpc, remote, sidebarRight: ctx.sidebarRight }),
21
- }, DebugBody)));
11
+ try {
12
+ disposers.push(ctx.sidebarRightTabs.register({
13
+ id: DEBUG_PROVIDER_ID,
14
+ kind: DEBUG_KIND,
15
+ multiple: false,
16
+ priority: 'extension',
17
+ title: () => '调试',
18
+ guide: [{ id: 'debug', order: 30, title: () => '调试', description: () => '启动工作区命令、检查端口并访问服务', icon: DebugIcon }],
19
+ }));
20
+ disposers.push(ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register({
21
+ name: 'sidebar.right.pane.tab', key: DEBUG_PROVIDER_ID,
22
+ inject: () => ({ rpc, remote, terminalRemote, sidebarRight: ctx.sidebarRight }),
23
+ }, DebugBody)));
24
+ }
25
+ catch (error) {
26
+ for (const dispose of disposers.reverse())
27
+ dispose();
28
+ if (isDuplicateDebugRegistration(error)) {
29
+ debugWarn(`codingns4dsh: 调试 Sidebar 已注册,跳过重复注册: ${DEBUG_PROVIDER_ID}`);
30
+ return () => { };
31
+ }
32
+ throw error;
33
+ }
22
34
  return () => { for (const dispose of disposers.reverse())
23
35
  dispose(); };
24
36
  }
25
- function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
37
+ function isDuplicateDebugRegistration(error) {
38
+ return error instanceof Error && /sidebarRight: (?:tab type id|tab kind) .* already registered/u.test(error.message);
39
+ }
40
+ function DebugBody({ sessionId, rpc, remote, terminalRemote, sidebarRight }) {
26
41
  const [workspaceId, setWorkspaceId] = useState(null);
27
42
  const [config, setConfig] = useState(null);
28
43
  const [instances, setInstances] = useState([]);
@@ -32,10 +47,13 @@ function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
32
47
  const [draft, setDraft] = useState(null);
33
48
  const [message, setMessage] = useState('正在读取工作区…');
34
49
  const [busy, setBusy] = useState(false);
35
- const load = async (currentWorkspaceId) => {
50
+ const load = async (currentWorkspaceId, isCurrent) => {
36
51
  const scope = { sessionId: String(sessionId), workspaceId: currentWorkspaceId, generation: 0 };
37
52
  const next = await call(rpc, 'debug/config/get', scope);
38
53
  const running = await call(rpc, 'debug/runtime/list', scope);
54
+ // Session 切换后,旧请求的结果不能覆盖新 Session 的面板状态。
55
+ if (isCurrent?.() === false)
56
+ return;
39
57
  setConfig(next);
40
58
  setInstances(running);
41
59
  setMessage('');
@@ -43,7 +61,7 @@ function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
43
61
  useEffect(() => {
44
62
  let disposed = false;
45
63
  setPortChecks({});
46
- const terminal = remote?.terminal;
64
+ const terminal = terminalRemote?.();
47
65
  void call(rpc, 'terminal/status', {}).then((status) => {
48
66
  if (!disposed)
49
67
  setTerminalStatus(status);
@@ -57,7 +75,7 @@ function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
57
75
  if (disposed)
58
76
  return;
59
77
  setWorkspaceId(id);
60
- await load(id);
78
+ await load(id, () => !disposed);
61
79
  }
62
80
  catch (error) {
63
81
  if (!disposed)
@@ -115,7 +133,7 @@ function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
115
133
  if (workspaceId === null)
116
134
  return createElement('section', { style: panelStyle }, createElement('div', { style: loadingStyle }, createElement('span', { style: loadingDotStyle }), message));
117
135
  const profiles = config?.profiles ?? [];
118
- return createElement('section', { style: panelStyle }, createElement('header', { style: headerStyle }, createElement('div', { style: titleBlockStyle }, createElement('div', { style: eyebrowStyle }, '工作区工具'), createElement('h2', { style: titleStyle }, '工作区调试'), createElement('div', { style: workspaceStyle, title: workspaceId }, createElement('span', { style: workspaceDotStyle }), workspaceId)), createElement('div', { style: headerActionsStyle }, createElement('span', { style: countStyle }, `${profiles.length} 个配置`), createElement('button', { type: 'button', 'aria-label': '添加启动配置', disabled: busy || draft !== null, onClick: () => setDraft(createProfileDraft()), style: primaryButtonStyle }, createElement('span', { 'aria-hidden': true }, '+'), ' 添加配置'))), message && createElement('div', { role: 'status', 'aria-live': 'polite', style: statusStyle(statusTone(message)) }, createElement('span', { style: statusIconStyle }, statusTone(message) === 'success' ? '✓' : statusTone(message) === 'error' ? '!' : 'i'), message), draft === null ? null : createProfileForm(draft), profiles.length === 0 && draft === null ? createElement('div', { style: emptyStyle }, createElement('div', { style: emptyIconStyle }, createElement(DebugIcon, { size: 22 })), createElement('strong', { style: emptyTitleStyle }, '还没有启动配置'), createElement('p', { style: emptyTextStyle }, '为当前 Workspace 添加一个命令,即可从这里启动终端、检查端口和访问服务。'), createElement('button', { type: 'button', 'aria-label': '添加启动配置', disabled: busy, onClick: () => setDraft(createProfileDraft()), style: secondaryButtonStyle }, '+ 添加第一个配置')) : null, profiles.map((profile) => {
136
+ return createElement('section', { style: panelStyle }, createElement('header', { style: headerStyle }, createElement('div', { style: titleBlockStyle }, createElement('div', { style: eyebrowStyle }, '工作区工具'), createElement('h2', { style: titleStyle }, '工作区调试'), createElement('div', { style: workspaceStyle, title: workspaceId }, createElement('span', { style: workspaceDotStyle }), workspaceId)), createElement('div', { style: headerActionsStyle }, createElement('span', { style: countStyle }, `${profiles.length} 个配置`), createElement('button', { type: 'button', 'aria-label': '添加启动配置', disabled: busy || draft !== null, onClick: () => setDraft(createProfileDraft()), style: primaryButtonStyle }, createElement('span', { 'aria-hidden': true }, '+'), ' 添加配置'))), message && createElement('div', { role: 'status', 'aria-live': 'polite', style: statusStyle(statusTone(message)) }, createElement('span', { style: statusIconStyle }, statusTone(message) === 'success' ? '✓' : statusTone(message) === 'error' ? '!' : 'i'), message), terminalStatus?.runtimeWarning === undefined ? null : createElement('div', { role: 'alert', style: warningStatusStyle }, createElement('span', { style: statusIconStyle }, '!'), terminalStatus.runtimeWarning), draft === null ? null : createProfileForm(draft), profiles.length === 0 && draft === null ? createElement('div', { style: emptyStyle }, createElement('div', { style: emptyIconStyle }, createElement(DebugIcon, { size: 22 })), createElement('strong', { style: emptyTitleStyle }, '还没有启动配置'), createElement('p', { style: emptyTextStyle }, '为当前 Workspace 添加一个命令,即可从这里启动终端、检查端口和访问服务。'), createElement('button', { type: 'button', 'aria-label': '添加启动配置', disabled: busy, onClick: () => setDraft(createProfileDraft()), style: secondaryButtonStyle }, '+ 添加第一个配置')) : null, profiles.map((profile) => {
119
137
  const running = runningInstances(profile, instances);
120
138
  const portCheck = portChecks[profile.id];
121
139
  return createElement('article', { key: profile.id, style: itemStyle }, createElement('div', { style: itemHeaderStyle }, createElement('div', { style: itemTitleBlockStyle }, createElement('strong', { style: itemTitleStyle }, profile.name), createElement('span', { style: itemCommandStyle }, formatCommand(profile))), createElement('span', { style: running > 0 ? runningBadgeStyle : stoppedBadgeStyle }, running > 0 ? `${running} 个运行中` : '未运行')), createElement('div', { style: metaStyle }, createElement('span', { style: metaItemStyle }, createElement('span', { style: metaKeyStyle }, '目录'), profile.cwdRelative || '.'), profile.port === null ? null : createElement('span', { style: metaItemStyle }, createElement('span', { style: metaKeyStyle }, '端口'), String(profile.port)), createElement('span', { style: metaItemStyle }, createElement('span', { style: metaKeyStyle }, '运行方式'), profile.runtimeType), profile.proxy.enabled ? createElement('span', { style: proxyBadgeStyle }, '代理已启用') : null), profile.port === null ? null : createElement('div', { style: portStatusStyle }, portCheck === undefined
@@ -150,7 +168,7 @@ function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
150
168
  style: inputStyle,
151
169
  }));
152
170
  const selectField = (label, key, options) => createElement('label', { style: selectFieldStyle }, createElement('span', { style: fieldLabelStyle }, label), createElement('select', { value: value[key], onChange: (event) => setDraft({ ...value, [key]: event.currentTarget.value }), style: inputStyle }, ...options.map((option) => createElement('option', { key: option.value, value: option.value }, option.label))));
153
- return createElement('div', { role: 'dialog', 'aria-modal': true, 'aria-labelledby': 'debug-shortcut-title', style: formOverlayStyle }, createElement('form', { style: formStyle, onSubmit: (event) => { event.preventDefault(); void saveProfile(value); } }, createElement('div', { style: formHeaderStyle }, createElement('div', undefined, createElement('h3', { id: 'debug-shortcut-title', style: formTitleStyle }, value.id === null ? '添加快捷启动项' : '编辑快捷启动项'), createElement('p', { style: formHintStyle }, '保存一组可重复使用的启动参数,之后可以一键打开终端。')), createElement('button', { type: 'button', disabled: busy, onClick: () => setDraft(null), style: closeButtonStyle, 'aria-label': '关闭快捷启动项' }, '×')), createElement('section', { style: formSectionStyle }, createElement('div', { style: formSectionHeaderStyle }, createElement('strong', { style: formSectionTitleStyle }, '启动入口'), createElement('span', { style: formSectionHintStyle }, '直接输入完整命令,例如 pnpm run dev --host 0.0.0.0。')), createElement('div', { style: formGridStyle }, field('名称', 'name', 'text', '例如:前端开发'), field('启动目录(Workspace 内相对路径)', 'cwdRelative', 'text', '留空使用根目录'), field('完整启动命令', 'commandLine', 'text', '例如:pnpm run dev --host 0.0.0.0', { gridColumn: '1 / -1' }))), createElement('section', { style: formSectionStyle }, createElement('div', { style: formSectionHeaderStyle }, createElement('strong', { style: formSectionTitleStyle }, '执行环境(可选)'), createElement('span', { style: formSectionHintStyle }, '只显示 Host 实际检测到的 Shell。')), createElement('div', { style: formGridStyle }, selectField('终端 Shell', 'shellProfileId', shellOptions(terminalStatus))), createElement('p', { style: runtimeHintStyle }, `运行方式由 Host 平台自动选择:${terminalRuntimeLabel(terminalStatus?.platform)}。`)), createElement('section', { style: formSectionStyle }, createElement('div', { style: formSectionHeaderStyle }, createElement('strong', { style: formSectionTitleStyle }, '服务检查'), createElement('span', { style: formSectionHintStyle }, '端口每 5 秒自动检查,也可手动刷新。')), createElement('div', { style: formGridStyle }, field('监听端口(可选)', 'port', 'number', '1 - 65535'), createElement('label', { style: proxyFieldStyle }, createElement('span', { style: fieldLabelStyle }, '反向代理'), createElement('span', { style: proxyToggleStyle }, createElement('span', undefined, '允许访问已确认的服务'), createElement('input', { type: 'checkbox', 'aria-label': '启用服务代理', checked: value.proxyEnabled, onChange: (event) => setDraft({ ...value, proxyEnabled: event.currentTarget.checked }), style: { accentColor: dshThemeColor.accent } }))))), createElement('div', { style: formActionsStyle }, createElement('button', { type: 'button', disabled: busy, onClick: () => setDraft(null), style: secondaryButtonStyle }, '关闭'), createElement('button', { type: 'submit', 'aria-label': value.id === null ? '保存配置并创建快捷启动项' : '保存快捷启动项修改', disabled: busy || value.commandLine.trim() === '', style: primaryButtonStyle }, busy ? '保存中…' : value.id === null ? '保存为快捷启动' : '保存修改'))));
171
+ return createElement('div', { role: 'dialog', 'aria-modal': true, 'aria-labelledby': 'debug-shortcut-title', style: formOverlayStyle }, createElement('form', { style: formStyle, onSubmit: (event) => { event.preventDefault(); void saveProfile(value); } }, createElement('div', { style: formHeaderStyle }, createElement('div', undefined, createElement('h3', { id: 'debug-shortcut-title', style: formTitleStyle }, value.id === null ? '添加快捷启动项' : '编辑快捷启动项'), createElement('p', { style: formHintStyle }, '保存一组可重复使用的启动参数,之后可以一键打开终端。')), createElement('button', { type: 'button', disabled: busy, onClick: () => setDraft(null), style: closeButtonStyle, 'aria-label': '关闭快捷启动项' }, '×')), createElement('section', { style: formSectionStyle }, createElement('div', { style: formSectionHeaderStyle }, createElement('strong', { style: formSectionTitleStyle }, '启动入口'), createElement('span', { style: formSectionHintStyle }, '直接输入完整命令,例如 pnpm run dev --host 0.0.0.0。')), createElement('div', { style: formGridStyle }, field('名称', 'name', 'text', '例如:前端开发'), field('启动目录(Workspace 内相对路径)', 'cwdRelative', 'text', '留空使用根目录'), field('完整启动命令', 'commandLine', 'text', '例如:pnpm run dev --host 0.0.0.0', { gridColumn: '1 / -1' }))), createElement('section', { style: formSectionStyle }, createElement('div', { style: formSectionHeaderStyle }, createElement('strong', { style: formSectionTitleStyle }, '执行环境(可选)'), createElement('span', { style: formSectionHintStyle }, '只显示 Host 实际检测到的 Shell。')), createElement('div', { style: formGridStyle }, selectField('终端 Shell', 'shellProfileId', shellOptions(terminalStatus))), createElement('p', { style: runtimeHintStyle }, `运行方式由 Host 平台自动选择(实际能力:${terminalRuntimeLabel(terminalStatus)})。`), terminalStatus?.runtimeWarning === undefined ? null : createElement('p', { style: warningHintStyle }, terminalStatus.runtimeWarning)), createElement('section', { style: formSectionStyle }, createElement('div', { style: formSectionHeaderStyle }, createElement('strong', { style: formSectionTitleStyle }, '服务检查'), createElement('span', { style: formSectionHintStyle }, '端口每 5 秒自动检查,也可手动刷新。')), createElement('div', { style: formGridStyle }, field('监听端口(可选)', 'port', 'number', '1 - 65535'), createElement('label', { style: proxyFieldStyle }, createElement('span', { style: fieldLabelStyle }, '反向代理'), createElement('span', { style: proxyToggleStyle }, createElement('span', undefined, '允许访问已确认的服务'), createElement('input', { type: 'checkbox', 'aria-label': '启用服务代理', checked: value.proxyEnabled, onChange: (event) => setDraft({ ...value, proxyEnabled: event.currentTarget.checked }), style: { accentColor: dshThemeColor.accent } }))))), createElement('div', { style: formActionsStyle }, createElement('button', { type: 'button', disabled: busy, onClick: () => setDraft(null), style: secondaryButtonStyle }, '关闭'), createElement('button', { type: 'submit', 'aria-label': value.id === null ? '保存配置并创建快捷启动项' : '保存快捷启动项修改', disabled: busy || value.commandLine.trim() === '', style: primaryButtonStyle }, busy ? '保存中…' : value.id === null ? '保存为快捷启动' : '保存修改'))));
154
172
  }
155
173
  async function saveProfile(value) {
156
174
  await withBusy(async () => {
@@ -171,7 +189,7 @@ function DebugBody({ sessionId, rpc, remote, sidebarRight }) {
171
189
  args: commandParts.slice(1),
172
190
  env: {},
173
191
  shell,
174
- runtimeType: runtimeTypeFor(terminalStatus?.platform, shell.profileId),
192
+ runtimeType: runtimeTypeFor(terminalStatus, shell.profileId),
175
193
  port,
176
194
  proxy: { enabled: value.proxyEnabled },
177
195
  };
@@ -331,6 +349,8 @@ const selectFieldStyle = { ...fieldStyle };
331
349
  const fieldLabelStyle = { color: dshThemeColor.labelSecondary, fontSize: 12, fontWeight: 500 };
332
350
  const inputStyle = { ...dshFieldStyle, width: '100%', boxSizing: 'border-box', minHeight: 32, padding: '6px 8px', borderRadius: 6, fontSize: 12 };
333
351
  const runtimeHintStyle = { margin: '-5px 0 0', color: dshThemeColor.labelTertiary, fontSize: 11 };
352
+ const warningHintStyle = { margin: '-5px 0 0', color: dshThemeColor.error, fontSize: 11 };
353
+ const warningStatusStyle = { ...statusStyle('error'), marginBottom: 0 };
334
354
  const formGridStyle = { display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(220px, 1fr))', gap: 14 };
335
355
  const proxyFieldStyle = { ...fieldStyle };
336
356
  const proxyToggleStyle = { display: 'flex', alignItems: 'center', justifyContent: 'space-between', height: 32, boxSizing: 'border-box', padding: '0 2px', border: 0, borderRadius: 0, color: dshThemeColor.labelSecondary, background: 'transparent', fontSize: 12, lineHeight: '18px' };
@@ -386,23 +406,33 @@ function shellArgsFor(profileId) {
386
406
  function defaultShellForPlatform(platform) {
387
407
  return platform === 'win32' ? 'powershell' : 'zsh';
388
408
  }
389
- function runtimeTypeFor(platform, shellProfileId) {
390
- if (platform !== 'win32' && platform !== 'darwin' && platform !== 'linux')
391
- throw new Error('Host 平台不支持自动选择终端运行方式');
392
- if (platform !== 'win32')
409
+ function runtimeTypeFor(status, shellProfileId) {
410
+ if (status === null || status === undefined)
411
+ throw new Error('尚未获取 Host 终端能力');
412
+ const runtimeTypes = status.runtimeTypes ?? (status.effectiveEnabled ? ['tmux'] : ['local-pty']);
413
+ if (runtimeTypes.includes('tmux'))
393
414
  return 'tmux';
394
- if (shellProfileId === 'cmd')
415
+ if (status.platform !== 'win32')
416
+ return 'local-pty';
417
+ if (shellProfileId === 'cmd' && runtimeTypes.includes('conpty-cmd'))
395
418
  return 'conpty-cmd';
396
- if (shellProfileId === 'git-bash')
419
+ if (shellProfileId === 'git-bash' && runtimeTypes.includes('conpty-git-bash'))
397
420
  return 'conpty-git-bash';
398
- return 'conpty-powershell';
421
+ if (runtimeTypes.includes('conpty-powershell'))
422
+ return 'conpty-powershell';
423
+ if (runtimeTypes.includes('local-pty'))
424
+ return 'local-pty';
425
+ throw new Error('Host 没有可用的终端 backend');
399
426
  }
400
- function terminalRuntimeLabel(platform) {
401
- if (platform === 'win32')
427
+ function terminalRuntimeLabel(status) {
428
+ const runtimeTypes = status?.runtimeTypes ?? (status?.effectiveEnabled ? ['tmux'] : ['local-pty']);
429
+ if (runtimeTypes.includes('tmux'))
430
+ return status?.platform === 'darwin' ? 'macOS 使用 tmux' : 'Linux 使用 tmux';
431
+ if (runtimeTypes.includes('conpty-powershell') || runtimeTypes.includes('conpty-cmd') || runtimeTypes.includes('conpty-git-bash'))
402
432
  return 'Windows 使用 ConPTY';
403
- if (platform === 'darwin' || platform === 'linux')
404
- return `${platform === 'darwin' ? 'macOS' : 'Linux'} 使用 tmux`;
405
- return '等待 Host 平台状态';
433
+ if (runtimeTypes.includes('local-pty'))
434
+ return '使用 local-pty(进程内 PTY)';
435
+ return '等待 Host 终端能力状态';
406
436
  }
407
437
  function splitCommandLine(value) {
408
438
  const parts = [];
@@ -1,3 +1,10 @@
1
- /** 读取 Host 注入的真实 DSH 版本,并在无法读取时拒绝启用 Client。 */
2
- export declare function assertInjectedDshVersion(): string;
1
+ import type { Context } from '@deepseek-ai/cordis';
2
+ /**
3
+ * 读取 Host 注入的真实 DSH 版本,并在无法读取时拒绝启用 Client。
4
+ *
5
+ * DSH 0.1.7 的设置服务迁移会让旧 Host 启动页注入钩子无法执行;此时
6
+ * `configForms` 是新版 Client 唯一可靠的运行时标志,使用其最低现代版本
7
+ * 作为能力路由版本,避免因为旧注入链失败而误判整个 Client 不兼容。
8
+ */
9
+ export declare function assertInjectedDshVersion(ctx?: Context): string;
3
10
  //# sourceMappingURL=dsh-runtime-version.d.ts.map
@@ -1,11 +1,33 @@
1
1
  import { assertSupportedDshVersion, CODINGNS_DSH_ERROR_CODES, CODINGNS_DSH_VERSION_GLOBAL, CodingNsDshError, } from '../shared/index.js';
2
- /** 读取 Host 注入的真实 DSH 版本,并在无法读取时拒绝启用 Client。 */
3
- export function assertInjectedDshVersion() {
2
+ /**
3
+ * 读取 Host 注入的真实 DSH 版本,并在无法读取时拒绝启用 Client。
4
+ *
5
+ * DSH 0.1.7 的设置服务迁移会让旧 Host 启动页注入钩子无法执行;此时
6
+ * `configForms` 是新版 Client 唯一可靠的运行时标志,使用其最低现代版本
7
+ * 作为能力路由版本,避免因为旧注入链失败而误判整个 Client 不兼容。
8
+ */
9
+ export function assertInjectedDshVersion(ctx) {
4
10
  const value = globalThis[CODINGNS_DSH_VERSION_GLOBAL];
5
11
  if (typeof value !== 'string' || value.trim() === '') {
12
+ if (hasModernConfigForms(ctx)) {
13
+ const modernVersion = '0.1.7-rc.2';
14
+ assertSupportedDshVersion(modernVersion);
15
+ return modernVersion;
16
+ }
6
17
  throw new CodingNsDshError(CODINGNS_DSH_ERROR_CODES.DSH_VERSION_UNSUPPORTED, '无法读取当前 DSH 版本;为避免 Client API 不兼容,已拒绝启用 codingns4dsh');
7
18
  }
8
19
  assertSupportedDshVersion(value);
9
20
  return value;
10
21
  }
22
+ function hasModernConfigForms(ctx) {
23
+ if (ctx === undefined)
24
+ return false;
25
+ try {
26
+ const forms = ctx.get('configForms');
27
+ return typeof forms?.get === 'function';
28
+ }
29
+ catch {
30
+ return false;
31
+ }
32
+ }
11
33
  //# sourceMappingURL=dsh-runtime-version.js.map
@@ -3,5 +3,5 @@ import type { FeaturePanelProps, CodingNsClientFeatureModule } from './types.js'
3
3
  /** 外部 Agent 集成模块。Agent 进程在 Host 运行,浏览器只读取目录和状态。 */
4
4
  export declare const cliAdaptersFeature: CodingNsClientFeatureModule;
5
5
  /** 设置页中的 Agent 列表和详情模态框。 */
6
- export declare function CliAdaptersPanel({ services, enabled }: FeaturePanelProps): ReactElement;
6
+ export declare function CliAdaptersPanel({ services, enabled, notify }: FeaturePanelProps): ReactElement;
7
7
  //# sourceMappingURL=cli-adapters.d.ts.map
@@ -7,7 +7,7 @@ import { registerExternalToolStreamUi } from '../external-tool-stream.js';
7
7
  export const cliAdaptersFeature = {
8
8
  descriptor: {
9
9
  name: 'cliAdapters',
10
- version: '0.1.0',
10
+ version: '0.1.1',
11
11
  enabledByDefault: true,
12
12
  dependencies: [],
13
13
  runtime: 'client',
@@ -33,7 +33,7 @@ export const cliAdaptersFeature = {
33
33
  settingsPanel: CliAdaptersPanel,
34
34
  };
35
35
  /** 设置页中的 Agent 列表和详情模态框。 */
36
- export function CliAdaptersPanel({ services, enabled }) {
36
+ export function CliAdaptersPanel({ services, enabled, notify }) {
37
37
  const t = useCodingNsTranslator(services.locale);
38
38
  const [catalog, setCatalog] = useState([]);
39
39
  const [selected, setSelected] = useState(null);
@@ -44,7 +44,7 @@ export function CliAdaptersPanel({ services, enabled }) {
44
44
  const [sessionsLoading, setSessionsLoading] = useState(false);
45
45
  const [restoringSessionId, setRestoringSessionId] = useState(null);
46
46
  const [archivingSessionId, setArchivingSessionId] = useState(null);
47
- const [message, setMessage] = useState('');
47
+ const [modelsError, setModelsError] = useState('');
48
48
  const disabled = !enabled;
49
49
  useEffect(() => {
50
50
  if (disabled)
@@ -55,7 +55,7 @@ export function CliAdaptersPanel({ services, enabled }) {
55
55
  .then((value) => { if (active)
56
56
  setCatalog(value); })
57
57
  .catch((error) => { if (active)
58
- setMessage(errorMessage(error)); })
58
+ notify({ kind: 'error', message: errorMessage(error) }); })
59
59
  .finally(() => { if (active)
60
60
  setLoading(false); });
61
61
  return () => { active = false; };
@@ -71,7 +71,7 @@ export function CliAdaptersPanel({ services, enabled }) {
71
71
  .then((value) => { if (active)
72
72
  setSessions(value); })
73
73
  .catch((error) => { if (active)
74
- setMessage(errorMessage(error)); })
74
+ notify({ kind: 'error', message: errorMessage(error) }); })
75
75
  .finally(() => { if (active)
76
76
  setSessionsLoading(false); });
77
77
  return () => { active = false; };
@@ -83,26 +83,29 @@ export function CliAdaptersPanel({ services, enabled }) {
83
83
  }
84
84
  let active = true;
85
85
  setModels(null);
86
- setMessage('');
86
+ setModelsError('');
87
87
  void callCliRpc(services.rpc, 'models', { adapterId: selected.id })
88
88
  .then((value) => { if (active)
89
89
  setModels(value); })
90
- .catch((error) => { if (active)
91
- setMessage(errorMessage(error)); });
90
+ .catch((error) => { if (active) {
91
+ const message = errorMessage(error);
92
+ setModelsError(message);
93
+ notify({ kind: 'error', message });
94
+ } });
92
95
  return () => { active = false; };
93
96
  }, [disabled, selected, services.rpc]);
94
97
  const rowStyle = dshSettingsListRowStyle;
95
98
  const buttonStyle = { ...dshSettingsButtonStyle, cursor: disabled ? 'not-allowed' : 'pointer' };
96
99
  const toggleAdapter = async (adapter, next) => {
97
100
  setBusyAdapterId(adapter.id);
98
- setMessage('');
99
101
  try {
100
102
  await callCliRpc(services.rpc, 'adapter/set', { adapterId: adapter.id, enabled: next });
101
103
  const refreshed = await callCliRpc(services.rpc, 'catalog', {});
102
104
  setCatalog(refreshed);
105
+ notify({ kind: 'success', message: next ? `已启用 ${adapter.name}` : `已停用 ${adapter.name}` });
103
106
  }
104
107
  catch (error) {
105
- setMessage(errorMessage(error));
108
+ notify({ kind: 'error', message: errorMessage(error) });
106
109
  }
107
110
  finally {
108
111
  setBusyAdapterId(null);
@@ -110,12 +113,12 @@ export function CliAdaptersPanel({ services, enabled }) {
110
113
  };
111
114
  const restoreSession = async (record) => {
112
115
  setRestoringSessionId(record.dshSessionId);
113
- setMessage('');
114
116
  try {
115
117
  await restoreCliSession(services.rpc, record);
118
+ notify({ kind: 'success', message: `已打开 ${record.title ?? record.adapterId} 会话` });
116
119
  }
117
120
  catch (error) {
118
- setMessage(errorMessage(error));
121
+ notify({ kind: 'error', message: errorMessage(error) });
119
122
  }
120
123
  finally {
121
124
  setRestoringSessionId(null);
@@ -123,13 +126,13 @@ export function CliAdaptersPanel({ services, enabled }) {
123
126
  };
124
127
  const archiveSession = async (record) => {
125
128
  setArchivingSessionId(record.dshSessionId);
126
- setMessage('');
127
129
  try {
128
130
  await archiveCliSession(services.rpc, record.dshSessionId);
129
131
  setSessions((current) => current.filter((item) => item.dshSessionId !== record.dshSessionId));
132
+ notify({ kind: 'success', message: `已移除 ${record.title ?? record.adapterId} 会话` });
130
133
  }
131
134
  catch (error) {
132
- setMessage(errorMessage(error));
135
+ notify({ kind: 'error', message: errorMessage(error) });
133
136
  }
134
137
  finally {
135
138
  setArchivingSessionId(null);
@@ -148,10 +151,10 @@ export function CliAdaptersPanel({ services, enabled }) {
148
151
  onRestore: (record) => { void restoreSession(record); },
149
152
  onArchive: (record) => { void archiveSession(record); },
150
153
  t,
151
- }), message && createElement('div', { role: 'alert', style: { marginTop: 10, color: dshThemeColor.error } }, message), selected !== null && createElement(AdapterDetailsDialog, {
154
+ }), selected !== null && createElement(AdapterDetailsDialog, {
152
155
  adapter: selected,
153
156
  models,
154
- loading: selected.installed && selected.enabled && models === null && !message,
157
+ loading: selected.installed && selected.enabled && models === null && modelsError === '',
155
158
  onClose: () => setSelected(null),
156
159
  buttonStyle,
157
160
  t,
@@ -3,7 +3,7 @@ import { registerDebugUi } from '../debug/ui.js';
3
3
  export const debugFeature = {
4
4
  descriptor: {
5
5
  name: 'debug',
6
- version: '0.1.0',
6
+ version: '0.1.1',
7
7
  enabledByDefault: true,
8
8
  dependencies: [],
9
9
  runtime: 'client',
@@ -18,7 +18,7 @@ export const debugFeature = {
18
18
  const uiContext = context.services.uiContext;
19
19
  if (uiContext === undefined)
20
20
  throw new Error('工作区调试模块缺少 DSH UI 上下文');
21
- context.resources.add(registerDebugUi(uiContext, context.services.rpc, context.services.remote));
21
+ context.resources.add(registerDebugUi(uiContext, context.services.rpc, context.services.remote, context.services.terminalRemote));
22
22
  },
23
23
  };
24
24
  //# sourceMappingURL=debug.js.map
@@ -7,6 +7,7 @@ import { cliAdaptersFeature } from './cli-adapters.js';
7
7
  import { terminalEnhancementFeature } from './terminal-enhancement.js';
8
8
  import { workspaceSessionEnhancementFeature } from './workspace-session-enhancement.js';
9
9
  import { debugFeature } from './debug.js';
10
+ import { gitManagementFeature } from '../git-management.js';
10
11
  import type { CodingNsClientFeatureModule, CodingNsClientServices } from './types.js';
11
12
  /** Client 侧功能模块清单:新增模块在这里登记一行,不需要改动设置页和入口。 */
12
13
  export declare const CLIENT_FEATURES: readonly CodingNsClientFeatureModule[];
@@ -21,7 +22,7 @@ export interface CodingNsSettingsModule {
21
22
  * 归属 Host 的模块由 Host 侧配置承载,这里跳过;没有 ui 描述的模块不显示。
22
23
  */
23
24
  export declare function settingsModules(registry: FeatureRegistry<CodingNsClientServices, CodingNsClientFeatureModule>): readonly CodingNsSettingsModule[];
24
- export { lanAccessFeature, loginProtectionFeature, reverseProxyFeature, cliAdaptersFeature, workspaceSessionEnhancementFeature, terminalEnhancementFeature, debugFeature };
25
+ export { lanAccessFeature, loginProtectionFeature, reverseProxyFeature, cliAdaptersFeature, workspaceSessionEnhancementFeature, terminalEnhancementFeature, debugFeature, gitManagementFeature };
25
26
  export { startBrowserRelayConnection } from './reverse-proxy.js';
26
27
  export { LanAccessPanel } from './lan-access-panel.js';
27
28
  export { LoginProtectionPanel } from './login-protection-panel.js';