@jingyi0605/codingns4dsh 0.1.0 → 0.1.2

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 (213) 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/features/auth.js +8 -5
  149. package/data/build/dist/host/features/debug.js +1 -1
  150. package/data/build/dist/host/features/git-management.d.ts +5 -0
  151. package/data/build/dist/host/features/git-management.js +290 -0
  152. package/data/build/dist/host/features/host-status.js +1 -1
  153. package/data/build/dist/host/features/index.d.ts +1 -0
  154. package/data/build/dist/host/features/index.js +3 -0
  155. package/data/build/dist/host/features/terminal-process.js +1 -1
  156. package/data/build/dist/host/features/terminal-status.d.ts +3 -1
  157. package/data/build/dist/host/features/terminal-status.js +3 -1
  158. package/data/build/dist/host/index.d.ts +7 -3
  159. package/data/build/dist/host/index.js +76 -11
  160. package/data/build/dist/host/lan-access-dsh.js +18 -5
  161. package/data/build/dist/host/native-session-bridge.d.ts +23 -1
  162. package/data/build/dist/host/native-session-bridge.js +157 -10
  163. package/data/build/dist/host/rpc-table.d.ts +1 -1
  164. package/data/build/dist/host/rpc.js +60 -10
  165. package/data/build/dist/host/session-migration-repair.d.ts +19 -0
  166. package/data/build/dist/host/session-migration-repair.js +395 -0
  167. package/data/build/dist/host/settings.d.ts +8 -1
  168. package/data/build/dist/host/settings.js +94 -3
  169. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
  170. package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
  171. package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
  172. package/data/build/dist/host/terminal/controller-factory.js +26 -7
  173. package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
  174. package/data/build/dist/host/terminal/runtime-manager.js +3 -0
  175. package/data/build/dist/host/terminal/startup.d.ts +2 -0
  176. package/data/build/dist/host/terminal/startup.js +2 -1
  177. package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
  178. package/data/build/dist/host/terminal/terminal-controller.js +101 -15
  179. package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
  180. package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
  181. package/data/build/dist/host/terminal/terminal-service.js +10 -0
  182. package/data/build/dist/index.d.ts +6 -2
  183. package/data/build/dist/index.js +8 -2
  184. package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
  185. package/data/build/dist/shared/contracts/config.d.ts +31 -1
  186. package/data/build/dist/shared/contracts/config.js +25 -0
  187. package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
  188. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  189. package/data/build/dist/shared/contracts/feature.d.ts +6 -0
  190. package/data/build/dist/shared/contracts/git.d.ts +82 -0
  191. package/data/build/dist/shared/contracts/git.js +3 -0
  192. package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
  193. package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
  194. package/data/build/dist/shared/contracts/version.d.ts +2 -2
  195. package/data/build/dist/shared/contracts/version.js +4 -6
  196. package/data/build/dist/shared/debug.d.ts +14 -0
  197. package/data/build/dist/shared/debug.js +53 -0
  198. package/data/build/dist/shared/index.d.ts +7 -4
  199. package/data/build/dist/shared/index.js +2 -1
  200. package/data/build/dist/transport/debug.d.ts +1 -1
  201. package/data/build/dist/transport/debug.js +3 -30
  202. package/data/build/dist/transport/dsh-session.js +3 -3
  203. package/data/build/dist/typert.host.js +4 -2
  204. package/data/build/dist/typert.remote-client.d.ts +11 -0
  205. package/data/build/dist/typert.remote-client.js +7 -0
  206. package/dsh.bundle.patch +1 -1
  207. package/package.json +17 -5
  208. package/profile/README.md +4 -4
  209. package/profile/package.json +6 -4
  210. package/profile/scripts/check-dsh-install.mjs +2 -2
  211. package/profile/version.json +2 -2
  212. package/scripts/check-dsh-install.mjs +2 -2
  213. package/version.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { CODINGNS_RPC_CHANNEL } from '../shared/contracts/transport.js';
