@jingyi0605/codingns4dsh 0.1.2 → 0.1.7-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (171) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +14 -5
  3. package/data/build/dist/client/account-bar.js +57 -3
  4. package/data/build/dist/client/bundle.js +36246 -268
  5. package/data/build/dist/client/cli-slots.js +60 -18
  6. package/data/build/dist/client/dsh-h5-bootstrap.d.ts +2 -0
  7. package/data/build/dist/client/dsh-h5-bootstrap.js +79 -13
  8. package/data/build/dist/client/features/file-management-panel.d.ts +5 -0
  9. package/data/build/dist/client/features/file-management-panel.js +48 -0
  10. package/data/build/dist/client/features/file-management.d.ts +4 -0
  11. package/data/build/dist/client/features/file-management.js +50 -0
  12. package/data/build/dist/client/features/index.d.ts +10 -1
  13. package/data/build/dist/client/features/index.js +12 -1
  14. package/data/build/dist/client/features/login-protection-panel.js +1 -1
  15. package/data/build/dist/client/features/login-protection-session.d.ts +2 -0
  16. package/data/build/dist/client/features/login-protection-session.js +18 -0
  17. package/data/build/dist/client/features/peer-host.d.ts +4 -0
  18. package/data/build/dist/client/features/peer-host.js +89 -0
  19. package/data/build/dist/client/features/reverse-proxy-panel.js +23 -7
  20. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -2
  21. package/data/build/dist/client/features/reverse-proxy.js +75 -5
  22. package/data/build/dist/client/features/types.d.ts +9 -0
  23. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +10 -0
  24. package/data/build/dist/client/features/workspace-session-enhancement.js +18 -1
  25. package/data/build/dist/client/file-management-dom.d.ts +12 -0
  26. package/data/build/dist/client/file-management-dom.js +527 -0
  27. package/data/build/dist/client/git-management.js +24 -5
  28. package/data/build/dist/client/host-navigation.d.ts +29 -0
  29. package/data/build/dist/client/host-navigation.js +29 -0
  30. package/data/build/dist/client/host-router.d.ts +3 -0
  31. package/data/build/dist/client/host-router.js +2 -0
  32. package/data/build/dist/client/index.d.ts +6 -1
  33. package/data/build/dist/client/index.js +30 -10
  34. package/data/build/dist/client/locale.js +36 -7
  35. package/data/build/dist/client/peer-host-connection-button.d.ts +13 -0
  36. package/data/build/dist/client/peer-host-connection-button.js +69 -0
  37. package/data/build/dist/client/peer-host-management-api.d.ts +40 -0
  38. package/data/build/dist/client/peer-host-management-api.js +33 -0
  39. package/data/build/dist/client/peer-host-management-panel.d.ts +13 -0
  40. package/data/build/dist/client/peer-host-management-panel.js +181 -0
  41. package/data/build/dist/client/peer-host-native-session-ui.d.ts +60 -0
  42. package/data/build/dist/client/peer-host-native-session-ui.js +372 -0
  43. package/data/build/dist/client/peer-host-scoped-client.d.ts +67 -0
  44. package/data/build/dist/client/peer-host-scoped-client.js +316 -0
  45. package/data/build/dist/client/peer-host-session-controller.d.ts +30 -0
  46. package/data/build/dist/client/peer-host-session-controller.js +95 -0
  47. package/data/build/dist/client/quick-phrase-slot.js +14 -0
  48. package/data/build/dist/client/remote-web-context.js +26 -4
  49. package/data/build/dist/client/session-changed-files-view.d.ts +15 -0
  50. package/data/build/dist/client/session-changed-files-view.js +337 -0
  51. package/data/build/dist/client/settings-bridge.d.ts +14 -4
  52. package/data/build/dist/client/settings-bridge.js +59 -12
  53. package/data/build/dist/client/settings-section.js +17 -4
  54. package/data/build/dist/client/subscription-slot.js +21 -8
  55. package/data/build/dist/client/workspace-session-rightbar-dom.d.ts +28 -0
  56. package/data/build/dist/client/workspace-session-rightbar-dom.js +291 -0
  57. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +25 -33
  58. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +7 -1
  59. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +34 -11
  60. package/data/build/dist/dsh-capabilities/matrix.js +9 -0
  61. package/data/build/dist/dsh-capabilities/routes.js +35 -0
  62. package/data/build/dist/dsh-capabilities/settings-store.d.ts +12 -0
  63. package/data/build/dist/dsh-capabilities/settings-store.js +34 -0
  64. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  65. package/data/build/dist/features/host-router.d.ts +26 -0
  66. package/data/build/dist/features/host-router.js +110 -0
  67. package/data/build/dist/features/registry.js +3 -0
  68. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +13 -1
  69. package/data/build/dist/host/cli-adapters/codex-driver.js +528 -72
  70. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +21 -2
  71. package/data/build/dist/host/cli-adapters/command-code-driver.js +408 -105
  72. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  73. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
  74. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +47 -28
  75. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +37 -0
  76. package/data/build/dist/host/cli-adapters/feature.js +94 -10
  77. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +6 -0
  78. package/data/build/dist/host/cli-adapters/json-rpc-process.js +14 -3
  79. package/data/build/dist/host/cli-adapters/kimi-driver.js +3 -3
  80. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +6 -0
  81. package/data/build/dist/host/cli-adapters/model-catalog.js +8 -0
  82. package/data/build/dist/host/cli-adapters/opencode-driver.js +14 -2
  83. package/data/build/dist/host/cli-adapters/pi-driver.js +2 -2
  84. package/data/build/dist/host/cli-adapters/process-utils.d.ts +8 -0
  85. package/data/build/dist/host/cli-adapters/process-utils.js +48 -0
  86. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -1
  87. package/data/build/dist/host/cli-adapters/registry.js +41 -7
  88. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +23 -4
  89. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  90. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +36 -15
  91. package/data/build/dist/host/dsh-device-runtime.d.ts +2 -0
  92. package/data/build/dist/host/dsh-device-runtime.js +55 -29
  93. package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
  94. package/data/build/dist/host/dsh-runtime-version.js +71 -2
  95. package/data/build/dist/host/features/auth.js +10 -2
  96. package/data/build/dist/host/features/file-management.d.ts +5 -0
  97. package/data/build/dist/host/features/file-management.js +267 -0
  98. package/data/build/dist/host/features/git-management.js +24 -2
  99. package/data/build/dist/host/features/index.d.ts +2 -0
  100. package/data/build/dist/host/features/index.js +6 -0
  101. package/data/build/dist/host/features/peer-host.d.ts +22 -0
  102. package/data/build/dist/host/features/peer-host.js +322 -0
  103. package/data/build/dist/host/features/types.d.ts +4 -0
  104. package/data/build/dist/host/host-router.d.ts +3 -0
  105. package/data/build/dist/host/host-router.js +2 -0
  106. package/data/build/dist/host/index-injection.d.ts +17 -0
  107. package/data/build/dist/host/index-injection.js +24 -0
  108. package/data/build/dist/host/index.d.ts +4 -1
  109. package/data/build/dist/host/index.js +41 -7
  110. package/data/build/dist/host/lan-access-dsh.d.ts +34 -1
  111. package/data/build/dist/host/lan-access-dsh.js +145 -26
  112. package/data/build/dist/host/modules/peer-host/host-api-proxy-service.d.ts +52 -0
  113. package/data/build/dist/host/modules/peer-host/host-api-proxy-service.js +166 -0
  114. package/data/build/dist/host/modules/peer-host/host-ws-connector.d.ts +31 -0
  115. package/data/build/dist/host/modules/peer-host/host-ws-connector.js +108 -0
  116. package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.d.ts +26 -0
  117. package/data/build/dist/host/modules/peer-host/host-ws-proxy-service.js +159 -0
  118. package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.d.ts +50 -0
  119. package/data/build/dist/host/modules/peer-host/peer-host-aggregate-service.js +97 -0
  120. package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.d.ts +19 -0
  121. package/data/build/dist/host/modules/peer-host/peer-host-diagnostics.js +54 -0
  122. package/data/build/dist/host/modules/peer-host/peer-host-handshake.d.ts +44 -0
  123. package/data/build/dist/host/modules/peer-host/peer-host-handshake.js +133 -0
  124. package/data/build/dist/host/modules/peer-host/peer-host-relay.d.ts +65 -0
  125. package/data/build/dist/host/modules/peer-host/peer-host-relay.js +196 -0
  126. package/data/build/dist/host/modules/peer-host/peer-host-session.d.ts +42 -0
  127. package/data/build/dist/host/modules/peer-host/peer-host-session.js +152 -0
  128. package/data/build/dist/host/modules/peer-host/peer-host-store.d.ts +92 -0
  129. package/data/build/dist/host/modules/peer-host/peer-host-store.js +323 -0
  130. package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.d.ts +34 -0
  131. package/data/build/dist/host/modules/peer-host/peer-host-ws-gateway.js +156 -0
  132. package/data/build/dist/host/native-session-bridge.d.ts +18 -2
  133. package/data/build/dist/host/native-session-bridge.js +236 -8
  134. package/data/build/dist/host/remote-web-runtime.js +26 -1
  135. package/data/build/dist/host/rpc.js +8 -3
  136. package/data/build/dist/host/session-changes.d.ts +5 -0
  137. package/data/build/dist/host/session-changes.js +163 -0
  138. package/data/build/dist/host/settings.js +6 -0
  139. package/data/build/dist/index.d.ts +1 -1
  140. package/data/build/dist/index.js +1 -1
  141. package/data/build/dist/shared/contracts/cli-adapter.d.ts +23 -2
  142. package/data/build/dist/shared/contracts/config.d.ts +15 -0
  143. package/data/build/dist/shared/contracts/config.js +10 -0
  144. package/data/build/dist/shared/contracts/dsh-device.d.ts +2 -0
  145. package/data/build/dist/shared/contracts/errors.d.ts +14 -0
  146. package/data/build/dist/shared/contracts/errors.js +15 -0
  147. package/data/build/dist/shared/contracts/feature.d.ts +2 -0
  148. package/data/build/dist/shared/contracts/file-management.d.ts +6 -0
  149. package/data/build/dist/shared/contracts/file-management.js +3 -0
  150. package/data/build/dist/shared/contracts/peer-host.d.ts +116 -15
  151. package/data/build/dist/shared/contracts/peer-host.js +19 -0
  152. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  153. package/data/build/dist/shared/contracts/version.js +1 -1
  154. package/data/build/dist/shared/index.d.ts +4 -3
  155. package/data/build/dist/shared/index.js +2 -2
  156. package/data/build/dist/transport/debug.d.ts +5 -0
  157. package/data/build/dist/transport/debug.js +23 -1
  158. package/data/build/dist/transport/dsh-transport.d.ts +2 -0
  159. package/data/build/dist/transport/dsh-transport.js +11 -0
  160. package/data/build/dist/transport/webrtc-client.d.ts +2 -0
  161. package/data/build/dist/transport/webrtc-client.js +2 -1
  162. package/data/build/dist/transport/webrtc-host.d.ts +2 -0
  163. package/data/build/dist/transport/webrtc-host.js +6 -3
  164. package/data/build/dist/typert.host.d.ts +1 -1
  165. package/data/build/dist/typert.host.js +2 -1
  166. package/dsh.bundle.patch +10 -0
  167. package/package.json +19 -11
  168. package/profile/README.md +2 -2
  169. package/profile/package.json +2 -2
  170. package/profile/version.json +1 -1
  171. package/version.json +1 -1