2
+ import { debugInfo, debugWarn } from '../shared/debug.js';
2
3
  /**
3
4
  * 把 DSH 本地设置和远程 Host 设置 RPC 统一成一个设置作用域。
4
5
  * 非回环页面使用 RPC,回环页面完全复用 DSH 的原生设置传输。
@@ -14,14 +15,14 @@ export class CodingNsSettingsBridge {
14
15
  constructor(local, rpc) {
15
16
  this.local = local;
16
17
  this.rpc = rpc;
17
- this.snapshot = local.getSnapshot();
18
+ this.snapshot = toStoreSnapshot(local.getSnapshot());
18
19
  this.localUnsubscribe = local.subscribe(() => {
19
20
  if (this.isRemote()) {
20
21
  void this.load().catch(() => undefined);
21
22
  return;
22
23
  }
23
24
  this.remoteLoaded = false;
24
- this.publish(local.getSnapshot());
25
+ this.publish(toStoreSnapshot(local.getSnapshot()));
25
26
  });
26
27
  }
27
28
  getSnapshot() { return this.snapshot; }
@@ -36,35 +37,40 @@ export class CodingNsSettingsBridge {
36
37
  return;
37
38
  if (this.remoteLoad !== undefined)
38
39
  return this.remoteLoad;
40
+ debugInfo('codingns4dsh: client settings load begin');
39
41
  this.remoteLoad = this.call('settings/get', {}).then((response) => {
40
42
  this.remoteLoaded = true;
41
43
  this.publish({
42
44
  status: 'ready',
43
45
  value: response.value,
44
- base: undefined,
45
- user: undefined,
46
46
  revision: response.revision,
47
47
  writable: true,
48
- mode: 'host',
49
48
  });
49
+ debugInfo('codingns4dsh: client settings load success', { revision: response.revision });
50
50
  }).finally(() => {
51
51
  this.remoteLoad = undefined;
52
52
  });
53
53
  return this.remoteLoad;
54
54
  }
55
55
  async set(field, value) {
56
- if (!this.isRemote())
57
- return this.local.set(field, value);
56
+ if (!this.isRemote()) {
57
+ await this.local.set(field, value);
58
+ return true;
59
+ }
58
60
  return this.mutate([{ op: 'set', path: [field], value: toJsonValue(value) }]);
59
61
  }
60
62
  async unset(field) {
61
- if (!this.isRemote())
62
- return this.local.unset(field);
63
+ if (!this.isRemote()) {
64
+ await this.local.unset(field);
65
+ return true;
66
+ }
63
67
  return this.mutate([{ op: 'unset', path: [field] }]);
64
68
  }
65
69
  async mutate(ops, expectedRevision) {
66
- if (!this.isRemote())
67
- return this.local.mutate(ops, expectedRevision);
70
+ if (!this.isRemote()) {
71
+ await this.local.mutate(ops, expectedRevision);
72
+ return true;
73
+ }
68
74
  const payload = expectedRevision === undefined ? { ops } : { ops, expectedRevision };
69
75
  const response = await this.call('settings/set', payload);
70
76
  this.remoteLoaded = true;
@@ -74,26 +80,12 @@ export class CodingNsSettingsBridge {
74
80
  value: response.value,
75
81
  revision: response.revision,
76
82
  writable: true,
77
- mode: 'host',
78
83
  });
84
+ return true;
79
85
  }
80
86
  dispose() { this.localUnsubscribe(); }
81
87
  async call(endpoint, payload) {
82
- let result;
83
- try {
84
- result = await this.rpc.call(CODINGNS_RPC_CHANNEL, endpoint, payload);
85
- }
86
- catch (error) {
87
- // DSH 原生连接通常把自定义 RPC 映射到 /api;保留逻辑通道兼容
88
- // Codingns4DSH Transport,同时在普通 Web Host 上回退到实际 Fetch 路由。
89
- const message = error instanceof Error ? error.message : String(error);
90
- if (!/HTTP (?:404|405)\b/u.test(message))
91
- throw error;
92
- result = await this.rpc.call('/api', `codingns/${endpoint}`, payload);
93
- }
94
- if (!result.ok)
95
- throw new Error(result.error.message);
96
- return result.value;
88
+ return callCodingNsRpc(this.rpc, endpoint, payload);
97
89
  }
98
90
  publish(next) {
99
91
  this.snapshot = next;
@@ -105,6 +97,35 @@ export class CodingNsSettingsBridge {
105
97
  return snapshot.mode === 'memory' || snapshot.status === 'unavailable';
106
98
  }
107
99
  }
100
+ /** 调用 Codingns4DSH Host RPC,并兼容 DSH 原生连接与普通 Web API 回退。 */
101
+ export async function callCodingNsRpc(rpc, endpoint, payload) {
102
+ let result;
103
+ try {
104
+ debugInfo('codingns4dsh: client rpc request', { channel: CODINGNS_RPC_CHANNEL, endpoint });
105
+ result = await rpc.call(CODINGNS_RPC_CHANNEL, endpoint, payload);
106
+ }
107
+ catch (error) {
108
+ // DSH 原生连接通常把自定义 RPC 映射到 /api;保留逻辑通道兼容
109
+ // Codingns4DSH Transport,同时在普通 Web Host 上回退到实际 Fetch 路由。
110
+ const message = error instanceof Error ? error.message : String(error);
111
+ if (!/HTTP (?:404|405)\b/u.test(message))
112
+ throw error;
113
+ debugWarn('codingns4dsh: client rpc fallback', { endpoint, error: message });
114
+ try {
115
+ result = await rpc.call('/api', `codingns/${endpoint}`, payload);
116
+ }
117
+ catch (fallbackError) {
118
+ console.error('codingns4dsh: client rpc fallback failed', { endpoint, error: fallbackError });
119
+ throw fallbackError;
120
+ }
121
+ }
122
+ if (!result.ok) {
123
+ console.error('codingns4dsh: client rpc response error', { endpoint, error: result.error });
124
+ throw new Error(result.error.message);
125
+ }
126
+ debugInfo('codingns4dsh: client rpc response success', { endpoint });
127
+ return result.value;
128
+ }
108
129
  /** 设置 RPC 只能传 JSON;在浏览器边界尽早拒绝函数、循环引用等无效值。 */
109
130
  function toJsonValue(value) {
110
131
  if (value === null || typeof value === 'string' || typeof value === 'boolean')
@@ -123,4 +144,7 @@ function toJsonValue(value) {
123
144
  export function createCodingNsSettingsBridge(local, rpc) {
124
145
  return new CodingNsSettingsBridge(local, rpc);
125
146
  }
147
+ function toStoreSnapshot(snapshot) {
148
+ return { value: snapshot.value, revision: snapshot.revision, writable: snapshot.writable, status: snapshot.status };
149
+ }
126
150
  //# sourceMappingURL=settings-bridge.js.map
@@ -1,9 +1,10 @@
1
1
  import type { ReactElement } from 'react';
2
2
  import type { PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
3
- import type { SettingsScope, SettingsSectionOwnerProps } from '@deepseek-ai/dsh-client-ui-settings/client';
3
+ import type { SettingsSectionOwnerProps } from '@deepseek-ai/dsh-client-ui-settings/client';
4
4
  import type { FeatureRegistry } from '../features/registry.js';
5
5
  import { type RestartFeatureStates, type CodingNsSettings } from '../shared/contracts/config.js';
6
6
  import type { CodingNsClientFeatureModule, CodingNsClientServices } from './features/types.js';
7
+ import type { CodingNsSettingsStore } from '../dsh-capabilities/settings-store.js';
7
8
  declare module '@deepseek-ai/dsh-client-ui-slots' {
8
9
  interface SlotMap {
9
10
  'settings.section': {
@@ -14,7 +15,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
14
15
  }
15
16
  }
16
17
  export interface CodingNsSectionProps extends PropsRuntime<'settings.section'> {
17
- readonly settings: SettingsScope<CodingNsSettings>;
18
+ readonly settings: CodingNsSettingsStore<CodingNsSettings>;
18
19
  readonly registry: FeatureRegistry<CodingNsClientServices, CodingNsClientFeatureModule>;
19
20
  readonly services: CodingNsClientServices;
20
21
  /** 当前 Client 进程启动时捕获的重启生效模块状态。 */
@@ -1,9 +1,10 @@
1
- import { createElement, useState, useSyncExternalStore } from 'react';
1
+ import { createElement, useEffect, useState, useSyncExternalStore } from 'react';
2
2
  import { CODINGNS_MODULES_FIELD, isFeatureEnabled, isFeatureDshVersionCompatible, } from '../shared/contracts/config.js';
3
3
  import { settingsModules } from './features/index.js';
4
- import { dshSettingsBodyStyle, dshSettingsCardStyle, dshSettingsHeaderStyle, dshSettingsPageStyle, dshSettingsSubtitleStyle, dshSettingsSummaryDescriptionStyle, dshSettingsSummaryLabelStyle, dshSettingsSummaryStyle, dshSettingsSummaryTextStyle, dshSettingsTitleStyle, dshThemeColor, } from './theme.js';
4
+ import { dshSettingsBodyStyle, dshSettingsCardStyle, dshSettingsHeaderStyle, dshSettingsPageStyle, dshSettingsSubtitleStyle, dshSettingsSummaryDescriptionStyle, dshSettingsSummaryLabelStyle, dshSettingsSummaryStyle, dshSettingsSummaryTextStyle, dshSettingsTitleStyle, dshSettingsToastStyle, dshThemeColor, } from './theme.js';
5
5
  import { useCodingNsTranslator } from './locale.js';
6
- import { isLegacyDshVersion } from '../shared/contracts/version.js';
6
+ import { CODINGNS_VERSION, DSH_COMPATIBILITY, isLegacyDshVersion } from '../shared/contracts/version.js';
7
+ const CODINGNS_GITHUB_URL = 'https://github.com/jingyi0605/Codingns4DSH';
7
8
  /**
8
9
  * DSH 设置页中的 Codingns4DSH 区块。
9
10
  *
@@ -13,41 +14,56 @@ import { isLegacyDshVersion } from '../shared/contracts/version.js';
13
14
  export function CodingNsSettingsSection({ settings, registry, services, restartStates = {} }) {
14
15
  const snapshot = useSyncExternalStore((listener) => settings.subscribe(listener), () => settings.getSnapshot(), () => settings.getSnapshot());
15
16
  const t = useCodingNsTranslator(services.locale);
16
- return createElement('section', { style: dshSettingsPageStyle }, createElement('header', { style: dshSettingsHeaderStyle }, createElement('h2', { style: dshSettingsTitleStyle }, t('settings.title')), createElement('p', { style: dshSettingsSubtitleStyle }, t('settings.subtitle'))), createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, settingsModules(registry).map((entry) => createElement(FeatureCard, {
17
+ const [toast, setToast] = useState(null);
18
+ useEffect(() => {
19
+ if (toast === null)
20
+ return;
21
+ const timer = globalThis.setTimeout(() => setToast(null), 3200);
22
+ return () => globalThis.clearTimeout(timer);
23
+ }, [toast]);
24
+ const notify = (notice) => setToast(notice);
25
+ return createElement('section', { style: dshSettingsPageStyle }, toast === null ? null : createElement('div', {
26
+ role: toast.kind === 'error' ? 'alert' : 'status',
27
+ 'aria-live': 'polite',
28
+ style: { ...dshSettingsToastStyle, borderColor: toast.kind === 'error' ? dshThemeColor.error : toast.kind === 'success' ? dshThemeColor.success : dshThemeColor.border },
29
+ }, toast.message), createElement('header', { style: dshSettingsHeaderStyle }, createElement('h2', { style: dshSettingsTitleStyle }, t('settings.title')), createElement('p', { style: dshSettingsSubtitleStyle }, t('settings.subtitle'))), createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 12 } }, settingsModules(registry).map((entry) => createElement(FeatureCard, {
17
30
  key: entry.module.descriptor.name,
18
31
  entry,
19
32
  snapshot,
20
33
  services,
21
34
  restartStates,
22
- }))));
35
+ notify,
36
+ }))), createElement('details', { style: { alignSelf: 'center', display: 'flex', flexDirection: 'column-reverse', alignItems: 'center', marginTop: 4, color: dshThemeColor.labelTertiary, textAlign: 'center', fontSize: 12, lineHeight: 1.5 } }, createElement('summary', { style: { cursor: 'pointer', color: dshThemeColor.labelSecondary, listStylePosition: 'inside' } }, t('settings.version', { version: CODINGNS_VERSION })), createElement('div', { style: { display: 'flex', flexDirection: 'column', gap: 4, alignItems: 'center', maxWidth: 'min(100%, 560px)', marginBottom: 8, padding: '8px 12px', border: `1px solid ${dshThemeColor.border}`, borderRadius: 6, background: dshThemeColor.surfaceSubtle } }, createElement('div', undefined, t('settings.compatibility', { range: DSH_COMPATIBILITY })), createElement('a', { href: CODINGNS_GITHUB_URL, target: '_blank', rel: 'noreferrer', style: { color: dshThemeColor.accent, overflowWrap: 'anywhere' } }, CODINGNS_GITHUB_URL))));
23
37
  }
24
38
  /** 通用功能模块卡片:标题栏开关由 descriptor.ui 决定,内容由模块自己提供。 */
25
- function FeatureCard({ entry, snapshot, services, restartStates }) {
39
+ function FeatureCard({ entry, snapshot, services, restartStates, notify }) {
26
40
  const { module, ui } = entry;
27
41
  const t = useCodingNsTranslator(services.locale);
28
- const [writeError, setWriteError] = useState(null);
29
42
  const versionCompatible = isFeatureDshVersionCompatible(module.descriptor, services.dshVersion);
30
43
  const requestedEnabled = isFeatureEnabled(module.descriptor, snapshot.value);
31
44
  const enabled = versionCompatible && requestedEnabled;
32
- const effectiveEnabled = module.descriptor.activation === 'restart'
33
- ? versionCompatible && (restartStates[module.descriptor.name] ?? module.descriptor.enabledByDefault)
34
- : enabled;
35
45
  const panel = module.settingsPanel;
36
46
  // 常驻模块不提供关闭入口;设置未就绪或只读时也不允许切换。
37
47
  const switchDisabled = ui.alwaysEnabled === true || !versionCompatible || snapshot.status === 'loading' || !snapshot.writable;
38
48
  const toggle = (next) => {
39
- setWriteError(null);
40
49
  if (!versionCompatible) {
41
- setWriteError(t('settings.versionBlocked', {
42
- version: services.dshVersion,
43
- minimum: module.descriptor.minimumDshVersion ?? '未知版本',
44
- }));
50
+ notify({ kind: 'error', message: t('settings.versionBlocked', {
51
+ version: services.dshVersion,
52
+ minimum: module.descriptor.minimumDshVersion ?? '未知版本',
53
+ }) });
45
54
  return;
46
55
  }
47
56
  void services.settings
48
57
  .mutate([{ op: 'set', path: [CODINGNS_MODULES_FIELD, module.descriptor.name], value: next }])
58
+ .then((accepted) => {
59
+ if (!accepted) {
60
+ notify({ kind: 'error', message: t('settings.moduleWriteRejected') });
61
+ return;
62
+ }
63
+ notify({ kind: 'success', message: t(next ? 'settings.moduleEnabled' : 'settings.moduleDisabled', { label: t(ui.labelKey ?? ui.label) }) });
64
+ })
49
65
  .catch((cause) => {
50
- setWriteError(cause instanceof Error ? cause.message : String(cause));
66
+ notify({ kind: 'error', message: cause instanceof Error ? cause.message : String(cause) });
51
67
  });
52
68
  };
53
69
  return createElement('details', {
@@ -67,9 +83,7 @@ function FeatureCard({ entry, snapshot, services, restartStates }) {
67
83
  version: services.dshVersion,
68
84
  minimum: module.descriptor.minimumDshVersion ?? '未知版本',
69
85
  }))
70
- : null, module.descriptor.activation !== 'restart' ? null : createElement('div', { role: 'status', style: { display: 'flex', flexDirection: 'column', gap: 4, padding: 10, border: `1px solid ${dshThemeColor.border}`, borderRadius: 6, fontSize: 13 } }, createElement('span', undefined, t('settings.restartTarget', { state: enabled ? t('settings.enabled') : t('settings.disabled') })), effectiveEnabled === enabled
71
- ? createElement('span', undefined, t('settings.runtimeReported'))
72
- : createElement('strong', undefined, t('settings.restartRequired'))), panel === undefined ? null : createElement(panel, { services, enabled, snapshot }), writeError === null ? null : createElement('div', { role: 'alert', style: { color: dshThemeColor.error } }, writeError)));
86
+ : null, panel === undefined ? null : createElement(panel, { services, enabled, snapshot, notify })));
73
87
  }
74
88
  /** 标题栏开关:真实 checkbox 语义,点击不会连带折叠卡片。 */
75
89
  function FeatureSwitch({ label, checked, disabled, onChange }) {
@@ -1,14 +1,17 @@
1
1
  import type { ReactElement } from 'react';
2
2
  import type { SlotRegistry } from '@deepseek-ai/dsh-client-ui-renderer/client';
3
3
  import type { CodingNsRpcClient } from './features/types.js';
4
+ import type { SessionSnapshot } from './cli-slots.js';
4
5
  declare module '@deepseek-ai/dsh-client-ui-slots' {
5
6
  interface SessionStandardProps {
6
7
  sessionId: string;
7
8
  }
8
9
  }
10
+ type SessionSelector = <Selected>(selector: (value: SessionSnapshot) => Selected) => Selected;
9
11
  interface SubscriptionSlotProps {
10
12
  readonly rpc: CodingNsRpcClient;
11
13
  readonly sessionId?: string;
14
+ readonly useSession?: SessionSelector;
12
15
  }
13
16
  /** 在 DSH 原生步骤统计左侧显示当前 Agent 的订阅余量。 */
14
17
  export declare function registerSubscriptionSlot(slots: SlotRegistry, rpc: CodingNsRpcClient): () => void;
@@ -1,5 +1,6 @@
1
1
  import { createElement, useEffect, useRef, useState } from 'react';
2
2
  import { callCliRpc } from './cli-catalog.js';
3
+ import { providerIconUrl } from './provider-icons.js';
3
4
  import { dshPopupSurfaceStyle, dshThemeColor } from './theme.js';
4
5
  /** 在 DSH 原生步骤统计左侧显示当前 Agent 的订阅余量。 */
5
6
  export function registerSubscriptionSlot(slots, rpc) {
@@ -14,17 +15,20 @@ export function registerSubscriptionSlot(slots, rpc) {
14
15
  function CommandCodeSubscriptionSlot(props) {
15
16
  const [usage, setUsage] = useState(null);
16
17
  const [adapterId, setAdapterId] = useState(null);
18
+ const [providerId, setProviderId] = useState(null);
17
19
  const [eligible, setEligible] = useState(false);
18
20
  const [open, setOpen] = useState(false);
19
21
  const [loading, setLoading] = useState(false);
20
22
  const [clock, setClock] = useState(() => Date.now());
21
23
  const rootRef = useRef(null);
24
+ const modelSelectionRevision = props.useSession?.((value) => JSON.stringify(value.modelSelection));
22
25
  useEffect(() => {
23
26
  const sessionId = props.sessionId?.trim();
24
27
  if (!sessionId) {
25
28
  setEligible(false);
26
29
  setUsage(null);
27
30
  setAdapterId(null);
31
+ setProviderId(null);
28
32
  setOpen(false);
29
33
  return;
30
34
  }
@@ -32,6 +36,7 @@ function CommandCodeSubscriptionSlot(props) {
32
36
  setEligible(false);
33
37
  setUsage(null);
34
38
  setAdapterId(null);
39
+ setProviderId(null);
35
40
  setOpen(false);
36
41
  const refresh = async () => {
37
42
  setLoading(true);
@@ -43,14 +48,19 @@ function CommandCodeSubscriptionSlot(props) {
43
48
  setEligible(false);
44
49
  setUsage(null);
45
50
  setAdapterId(null);
51
+ setProviderId(null);
46
52
  }
47
53
  return;
48
54
  }
49
55
  if (active) {
50
56
  setEligible(true);
51
57
  setAdapterId(adapterId);
58
+ setProviderId(selection.providerId ?? null);
52
59
  }
53
- const next = await callCliRpc(props.rpc, 'subscription', { adapterId });
60
+ const next = await callCliRpc(props.rpc, 'subscription', {
61
+ adapterId,
62
+ ...(selection.providerId ? { providerId: selection.providerId } : {}),
63
+ });
54
64
  if (active)
55
65
  setUsage(next);
56
66
  }
@@ -71,7 +81,7 @@ function CommandCodeSubscriptionSlot(props) {
71
81
  active = false;
72
82
  globalThis.clearInterval(timer);
73
83
  };
74
- }, [props.rpc, props.sessionId]);
84
+ }, [props.rpc, props.sessionId, modelSelectionRevision]);
75
85
  useEffect(() => {
76
86
  if (!eligible || usage === null)
77
87
  return;
@@ -98,17 +108,33 @@ function CommandCodeSubscriptionSlot(props) {
98
108
  };
99
109
  }, [open]);
100
110
  // 未拿到真实订阅数据时不占用底部栏空间;加载状态不能伪装成订阅存在。
101
- if (!eligible || usage === null || (usage.sub2api === undefined && resolveDisplayWindow(usage) === null))
111
+ if (!eligible || usage === null || (usage.sub2api === undefined && usage.deepseek === undefined && usage.providerBalance === undefined && resolveDisplayWindow(usage) === null))
102
112
  return null;
103
113
  const sub2api = usage.sub2api;
104
- const displayWindow = sub2api === undefined ? resolveDisplayWindow(usage) : null;
114
+ const deepseek = usage.deepseek;
115
+ const providerBalance = usage.providerBalance;
116
+ const displayWindow = sub2api === undefined && deepseek === undefined && providerBalance === undefined ? resolveDisplayWindow(usage) : null;
105
117
  const remaining = displayWindow?.remainingPercent ?? null;
106
118
  const resetLabel = displayWindow === null ? null : formatCountdown(displayWindow.resetsAt, clock);
107
- const providerName = subscriptionProviderName(adapterId);
108
- const label = sub2api === undefined
119
+ const providerName = subscriptionProviderName(adapterId, providerId, usage);
120
+ const deepseekBalance = deepseek === undefined ? null : selectDeepseekBalance(deepseek);
121
+ const providerLogoSource = usage.provider?.logoDataUrl ?? (isRemoteWebContext() ? '' : usage.provider?.logoUrl ?? '');
122
+ const deepseekIconSource = providerLogoSource || (providerBalance === undefined ? providerIconUrl('dsh') : '');
123
+ const label = sub2api === undefined && deepseek === undefined && providerBalance === undefined
109
124
  ? `${providerName} 订阅余量 ${formatPercent(remaining ?? 0)}%`
110
- : `${providerName} 上游余额 ${formatSub2ApiMoney(sub2api.balance, sub2api.unit)}`;
111
- const logoSource = sub2api === undefined ? '' : (sub2api.logoDataUrl ?? (isRemoteWebContext() ? '' : sub2api.logoUrl));
125
+ : sub2api !== undefined
126
+ ? `${providerName} 上游余额 ${formatSub2ApiMoney(sub2api.balance, sub2api.unit)}`
127
+ : deepseek !== undefined
128
+ ? `${providerName} 余额 ${deepseekBalance === null ? '不可用' : formatDeepseekMoney(deepseekBalance.totalBalance, deepseekBalance.currency)}`
129
+ : `${providerName} 余量 ${formatProviderBalance(providerBalance)}`;
130
+ const logoSource = providerLogoSource || (sub2api === undefined ? '' : (sub2api.logoDataUrl ?? (isRemoteWebContext() ? '' : sub2api.logoUrl)));
131
+ const triggerContent = sub2api === undefined && deepseek === undefined && providerBalance === undefined
132
+ ? createElement('span', { 'aria-hidden': true, style: progressRingStyle() }, createElement('span', { style: { ...progressRingVisualStyle, background: progressRingVisualBackground(remaining === null ? 0 : remaining / 100, false) } }, createElement('span', { style: progressRingValueStyle }, createElement('span', undefined, formatRingPercentage(remaining ?? 0)), createElement('span', { style: progressRingSuffixStyle }, '%'))))
133
+ : sub2api !== undefined
134
+ ? createElement('span', { 'aria-hidden': true, style: sub2apiIdentityStyle }, logoSource !== '' && createElement('img', { src: logoSource, alt: '', width: 20, height: 20, style: sub2apiLogoStyle }), createElement('span', undefined, formatSub2ApiMoney(sub2api.balance, sub2api.unit)))
135
+ : deepseek !== undefined
136
+ ? createElement('span', { 'aria-hidden': true, style: deepseekBalanceIdentityStyle }, deepseekIconSource !== undefined && createElement('img', { src: deepseekIconSource, alt: '', width: 18, height: 18, style: deepseekLogoStyle }), createElement('span', { style: deepseekBalanceStyle }, deepseekBalance === null ? '--' : formatDeepseekMoney(deepseekBalance.totalBalance, deepseekBalance.currency)))
137
+ : createElement('span', { 'aria-hidden': true, style: deepseekBalanceIdentityStyle }, deepseekIconSource !== undefined && createElement('img', { src: deepseekIconSource, alt: '', width: 18, height: 18, style: deepseekLogoStyle }), createElement('span', { style: deepseekBalanceStyle }, formatProviderBalance(providerBalance)));
112
138
  return createElement('div', { ref: rootRef, style: subscriptionRootStyle }, createElement('button', {
113
139
  type: 'button',
114
140
  onClick: () => setOpen((value) => !value),
@@ -116,19 +142,42 @@ function CommandCodeSubscriptionSlot(props) {
116
142
  'aria-label': label,
117
143
  'aria-expanded': open,
118
144
  style: subscriptionTriggerStyle,
119
- }, sub2api === undefined
120
- ? createElement('span', { 'aria-hidden': true, style: progressRingStyle() }, createElement('span', { style: { ...progressRingVisualStyle, background: progressRingVisualBackground(remaining === null ? 0 : remaining / 100, false) } }, createElement('span', { style: progressRingValueStyle }, createElement('span', undefined, formatRingPercentage(remaining ?? 0)), createElement('span', { style: progressRingSuffixStyle }, '%'))))
121
- : createElement('span', { 'aria-hidden': true, style: sub2apiIdentityStyle }, logoSource !== '' && createElement('img', { src: logoSource, alt: '', width: 20, height: 20, style: sub2apiLogoStyle }), createElement('span', undefined, formatSub2ApiMoney(sub2api.balance, sub2api.unit))), createElement('span', { className: 'bOPqQW_label', style: subscriptionLabelStyle }, sub2api === undefined ? (resetLabel ?? '订阅余量') : `今日 ${formatSub2ApiMoney(sub2api.today.cost, sub2api.unit)}`)), open && createElement(SubscriptionPopover, { usage, providerName }));
145
+ }, triggerContent, createElement('span', { style: subscriptionLabelStyle }, sub2api === undefined && deepseek === undefined && providerBalance === undefined
146
+ ? (resetLabel ?? '订阅余量')
147
+ : sub2api !== undefined ? `今日 ${formatSub2ApiMoney(sub2api.today.cost, sub2api.unit)}` : deepseek !== undefined ? '账户余额' : '官方余量')), open && createElement(SubscriptionPopover, { usage, providerName }));
122
148
  }
123
149
  function SubscriptionPopover({ usage, providerName }) {
124
150
  if (usage.sub2api !== undefined)
125
151
  return createElement(Sub2ApiPopover, { usage: usage.sub2api, providerName });
152
+ if (usage.deepseek !== undefined)
153
+ return createElement(DeepseekPopover, { usage: usage.deepseek, providerName });
154
+ if (usage.providerBalance !== undefined)
155
+ return createElement(ProviderBalancePopover, { usage: usage.providerBalance, providerName });
126
156
  const windows = [
127
- ['5 小时额度', usage.primary],
128
- ['周额度', usage.secondary],
129
- ['月额度', usage.monthly],
157
+ { id: 'primary', label: formatSubscriptionWindowLabel(usage.primary, '5 小时额度'), window: usage.primary },
158
+ { id: 'secondary', label: formatSubscriptionWindowLabel(usage.secondary, '周额度'), window: usage.secondary },
159
+ { id: 'monthly', label: formatSubscriptionWindowLabel(usage.monthly, '月额度'), window: usage.monthly },
130
160
  ];
131
- return createElement('div', { role: 'dialog', 'aria-label': `${providerName} 订阅使用情况`, style: subscriptionPopoverStyle }, createElement('div', { style: popoverHeadingStyle }, createElement('strong', undefined, `${providerName} 订阅`), usage.planType && createElement('span', { style: { color: dshThemeColor.labelTertiary } }, formatPlanType(usage.planType))), ...windows.map(([label, window]) => window === null ? null : createElement('section', { key: label, style: windowStyle }, createElement('div', { style: windowHeadingStyle }, createElement('span', undefined, label), createElement('span', undefined, `${formatPercent(window.remainingPercent)}%`)), createElement('div', { role: 'progressbar', 'aria-label': `${label}剩余 ${formatPercent(window.remainingPercent)}%`, 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': window.remainingPercent, style: barStyle }, createElement('span', { style: { ...barFillStyle, width: `${window.remainingPercent}%` } })), window.resetsAt !== null && createElement('div', { style: resetStyle }, `重置于 ${formatCountdown(window.resetsAt)}`))));
161
+ return createElement('div', { role: 'dialog', 'aria-label': `${providerName} 订阅使用情况`, style: subscriptionPopoverStyle }, createElement('div', { style: popoverHeadingStyle }, createElement('strong', undefined, `${providerName} 订阅`), usage.planType && createElement('span', { style: { color: dshThemeColor.labelTertiary } }, formatPlanType(usage.planType))), ...windows.map(({ id, label, window }) => window === null ? null : createElement('section', { key: id, style: windowStyle }, createElement('div', { style: windowHeadingStyle }, createElement('span', undefined, label), createElement('span', undefined, `${formatPercent(window.remainingPercent)}%`)), createElement('div', { role: 'progressbar', 'aria-label': `${label}剩余 ${formatPercent(window.remainingPercent)}%`, 'aria-valuemin': 0, 'aria-valuemax': 100, 'aria-valuenow': window.remainingPercent, style: barStyle }, createElement('span', { style: { ...barFillStyle, width: `${window.remainingPercent}%` } })), window.resetsAt !== null && createElement('div', { style: resetStyle }, `重置于 ${formatCountdown(window.resetsAt)}`))));
162
+ }
163
+ /** 根据服务端返回的窗口时长生成准确的额度标签,避免把七天窗口误显示成五小时。 */
164
+ function formatSubscriptionWindowLabel(window, fallback) {
165
+ const durationMins = window?.windowDurationMins;
166
+ if (durationMins === null || durationMins === undefined || !Number.isFinite(durationMins) || durationMins <= 0)
167
+ return fallback;
168
+ if (durationMins % (24 * 60) === 0)
169
+ return `${durationMins / (24 * 60)} 天额度`;
170
+ if (durationMins % 60 === 0)
171
+ return `${durationMins / 60} 小时额度`;
172
+ return `${durationMins} 分钟额度`;
173
+ }
174
+ function ProviderBalancePopover({ usage, providerName }) {
175
+ return createElement('div', { role: 'dialog', 'aria-label': `${providerName} 官方余量`, style: subscriptionPopoverStyle }, createElement('div', { style: popoverHeadingStyle }, createElement('strong', undefined, `${providerName} 官方余量`), createElement('span', { style: { color: dshThemeColor.labelTertiary } }, formatProviderBalance(usage))), usage.used !== null && usage.total !== null && createElement('div', { style: upstreamMetaStyle }, `已用 ${formatProviderBalanceValue(usage.used, usage.unit)} / ${formatProviderBalanceValue(usage.total, usage.unit)}`), usage.details.length === 0
176
+ ? createElement('div', { style: resetStyle }, '暂无更多统计')
177
+ : usage.details.map((item) => createElement('div', { key: item.label, style: deepseekBalanceDetailsStyle }, createElement('span', undefined, item.label), createElement('span', undefined, String(item.value)))));
178
+ }
179
+ function DeepseekPopover({ usage, providerName }) {
180
+ return createElement('div', { role: 'dialog', 'aria-label': `${providerName} 账户余额`, style: subscriptionPopoverStyle }, createElement('div', { style: popoverHeadingStyle }, createElement('strong', undefined, `${providerName} 账户余额`), createElement('span', { style: { color: usage.isAvailable === false ? dshThemeColor.error : dshThemeColor.labelTertiary } }, usage.isAvailable === false ? '不可用' : '可用')), usage.balances.map((balance) => createElement('section', { key: balance.currency, style: deepseekBalanceSectionStyle }, createElement('div', { style: windowHeadingStyle }, createElement('span', undefined, balance.currency), createElement('strong', undefined, formatDeepseekMoney(balance.totalBalance, balance.currency))), createElement('div', { style: deepseekBalanceDetailsStyle }, createElement('span', undefined, `赠送 ${formatDeepseekMoney(balance.grantedBalance, balance.currency)}`), createElement('span', undefined, `充值 ${formatDeepseekMoney(balance.toppedUpBalance, balance.currency)}`)))), createElement('div', { style: deepseekUnavailableStatsStyle }, '官方 DeepSeek API 当前只提供账户余额接口,暂无请求量、Token 或费用明细。'));
132
181
  }
133
182
  function Sub2ApiPopover({ usage, providerName }) {
134
183
  return createElement('div', { role: 'dialog', 'aria-label': `${providerName} 上游用量`, style: subscriptionPopoverStyle }, createElement('div', { style: popoverHeadingStyle }, createElement('strong', undefined, `${providerName} 上游用量`), createElement('span', { style: { color: dshThemeColor.labelTertiary } }, formatSub2ApiMoney(usage.balance, usage.unit))), createElement('div', { style: upstreamMetaStyle }, createElement('span', { style: upstreamTypeStyle }, usage.upstreamType), usage.upstreamUrl === ''
@@ -146,13 +195,25 @@ function createModelRow(model, unit) {
146
195
  function resolveDisplayWindow(usage) {
147
196
  return usage.primary ?? usage.secondary ?? usage.monthly;
148
197
  }
198
+ function selectDeepseekBalance(usage) {
199
+ return usage.balances.find((balance) => balance.currency.toUpperCase() === 'USD') ?? usage.balances[0] ?? null;
200
+ }
149
201
  function isSubscriptionAdapter(adapterId) {
150
202
  return adapterId === 'command-code' || adapterId === 'codex' || adapterId === 'claude-code' || adapterId === 'dsh' || adapterId === 'grok' || adapterId === 'opencode';
151
203
  }
152
204
  function isRemoteWebContext() {
153
205
  return globalThis.__CODINGNS4DSH_REMOTE_WEB_CONTEXT__ === true;
154
206
  }
155
- function subscriptionProviderName(adapterId) {
207
+ function subscriptionProviderName(adapterId, providerId, usage) {
208
+ if (usage.provider?.displayName)
209
+ return usage.provider.displayName;
210
+ if (adapterId === 'dsh' && providerId !== null) {
211
+ if (/^(?:deepseek(?:-official)?|official-deepseek)$/iu.test(providerId))
212
+ return 'DeepSeek 官方';
213
+ return formatProviderName(providerId);
214
+ }
215
+ if (adapterId === 'dsh' && usage.sub2api !== undefined)
216
+ return `${usage.sub2api.upstreamType} 上游`;
156
217
  switch (adapterId) {
157
218
  case 'command-code': return 'Command Code';
158
219
  case 'codex': return 'Codex';
@@ -163,6 +224,9 @@ function subscriptionProviderName(adapterId) {
163
224
  default: return 'Agent';
164
225
  }
165
226
  }
227
+ function formatProviderName(value) {
228
+ return value.replace(/[-_]+/gu, ' ').replace(/(^|\s)([a-z])/gu, (_match, prefix, letter) => `${prefix}${letter.toUpperCase()}`);
229
+ }
166
230
  function formatPercent(value) { return Math.max(0, Math.min(100, value)).toFixed(0); }
167
231
  function formatRingPercentage(value) { return String(Math.floor(Math.max(0, Math.min(100, value)))); }
168
232
  function formatSub2ApiMoney(value, unit) {
@@ -171,6 +235,28 @@ function formatSub2ApiMoney(value, unit) {
171
235
  return `$${value.toFixed(2)}`;
172
236
  return `${value.toFixed(2)}${normalizedUnit === '' ? '' : ` ${normalizedUnit}`}`;
173
237
  }
238
+ function formatDeepseekMoney(value, currency) {
239
+ const normalizedCurrency = currency.trim().toUpperCase();
240
+ const amount = value.toFixed(2);
241
+ if (normalizedCurrency === 'USD')
242
+ return `$${amount}`;
243
+ if (normalizedCurrency === 'CNY')
244
+ return `¥${amount}`;
245
+ return `${amount} ${normalizedCurrency}`;
246
+ }
247
+ function formatProviderBalance(usage) {
248
+ if (usage === undefined || usage.remaining === null)
249
+ return '--';
250
+ return formatProviderBalanceValue(usage.remaining, usage.unit);
251
+ }
252
+ function formatProviderBalanceValue(value, unit) {
253
+ const normalized = unit?.trim().toUpperCase() ?? '';
254
+ if (normalized === '%')
255
+ return `${value.toFixed(0)}%`;
256
+ if (normalized === 'USD')
257
+ return `$${value.toFixed(2)}`;
258
+ return `${value.toFixed(2)}${normalized === '' ? '' : ` ${normalized}`}`;
259
+ }
174
260
  function formatSub2ApiTokens(value) { return new Intl.NumberFormat('zh-CN', { notation: 'compact', maximumFractionDigits: 1 }).format(value); }
175
261
  function formatSub2ApiPercent(value) { return `${Math.max(0, Math.min(100, value)).toFixed(1)}%`; }
176
262
  function formatInteger(value) { return new Intl.NumberFormat('zh-CN').format(Math.max(0, Math.round(value))); }
@@ -193,6 +279,12 @@ const subscriptionTriggerStyle = { display: 'inline-flex', alignItems: 'center',
193
279
  const subscriptionLabelStyle = { whiteSpace: 'nowrap' };
194
280
  const sub2apiIdentityStyle = { display: 'inline-flex', alignItems: 'center', gap: 5, whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' };
195
281
  const sub2apiLogoStyle = { display: 'block', borderRadius: 4, objectFit: 'contain' };
282
+ const deepseekBalanceIdentityStyle = { display: 'inline-flex', alignItems: 'center', gap: 5, whiteSpace: 'nowrap', fontVariantNumeric: 'tabular-nums' };
283
+ const deepseekLogoStyle = { display: 'block', borderRadius: 5, objectFit: 'contain' };
284
+ const deepseekBalanceStyle = { display: 'inline-flex', alignItems: 'center', color: dshThemeColor.labelSecondary, fontSize: 13, fontWeight: 600, fontVariantNumeric: 'tabular-nums' };
285
+ const deepseekBalanceSectionStyle = { display: 'grid', gap: 6, marginTop: 10, padding: '10px 0 2px', borderTop: `1px solid ${dshThemeColor.border}` };
286
+ const deepseekBalanceDetailsStyle = { display: 'flex', justifyContent: 'space-between', gap: 12, color: dshThemeColor.labelTertiary, fontSize: 12 };
287
+ const deepseekUnavailableStatsStyle = { marginTop: 12, paddingTop: 10, borderTop: `1px solid ${dshThemeColor.border}`, color: dshThemeColor.labelTertiary, fontSize: 12, lineHeight: '17px' };
196
288
  const progressRingVisualStyle = { boxSizing: 'border-box', width: '100%', height: '100%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', padding: 2, borderRadius: 'inherit' };
197
289
  const progressRingValueStyle = { boxSizing: 'border-box', width: '100%', height: '100%', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', gap: 0.5, borderRadius: 'inherit', background: dshThemeColor.menuBackground, fontSize: 7, lineHeight: 1, fontWeight: 700, color: dshThemeColor.labelPrimary, whiteSpace: 'nowrap' };
198
290
  const progressRingSuffixStyle = { fontSize: 5.5, lineHeight: 1, color: dshThemeColor.labelTertiary, transform: 'translateY(1px)' };
@@ -48,15 +48,20 @@ export interface TerminalObservable<T> {
48
48
  getSnapshot(): T;
49
49
  subscribe(listener: () => void): () => void;
50
50
  }
51
+ interface WorkspaceBindingResolution {
52
+ readonly id: WebTerminalId;
53
+ /** 是否来自已有工作区绑定;新建绑定仍允许本次 view 创建 Host 终端。 */
54
+ readonly existing: boolean;
55
+ }
51
56
  /** 一个 Sidebar 标签对应的终端模型;DOM 卸载只 detach,显式 close 才结束 Host 进程。 */
52
57
  export declare class CodingNsTerminalView {
53
58
  readonly sessionId: string;
54
- readonly id: WebTerminalId;
55
59
  private readonly remote;
56
60
  private readonly createWhenMissing;
57
61
  private readonly shellPath?;
58
62
  private readonly onWorkspaceResolved?;
59
63
  readonly state: TerminalObservable<TerminalViewState>;
64
+ id: WebTerminalId;
60
65
  private readonly store;
61
66
  private readonly lifetime;
62
67
  private followController;
@@ -68,7 +73,7 @@ export declare class CodingNsTerminalView {
68
73
  private attachmentId;
69
74
  /** 最近一次已排队的尺寸;ResizeObserver 可能在同一布局周期内重复触发。 */
70
75
  private lastResize;
71
- constructor(sessionId: string, id: WebTerminalId, remote: TerminalRemoteSource, createWhenMissing: boolean, shellPath?: string | undefined, onWorkspaceResolved?: ((workspaceId: string) => void) | undefined);
76
+ constructor(sessionId: string, id: WebTerminalId, remote: TerminalRemoteSource, createWhenMissing: boolean, shellPath?: string | undefined, onWorkspaceResolved?: ((workspaceId: string, id: WebTerminalId) => WorkspaceBindingResolution | undefined) | undefined);
72
77
  mount(): () => void;
73
78
  refresh(): Promise<void>;
74
79
  acknowledge(revision: number): void;
@@ -93,7 +98,11 @@ export declare class CodingNsWebTerminals extends Service {
93
98
  private readonly recoveries;
94
99
  private readonly closeFailureStore;
95
100
  readonly closeFailures: TerminalObservable<readonly TerminalCloseFailure[]>;
101
+ private readonly remoteReadyStore;
102
+ readonly remoteReadyState: TerminalObservable<boolean>;
96
103
  private readonly closeRequests;
104
+ /** Remote 注入前不能执行关闭请求;就绪后统一冲刷,避免把启动竞态显示成永久错误。 */
105
+ private cleanupQueued;
97
106
  constructor(ctx: Context, remote: TerminalRemoteSource);
98
107
  view(sessionId: string, key: string, contentId: string, terminalId?: WebTerminalId, shellPath?: string): CodingNsTerminalView;
99
108
  /** 返回某个 Sidebar 内容已经保存的 Host 终端身份,用于恢复时去重。 */
@@ -103,9 +112,12 @@ export declare class CodingNsWebTerminals extends Service {
103
112
  close(sessionId: string, key: string, contentId: string, terminalId?: WebTerminalId): void;
104
113
  recover(sessionId: string): Promise<readonly WebTerminalInfo[]>;
105
114
  retryClose(id: WebTerminalId): void;
115
+ /** 由 Client 的 Remote 注入回调调用,完成启动阶段延迟的关闭请求。 */
116
+ remoteReady(): void;
106
117
  dispose(): Promise<void>;
107
118
  private rememberWorkspace;
108
119
  private cleanup;
120
+ private flushCleanup;
109
121
  }
110
122
  declare module '@deepseek-ai/cordis' {
111
123
  interface Context {