@@ -2,8 +2,20 @@ import { createElement, useEffect, useRef, useState } from 'react';
2
2
  import { callCliRpc } from './cli-catalog.js';
3
3
  import { providerIconUrl } from './provider-icons.js';
4
4
  import { dshPopupSurfaceStyle, dshThemeColor } from './theme.js';
5
+ const SUBSCRIPTION_STYLE_ID = 'codingns4dsh-subscription-responsive-style';
6
+ /** 移动端订阅入口只保留图标,完整数据仍可在点击后的弹层中查看。 */
7
+ function installSubscriptionStyles() {
8
+ if (typeof document === 'undefined' || document.querySelector(`style[data-plugin-css="${SUBSCRIPTION_STYLE_ID}"]`) !== null)
9
+ return;
10
+ const style = document.createElement('style');
11
+ style.dataset.plugin = 'codingns4dsh';
12
+ style.dataset.pluginCss = SUBSCRIPTION_STYLE_ID;
13
+ style.textContent = '@media (max-width: 768px){.codingns4dsh-subscription-trigger{gap:0!important;padding-left:4px!important;padding-right:4px!important}.codingns4dsh-subscription-label,.codingns4dsh-subscription-value{display:none!important}.codingns4dsh-subscription-popover{position:fixed!important;left:12px!important;right:12px!important;bottom:48px!important;width:auto!important;min-width:0!important;max-width:none!important;max-height:calc(100vh - 72px)!important;overflow:auto!important}}';
14
+ document.head.appendChild(style);
15
+ }
5
16
  /** 在 DSH 原生步骤统计左侧显示当前 Agent 的订阅余量。 */
6
17
  export function registerSubscriptionSlot(slots, rpc) {
18
+ installSubscriptionStyles();
7
19
  return slots.inject('conversation.composer.dock', () => slots.register({
8
20
  name: 'conversation.composer.dock',
9
21
  id: 'codingns4dsh-subscription',
@@ -131,18 +143,19 @@ function CommandCodeSubscriptionSlot(props) {
131
143
  const triggerContent = sub2api === undefined && deepseek === undefined && providerBalance === undefined
132
144
  ? 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
145
  : 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)))
146
+ ? createElement('span', { 'aria-hidden': true, style: sub2apiIdentityStyle }, createElement('img', { src: logoSource || providerIconUrl(adapterId ?? 'dsh') || '', alt: '', width: 20, height: 20, style: sub2apiLogoStyle }), createElement('span', { className: 'codingns4dsh-subscription-value' }, formatSub2ApiMoney(sub2api.balance, sub2api.unit)))
135
147
  : 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)));
148
+ ? createElement('span', { 'aria-hidden': true, style: deepseekBalanceIdentityStyle }, deepseekIconSource !== undefined && createElement('img', { src: deepseekIconSource, alt: '', width: 18, height: 18, style: deepseekLogoStyle }), createElement('span', { className: 'codingns4dsh-subscription-value', style: deepseekBalanceStyle }, deepseekBalance === null ? '--' : formatDeepseekMoney(deepseekBalance.totalBalance, deepseekBalance.currency)))
149
+ : createElement('span', { 'aria-hidden': true, style: deepseekBalanceIdentityStyle }, deepseekIconSource !== undefined && createElement('img', { src: deepseekIconSource, alt: '', width: 18, height: 18, style: deepseekLogoStyle }), createElement('span', { className: 'codingns4dsh-subscription-value', style: deepseekBalanceStyle }, formatProviderBalance(providerBalance)));
138
150
  return createElement('div', { ref: rootRef, style: subscriptionRootStyle }, createElement('button', {
139
151
  type: 'button',
152
+ className: 'codingns4dsh-subscription-trigger',
140
153
  onClick: () => setOpen((value) => !value),
141
154
  disabled: loading && usage === null,
142
155
  'aria-label': label,
143
156
  'aria-expanded': open,
144
157
  style: subscriptionTriggerStyle,
145
- }, triggerContent, createElement('span', { style: subscriptionLabelStyle }, sub2api === undefined && deepseek === undefined && providerBalance === undefined
158
+ }, triggerContent, createElement('span', { className: 'codingns4dsh-subscription-label', style: subscriptionLabelStyle }, sub2api === undefined && deepseek === undefined && providerBalance === undefined
146
159
  ? (resetLabel ?? '订阅余量')
147
160
  : sub2api !== undefined ? `今日 ${formatSub2ApiMoney(sub2api.today.cost, sub2api.unit)}` : deepseek !== undefined ? '账户余额' : '官方余量')), open && createElement(SubscriptionPopover, { usage, providerName }));
148
161
  }
@@ -158,7 +171,7 @@ function SubscriptionPopover({ usage, providerName }) {
158
171
  { id: 'secondary', label: formatSubscriptionWindowLabel(usage.secondary, '周额度'), window: usage.secondary },
159
172
  { id: 'monthly', label: formatSubscriptionWindowLabel(usage.monthly, '月额度'), window: usage.monthly },
160
173
  ];
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)}`))));
174
+ return createElement('div', { className: 'codingns4dsh-subscription-popover', 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
175
  }
163
176
  /** 根据服务端返回的窗口时长生成准确的额度标签,避免把七天窗口误显示成五小时。 */
164
177
  function formatSubscriptionWindowLabel(window, fallback) {
@@ -172,15 +185,15 @@ function formatSubscriptionWindowLabel(window, fallback) {
172
185
  return `${durationMins} 分钟额度`;
173
186
  }
174
187
  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
188
+ return createElement('div', { className: 'codingns4dsh-subscription-popover', 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
189
  ? createElement('div', { style: resetStyle }, '暂无更多统计')
177
190
  : usage.details.map((item) => createElement('div', { key: item.label, style: deepseekBalanceDetailsStyle }, createElement('span', undefined, item.label), createElement('span', undefined, String(item.value)))));
178
191
  }
179
192
  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 或费用明细。'));
193
+ return createElement('div', { className: 'codingns4dsh-subscription-popover', 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 或费用明细。'));
181
194
  }
182
195
  function Sub2ApiPopover({ usage, providerName }) {
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 === ''
196
+ return createElement('div', { className: 'codingns4dsh-subscription-popover', 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 === ''
184
197
  ? createElement('span', { style: upstreamMutedStyle }, '地址未提供')
185
198
  : createElement('a', { href: usage.upstreamUrl, target: '_blank', rel: 'noreferrer', title: usage.upstreamUrl, style: upstreamLinkStyle }, usage.upstreamUrl)), createElement('div', { style: sub2apiStatsGridStyle }, createSub2ApiStat('今日请求数', formatInteger(usage.today.requests)), createSub2ApiStat('今日 Token 用量', formatSub2ApiTokens(usage.today.totalTokens)), createSub2ApiStat('今日费用', formatSub2ApiMoney(usage.today.cost, usage.unit)), createSub2ApiStat('累计请求数', formatInteger(usage.total.requests)), createSub2ApiStat('累计 Token 用量', formatSub2ApiTokens(usage.total.totalTokens)), createSub2ApiStat('累计费用', formatSub2ApiMoney(usage.total.cost, usage.unit)), createSub2ApiStat('今日缓存命中率', formatSub2ApiPercent(usage.today.cacheHitRate)), createSub2ApiStat('累计缓存命中率', formatSub2ApiPercent(usage.total.cacheHitRate))), createElement('section', { style: sub2apiSectionStyle }, createElement('strong', { style: sub2apiSectionTitleStyle }, '按模型统计'), usage.models.length === 0
186
199
  ? createElement('div', { style: resetStyle }, '暂无按模型统计')
@@ -0,0 +1,28 @@
1
+ /** 右侧栏宽度比例的本地存储键;宽度属于浏览器布局,不应同步到 Host。 */
2
+ export declare const WORKSPACE_RIGHTBAR_RATIO_STORAGE_KEY = "codingns4dsh.workspace.rightbar-ratio.v1";
3
+ interface StorageLike {
4
+ getItem(key: string): string | null;
5
+ setItem(key: string, value: string): void;
6
+ }
7
+ export interface WorkspaceSessionRightbarDomOptions {
8
+ readonly document?: Document;
9
+ readonly window?: Window;
10
+ readonly MutationObserver?: typeof MutationObserver;
11
+ readonly storage?: StorageLike;
12
+ }
13
+ export interface WorkspaceSessionRightbarDomController {
14
+ /** 主动重新查找句柄并应用已保存的比例。 */
15
+ refresh(): void;
16
+ /** 断开观察器并移除事件监听。 */
17
+ dispose(): void;
18
+ }
19
+ /**
20
+ * 记忆 DSH 原生对话区与右侧栏的宽度比例。
21
+ *
22
+ * DSH 不同版本暴露了两种句柄:旧版使用 `data-side="rightbar"` 并直接写入
23
+ * `left`,新版使用 `workbench-side-resizer[data-side="right"]` 并通过工作台
24
+ * CSS 变量控制宽度。这里只读取边界,不依赖 React Fiber 或版本号。
25
+ */
26
+ export declare function startWorkspaceSessionRightbarDom(options?: WorkspaceSessionRightbarDomOptions): WorkspaceSessionRightbarDomController;
27
+ export {};
28
+ //# sourceMappingURL=workspace-session-rightbar-dom.d.ts.map
@@ -0,0 +1,291 @@
1
+ /** 右侧栏宽度比例的本地存储键;宽度属于浏览器布局,不应同步到 Host。 */
2
+ export const WORKSPACE_RIGHTBAR_RATIO_STORAGE_KEY = 'codingns4dsh.workspace.rightbar-ratio.v1';
3
+ const RIGHTBAR_HANDLE_SELECTOR = '[data-side="rightbar"], .workbench-side-resizer[data-side="right"]';
4
+ const MIN_RIGHTBAR_SHARE = 0.12;
5
+ const MAX_RIGHTBAR_SHARE = 0.8;
6
+ /**
7
+ * 记忆 DSH 原生对话区与右侧栏的宽度比例。
8
+ *
9
+ * DSH 不同版本暴露了两种句柄:旧版使用 `data-side="rightbar"` 并直接写入
10
+ * `left`,新版使用 `workbench-side-resizer[data-side="right"]` 并通过工作台
11
+ * CSS 变量控制宽度。这里只读取边界,不依赖 React Fiber 或版本号。
12
+ */
13
+ export function startWorkspaceSessionRightbarDom(options = {}) {
14
+ const dom = options.document ?? (typeof document === 'undefined' ? undefined : document);
15
+ const win = options.window ?? (typeof window === 'undefined' ? undefined : window);
16
+ const Observer = options.MutationObserver
17
+ ?? (typeof MutationObserver === 'undefined' ? undefined : MutationObserver);
18
+ const storage = options.storage ?? resolveStorage();
19
+ let disposed = false;
20
+ let scanQueued = false;
21
+ let desiredShare = readStoredShare(storage);
22
+ let userInteracting = false;
23
+ let userCommitPending = false;
24
+ let applying = false;
25
+ let pendingShare;
26
+ const scan = () => {
27
+ if (disposed || dom === undefined || win === undefined)
28
+ return;
29
+ const handle = findRightbarHandle(dom);
30
+ if (handle === undefined)
31
+ return;
32
+ if (applying)
33
+ return;
34
+ const share = readRightbarShare(handle, win);
35
+ if (share === undefined)
36
+ return;
37
+ if (pendingShare !== undefined) {
38
+ const expectedShare = pendingShare;
39
+ pendingShare = undefined;
40
+ if (sharesMatch(share, expectedShare))
41
+ return;
42
+ // 宿主重绘时可能先移除旧句柄的内联位置,再补上新的布局状态。
43
+ // 这种中间态可以直接继续恢复;如果仍有旧位置,则等下一次宿主重绘,
44
+ // 避免在 React 状态提交前重复派发拖拽事件。
45
+ if (handle.style.left !== '')
46
+ return;
47
+ }
48
+ if (userCommitPending) {
49
+ userCommitPending = false;
50
+ desiredShare = share;
51
+ writeStoredShare(storage, share);
52
+ return;
53
+ }
54
+ // 宿主布局可能在插件恢复后再次渲染默认宽度。用户没有拖拽时,
55
+ // 始终把句柄拉回已保存比例,避免默认值覆盖本地布局。
56
+ if (!userInteracting && desiredShare !== undefined && !sharesMatch(share, desiredShare)) {
57
+ applyShare(handle, desiredShare, win);
58
+ return;
59
+ }
60
+ if (userInteracting)
61
+ return;
62
+ desiredShare = share;
63
+ writeStoredShare(storage, share);
64
+ };
65
+ const scheduleScan = () => {
66
+ if (disposed || scanQueued)
67
+ return;
68
+ scanQueued = true;
69
+ queueMicrotask(() => {
70
+ scanQueued = false;
71
+ scan();
72
+ });
73
+ };
74
+ const observer = dom === undefined || Observer === undefined
75
+ ? undefined
76
+ : new Observer(scheduleScan);
77
+ if (observer !== undefined && dom !== undefined) {
78
+ observer.observe(dom.documentElement ?? dom, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
79
+ }
80
+ const onPointerStart = () => {
81
+ if (applying)
82
+ return;
83
+ userInteracting = true;
84
+ };
85
+ const onPointerEnd = () => {
86
+ if (applying)
87
+ return;
88
+ userInteracting = false;
89
+ userCommitPending = true;
90
+ scheduleScan();
91
+ };
92
+ const onResize = () => {
93
+ if (disposed || win === undefined || dom === undefined)
94
+ return;
95
+ const handle = findRightbarHandle(dom);
96
+ const savedShare = readStoredShare(storage);
97
+ if (handle !== undefined && savedShare !== undefined)
98
+ applyShare(handle, savedShare, win);
99
+ scheduleScan();
100
+ };
101
+ dom?.addEventListener('mousedown', onPointerStart, true);
102
+ dom?.addEventListener('pointerdown', onPointerStart, true);
103
+ dom?.addEventListener('mouseup', onPointerEnd, true);
104
+ dom?.addEventListener('pointerup', onPointerEnd, true);
105
+ win?.addEventListener('resize', onResize);
106
+ scheduleScan();
107
+ return {
108
+ refresh: scheduleScan,
109
+ dispose() {
110
+ if (disposed)
111
+ return;
112
+ disposed = true;
113
+ observer?.disconnect();
114
+ dom?.removeEventListener('mousedown', onPointerStart, true);
115
+ dom?.removeEventListener('pointerdown', onPointerStart, true);
116
+ dom?.removeEventListener('mouseup', onPointerEnd, true);
117
+ dom?.removeEventListener('pointerup', onPointerEnd, true);
118
+ win?.removeEventListener('resize', onResize);
119
+ },
120
+ };
121
+ function applyShare(handle, share, targetWindow) {
122
+ const containerWidth = resolveContainerWidth(handle, targetWindow);
123
+ const rightWidth = containerWidth * share;
124
+ const container = resolveContainerRect(handle, targetWindow.innerWidth);
125
+ const targetClientX = container.left + containerWidth - rightWidth;
126
+ applying = true;
127
+ pendingShare = share;
128
+ try {
129
+ // 右侧栏宽度通常由宿主 React 的拖拽状态驱动。模拟一次原生拖拽,
130
+ // 让宿主自己更新面板和调整线,避免只移动句柄而面板状态仍是默认值。
131
+ const nativeResizeDispatched = dispatchNativeResize(handle, targetWindow, targetClientX);
132
+ if (!nativeResizeDispatched && handle.matches('[data-side="rightbar"]')) {
133
+ // `left` 是相对于 offsetParent 的坐标,不能直接使用视口宽度。
134
+ handle.style.left = `${Math.round(containerWidth - rightWidth)}px`;
135
+ }
136
+ const shell = handle.closest('.workbench-shell');
137
+ if (!nativeResizeDispatched && shell !== null) {
138
+ shell.style.setProperty('--workbench-right-width', `${Math.round(rightWidth)}px`);
139
+ shell.style.setProperty('--workbench-right-current-width', `${Math.round(rightWidth)}px`);
140
+ }
141
+ }
142
+ finally {
143
+ applying = false;
144
+ }
145
+ }
146
+ }
147
+ function dispatchNativeResize(handle, targetWindow, targetClientX) {
148
+ const targetDocument = targetWindow.document;
149
+ if (targetDocument === undefined || typeof handle.dispatchEvent !== 'function')
150
+ return false;
151
+ const currentClientX = handle.getBoundingClientRect().left;
152
+ const PointerEventCtor = targetWindow.PointerEvent;
153
+ const canCapture = typeof handle.setPointerCapture === 'function';
154
+ if (typeof PointerEventCtor === 'function' && canCapture && Number.isFinite(currentClientX)) {
155
+ const pointerId = 2147483000;
156
+ const eventInit = (clientX, type) => ({
157
+ bubbles: true,
158
+ cancelable: true,
159
+ clientX,
160
+ pointerId,
161
+ pointerType: 'mouse',
162
+ isPrimary: true,
163
+ button: type === 'pointerdown' ? 0 : -1,
164
+ buttons: type === 'pointerup' ? 0 : 1,
165
+ });
166
+ // 合成 PointerEvent 不属于真实硬件指针,宿主直接调用
167
+ // setPointerCapture(pointerId) 会在 Chromium 中抛 NotFoundError。
168
+ // 宿主的拖拽逻辑只依赖捕获后的 id 和后续事件,因此临时屏蔽捕获调用即可。
169
+ const captureTarget = handle;
170
+ const ownCapture = Object.getOwnPropertyDescriptor(handle, 'setPointerCapture');
171
+ let capturePatched = false;
172
+ try {
173
+ Object.defineProperty(handle, 'setPointerCapture', {
174
+ configurable: true,
175
+ writable: true,
176
+ value: () => undefined,
177
+ });
178
+ capturePatched = true;
179
+ }
180
+ catch {
181
+ return false;
182
+ }
183
+ try {
184
+ handle.dispatchEvent(new PointerEventCtor('pointerdown', eventInit(currentClientX, 'pointerdown')));
185
+ handle.dispatchEvent(new PointerEventCtor('pointermove', eventInit(targetClientX, 'pointermove')));
186
+ handle.dispatchEvent(new PointerEventCtor('pointerup', eventInit(targetClientX, 'pointerup')));
187
+ }
188
+ catch {
189
+ return false;
190
+ }
191
+ finally {
192
+ if (capturePatched) {
193
+ if (ownCapture === undefined)
194
+ Reflect.deleteProperty(handle, 'setPointerCapture');
195
+ else
196
+ Object.defineProperty(handle, 'setPointerCapture', ownCapture);
197
+ }
198
+ }
199
+ return true;
200
+ }
201
+ const MouseEventCtor = targetWindow.MouseEvent;
202
+ if (typeof MouseEventCtor !== 'function' || !Number.isFinite(currentClientX))
203
+ return false;
204
+ handle.dispatchEvent(new MouseEventCtor('mousedown', { bubbles: true, clientX: currentClientX }));
205
+ targetDocument.dispatchEvent(new MouseEventCtor('mousemove', { bubbles: true, clientX: targetClientX }));
206
+ targetDocument.dispatchEvent(new MouseEventCtor('mouseup', { bubbles: true, clientX: targetClientX }));
207
+ return false;
208
+ }
209
+ function findRightbarHandle(dom) {
210
+ return dom.querySelector(RIGHTBAR_HANDLE_SELECTOR) ?? undefined;
211
+ }
212
+ function readRightbarShare(handle, win) {
213
+ const viewportWidth = win.innerWidth;
214
+ if (!Number.isFinite(viewportWidth) || viewportWidth <= 0)
215
+ return undefined;
216
+ const containerWidth = resolveContainerWidth(handle, win);
217
+ const rightWidth = resolveRightbarWidth(handle, viewportWidth);
218
+ if (!Number.isFinite(containerWidth) || containerWidth <= 0 || !Number.isFinite(rightWidth) || rightWidth <= 0)
219
+ return undefined;
220
+ return clamp(rightWidth / containerWidth, MIN_RIGHTBAR_SHARE, MAX_RIGHTBAR_SHARE);
221
+ }
222
+ function resolveRightbarWidth(handle, viewportWidth) {
223
+ const containerWidth = resolveContainerWidth(handle, { innerWidth: viewportWidth });
224
+ const inlineLeft = Number.parseFloat(handle.style.left);
225
+ if (Number.isFinite(inlineLeft))
226
+ return containerWidth - inlineLeft;
227
+ const shell = handle.closest('.workbench-shell');
228
+ const cssWidth = shell?.style.getPropertyValue('--workbench-right-width');
229
+ const configuredWidth = cssWidth === undefined ? Number.NaN : Number.parseFloat(cssWidth);
230
+ if (Number.isFinite(configuredWidth) && configuredWidth > 0)
231
+ return configuredWidth;
232
+ const panel = shell?.querySelector('.workbench-auxiliary');
233
+ const measuredWidth = panel?.getBoundingClientRect().width ?? 0;
234
+ if (measuredWidth > 0)
235
+ return measuredWidth;
236
+ const boundary = handle.getBoundingClientRect().left;
237
+ const container = resolveContainerRect(handle, viewportWidth);
238
+ return boundary > container.left ? container.right - boundary : 0;
239
+ }
240
+ function resolveContainerWidth(handle, targetWindow) {
241
+ return Math.max(1, resolveContainerRect(handle, targetWindow.innerWidth).width);
242
+ }
243
+ function resolveContainerRect(handle, viewportWidth) {
244
+ const positioningParent = handle.offsetParent;
245
+ const parentRect = positioningParent?.getBoundingClientRect();
246
+ if (parentRect !== undefined && parentRect.width > 0) {
247
+ return { left: parentRect.left, right: parentRect.right, width: parentRect.width };
248
+ }
249
+ const shell = handle.closest('.workbench-shell');
250
+ const shellRect = shell?.getBoundingClientRect();
251
+ if (shellRect !== undefined && shellRect.width > 0) {
252
+ return { left: shellRect.left, right: shellRect.right, width: shellRect.width };
253
+ }
254
+ return { left: 0, right: viewportWidth, width: viewportWidth };
255
+ }
256
+ function resolveStorage() {
257
+ try {
258
+ return typeof localStorage === 'undefined' ? undefined : localStorage;
259
+ }
260
+ catch {
261
+ return undefined;
262
+ }
263
+ }
264
+ function readStoredShare(storage) {
265
+ if (storage === undefined)
266
+ return undefined;
267
+ try {
268
+ const value = Number.parseFloat(storage.getItem(WORKSPACE_RIGHTBAR_RATIO_STORAGE_KEY) ?? '');
269
+ return Number.isFinite(value) ? clamp(value, MIN_RIGHTBAR_SHARE, MAX_RIGHTBAR_SHARE) : undefined;
270
+ }
271
+ catch {
272
+ return undefined;
273
+ }
274
+ }
275
+ function writeStoredShare(storage, share) {
276
+ if (storage === undefined)
277
+ return;
278
+ try {
279
+ storage.setItem(WORKSPACE_RIGHTBAR_RATIO_STORAGE_KEY, String(Number(share.toFixed(6))));
280
+ }
281
+ catch {
282
+ // 隐私模式或受限 iframe 中本地存储不可用时,仅失去跨刷新记忆。
283
+ }
284
+ }
285
+ function clamp(value, min, max) {
286
+ return Math.max(min, Math.min(max, value));
287
+ }
288
+ function sharesMatch(left, right) {
289
+ return Math.abs(left - right) < 0.002;
290
+ }
291
+ //# sourceMappingURL=workspace-session-rightbar-dom.js.map
@@ -1,18 +1,31 @@
1
- import { accepted } from '../settings-store.js';
1
+ import { accepted, sameSettingsSnapshot } from '../settings-store.js';
2
2
  import { debugInfo, debugWarn } from '../../shared/debug.js';
3
+ /**
4
+ * 未暴露 ConfigForm(memory 模式、命名空间未下发或版本缺少该接口)时的占位快照。
5
+ *
6
+ * 必须是稳定引用:设置页用 `useSyncExternalStore` 读取它,每次返回新对象会让
7
+ * React 每帧比对失败并持续强制渲染,最终以 React #185 崩溃整个设置分区。
8
+ */
9
+ const UNAVAILABLE_FORM_SNAPSHOT = Object.freeze({
10
+ value: undefined,
11
+ revision: undefined,
12
+ writable: false,
13
+ status: 'unavailable',
14
+ });
3
15
  /** 0.1.7 Client ConfigForm 路由适配器。 */
4
16
  export function createConfigFormSettingsStore(forms, namespace, options = {}) {
5
17
  const form = forms.get(namespace);
6
18
  if (form === undefined) {
7
19
  return {
8
- getSnapshot: () => ({ value: undefined, revision: undefined, writable: false, status: 'unavailable' }),
20
+ getSnapshot: () => UNAVAILABLE_FORM_SNAPSHOT,
9
21
  subscribe: () => () => undefined,
10
22
  mutate: async () => false,
11
23
  set: async () => false,
12
24
  unset: async () => false,
13
25
  };
14
26
  }
15
- let snapshot = toStoreSnapshot(form.getSnapshot());
27
+ const hostWriterAvailable = options.writeUnfenced !== undefined;
28
+ let snapshot = toStoreSnapshot(form.getSnapshot(), hostWriterAvailable);
16
29
  debugInfo('codingns4dsh: client config form selected', {
17
30
  namespace,
18
31
  status: snapshot.status,
@@ -21,12 +34,14 @@ export function createConfigFormSettingsStore(forms, namespace, options = {}) {
21
34
  });
22
35
  const listeners = new Set();
23
36
  const refresh = () => {
24
- const next = toStoreSnapshot(form.getSnapshot());
37
+ const next = toStoreSnapshot(form.getSnapshot(), hostWriterAvailable);
25
38
  // 自有 Host RPC 的写入答复可能先于原生 mirror 事件到达;旧 revision
26
39
  // 只能被丢弃,不能把刚接受的模块开关覆盖回去。
27
40
  if (snapshot.revision !== undefined && next.revision !== undefined && next.revision < snapshot.revision)
28
41
  return;
29
- if (sameSnapshot(snapshot, next))
42
+ // ConfigForm 每次读取都可能返回新对象;内容未变化时保持同一引用,
43
+ // 否则订阅方(useSyncExternalStore)会被无意义地唤醒并反复渲染。
44
+ if (sameSettingsSnapshot(snapshot, next))
30
45
  return;
31
46
  snapshot = next;
32
47
  for (const listener of [...listeners])
@@ -40,7 +55,7 @@ export function createConfigFormSettingsStore(forms, namespace, options = {}) {
40
55
  writable: true,
41
56
  status: 'ready',
42
57
  };
43
- if (sameSnapshot(snapshot, next))
58
+ if (sameSettingsSnapshot(snapshot, next))
44
59
  return true;
45
60
  snapshot = next;
46
61
  for (const listener of [...listeners])
@@ -97,12 +112,14 @@ async function writeWithRetry(first, retry) {
97
112
  debugInfo('codingns4dsh: client config form write retry completed', { accepted: retried });
98
113
  return retried;
99
114
  }
100
- function toStoreSnapshot(snapshot) {
115
+ function toStoreSnapshot(snapshot, hostWriterAvailable = false) {
101
116
  const value = toClientValue(snapshot.value);
102
117
  return {
103
118
  value: value,
104
119
  revision: snapshot.revision,
105
- writable: snapshot.writable,
120
+ // 0.1.7 ConfigForm 的 writable 只描述原生表单镜像;插件自己的 Host
121
+ // RPC 是实际写入边界,因此存在该 writer 时不能把开关误判为只读。
122
+ writable: snapshot.writable || hostWriterAvailable,
106
123
  status: snapshot.status ?? 'ready',
107
124
  };
108
125
  }
@@ -111,29 +128,4 @@ function toClientValue(value) {
111
128
  ? undefined
112
129
  : (({ cliSessions: _cliSessions, ...clientValue }) => clientValue)(value);
113
130
  }
114
- function sameSnapshot(left, right) {
115
- return sameConfigValue(left.value, right.value)
116
- && left.revision === right.revision
117
- && left.writable === right.writable
118
- && left.status === right.status;
119
- }
120
- /** ConfigForm 可能在每次读取时返回新对象;按配置内容比较,避免无意义地唤醒所有消费者。 */
121
- function sameConfigValue(left, right) {
122
- if (Object.is(left, right))
123
- return true;
124
- if (left === null || right === null || typeof left !== 'object' || typeof right !== 'object')
125
- return false;
126
- if (Array.isArray(left) || Array.isArray(right)) {
127
- if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length)
128
- return false;
129
- return left.every((value, index) => sameConfigValue(value, right[index]));
130
- }
131
- const leftRecord = left;
132
- const rightRecord = right;
133
- const leftKeys = Object.keys(leftRecord);
134
- const rightKeys = Object.keys(rightRecord);
135
- if (leftKeys.length !== rightKeys.length)
136
- return false;
137
- return leftKeys.every((key) => Object.prototype.hasOwnProperty.call(rightRecord, key) && sameConfigValue(leftRecord[key], rightRecord[key]));
138
- }
139
131
  //# sourceMappingURL=config-forms-adapter.js.map
@@ -1,7 +1,13 @@
1
1
  import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
2
2
  import type { CodingNsSettings } from '../../shared/contracts/config.js';
3
3
  import { type CodingNsSettingsOperation, type CodingNsSettingsSnapshot, type CodingNsSettingsStore } from '../settings-store.js';
4
- /** 旧版 Client SettingsScope 到 Codingns4DSH 内部设置接口的适配器。 */
4
+ /**
5
+ * 旧版 Client SettingsScope 到 Codingns4DSH 内部设置接口的适配器。
6
+ *
7
+ * SettingsScope 每次读取都可能返回新对象,而设置页通过 `useSyncExternalStore`
8
+ * 读取快照。这里缓存上一次的快照,内容不变时不替换引用、不唤醒订阅者,避免
9
+ * React 反复强制渲染(React #185)。
10
+ */
5
11
  export declare function createLegacyClientSettingsStore(scope: SettingsScope<CodingNsSettings>): CodingNsSettingsStore<CodingNsSettings>;
6
12
  export type CodingNsClientSettingsStore = CodingNsSettingsStore<CodingNsSettings>;
7
13
  export type { CodingNsSettingsOperation, CodingNsSettingsSnapshot };
@@ -1,16 +1,39 @@
1
- import { accepted } from '../settings-store.js';
2
- /** 旧版 Client SettingsScope 到 Codingns4DSH 内部设置接口的适配器。 */
1
+ import { accepted, sameSettingsSnapshot, } from '../settings-store.js';
2
+ /**
3
+ * 旧版 Client SettingsScope 到 Codingns4DSH 内部设置接口的适配器。
4
+ *
5
+ * SettingsScope 每次读取都可能返回新对象,而设置页通过 `useSyncExternalStore`
6
+ * 读取快照。这里缓存上一次的快照,内容不变时不替换引用、不唤醒订阅者,避免
7
+ * React 反复强制渲染(React #185)。
8
+ */
3
9
  export function createLegacyClientSettingsStore(scope) {
4
- const store = {
5
- getSnapshot: () => {
6
- const snapshot = scope.getSnapshot();
7
- return { value: snapshot.value, revision: snapshot.revision, writable: snapshot.writable, status: snapshot.status };
10
+ const listeners = new Set();
11
+ let snapshot = fromScopeSnapshot(scope.getSnapshot());
12
+ const refresh = () => {
13
+ const next = fromScopeSnapshot(scope.getSnapshot());
14
+ if (sameSettingsSnapshot(snapshot, next))
15
+ return;
16
+ snapshot = next;
17
+ for (const listener of [...listeners])
18
+ listener();
19
+ };
20
+ const unsubscribeScope = scope.subscribe(refresh);
21
+ return {
22
+ getSnapshot: () => snapshot,
23
+ subscribe: (listener) => {
24
+ listeners.add(listener);
25
+ return () => listeners.delete(listener);
26
+ },
27
+ mutate: async (operations, revision) => accepted(scope.mutate(operations, revision)),
28
+ set: async (field, value) => accepted(scope.set(field, value)),
29
+ unset: async (field) => accepted(scope.unset(field)),
30
+ dispose: () => {
31
+ unsubscribeScope();
32
+ listeners.clear();
8
33
  },
9
- subscribe: (listener) => scope.subscribe(listener),
10
- mutate: (operations, revision) => accepted(scope.mutate(operations, revision)),
11
- set: (field, value) => accepted(scope.set(field, value)),
12
- unset: (field) => accepted(scope.unset(field)),
13
34
  };
14
- return store;
35
+ }
36
+ function fromScopeSnapshot(snapshot) {
37
+ return { value: snapshot.value, revision: snapshot.revision, writable: snapshot.writable, status: snapshot.status };
15
38
  }
16
39
  //# sourceMappingURL=settings-scope-adapter.js.map
@@ -15,6 +15,15 @@ export const DSH_CAPABILITY_MATRIX = [
15
15
  route('conversation.tool-call', 'conversation-events', 'client', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['client/external-tool-stream.ts']),
16
16
  route('sidebar.right', 'sidebar-right-tabs', 'client', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['client/terminal/ui.ts']),
17
17
  route('typert.remote', 'remote-result', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/terminal/terminal-controller.ts', 'client/terminal/model.ts']),
18
+ route('peer-host.store', 'peer-host-store', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/features/peer-host.ts']),
19
+ route('peer-host.handshake', 'peer-host-handshake', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/features/peer-host.ts']),
20
+ route('peer-host.http-proxy', 'peer-host-http-proxy', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/features/peer-host.ts']),
21
+ route('peer-host.ws-proxy', 'peer-host-ws-proxy', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/features/peer-host.ts']),
22
+ route('peer-host.aggregate', 'peer-host-aggregate', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/features/peer-host.ts']),
23
+ route('peer-host.relay-route', 'peer-host-relay-route', 'host', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['host/features/peer-host.ts']),
24
+ route('peer-host.native-navigation', 'peer-host-native-navigation-legacy', 'client', '>=0.1.5-rc.3 <=0.1.6', 'deprecated', ['client/features/peer-host.ts']),
25
+ route('peer-host.native-navigation', 'peer-host-native-navigation-modern', 'client', '>=0.1.7-rc.2 <=0.1.7-rc.2', 'supported', ['client/features/peer-host.ts']),
26
+ route('peer-host.remote-web-context-fallback', 'peer-host-remote-web-context-fallback', 'client', '>=0.1.5-rc.3 <=0.1.7-rc.2', 'supported', ['client/features/peer-host.ts']),
18
27
  ];
19
28
  function route(capability, routeId, runtime, supportedDsh, status, consumers, replacement, removableAfter) {
20
29
  return {