@jingyi0605/codingns4dsh 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.en.md +306 -0
  2. package/README.md +190 -993
  3. package/data/build/dist/client/account-bar.d.ts +2 -2
  4. package/data/build/dist/client/account-bar.js +96 -39
  5. package/data/build/dist/client/bundle.js +13722 -3611
  6. package/data/build/dist/client/cli-catalog.js +20 -3
  7. package/data/build/dist/client/cli-slots.d.ts +5 -0
  8. package/data/build/dist/client/cli-slots.js +57 -7
  9. package/data/build/dist/client/debug/ui.d.ts +1 -1
  10. package/data/build/dist/client/debug/ui.js +62 -32
  11. package/data/build/dist/client/dsh-runtime-version.d.ts +9 -2
  12. package/data/build/dist/client/dsh-runtime-version.js +24 -2
  13. package/data/build/dist/client/features/cli-adapters.d.ts +1 -1
  14. package/data/build/dist/client/features/cli-adapters.js +19 -16
  15. package/data/build/dist/client/features/debug.js +2 -2
  16. package/data/build/dist/client/features/index.d.ts +2 -1
  17. package/data/build/dist/client/features/index.js +3 -1
  18. package/data/build/dist/client/features/lan-access-panel.d.ts +1 -1
  19. package/data/build/dist/client/features/lan-access-panel.js +8 -10
  20. package/data/build/dist/client/features/lan-access.js +1 -1
  21. package/data/build/dist/client/features/login-protection-panel.d.ts +1 -1
  22. package/data/build/dist/client/features/login-protection-panel.js +7 -9
  23. package/data/build/dist/client/features/reverse-proxy-panel.d.ts +1 -1
  24. package/data/build/dist/client/features/reverse-proxy-panel.js +99 -11
  25. package/data/build/dist/client/features/reverse-proxy.d.ts +1 -1
  26. package/data/build/dist/client/features/reverse-proxy.js +19 -18
  27. package/data/build/dist/client/features/terminal-enhancement-panel.d.ts +1 -1
  28. package/data/build/dist/client/features/terminal-enhancement-panel.js +62 -55
  29. package/data/build/dist/client/features/terminal-enhancement.d.ts +1 -1
  30. package/data/build/dist/client/features/terminal-enhancement.js +3 -3
  31. package/data/build/dist/client/features/types.d.ts +14 -3
  32. package/data/build/dist/client/features/workspace-session-enhancement-panel.d.ts +1 -1
  33. package/data/build/dist/client/features/workspace-session-enhancement-panel.js +31 -9
  34. package/data/build/dist/client/features/workspace-session-enhancement.js +72 -11
  35. package/data/build/dist/client/git-management.d.ts +16 -0
  36. package/data/build/dist/client/git-management.js +757 -0
  37. package/data/build/dist/client/index.d.ts +3 -2
  38. package/data/build/dist/client/index.js +152 -27
  39. package/data/build/dist/client/locale.js +91 -29
  40. package/data/build/dist/client/quick-phrase-layout.d.ts +49 -0
  41. package/data/build/dist/client/quick-phrase-layout.js +95 -0
  42. package/data/build/dist/client/quick-phrase-slot.d.ts +7 -0
  43. package/data/build/dist/client/quick-phrase-slot.js +370 -0
  44. package/data/build/dist/client/remote-web-context.js +1 -1
  45. package/data/build/dist/client/settings-bridge.d.ts +9 -6
  46. package/data/build/dist/client/settings-bridge.js +51 -27
  47. package/data/build/dist/client/settings-section.d.ts +3 -2
  48. package/data/build/dist/client/settings-section.js +33 -19
  49. package/data/build/dist/client/subscription-slot.d.ts +3 -0
  50. package/data/build/dist/client/subscription-slot.js +108 -16
  51. package/data/build/dist/client/terminal/model.d.ts +14 -2
  52. package/data/build/dist/client/terminal/model.js +109 -21
  53. package/data/build/dist/client/terminal/recovery.d.ts +50 -0
  54. package/data/build/dist/client/terminal/recovery.js +101 -0
  55. package/data/build/dist/client/terminal/ui.d.ts +2 -2
  56. package/data/build/dist/client/terminal/ui.js +49 -11
  57. package/data/build/dist/client/terminal/xterm-view.d.ts +2 -2
  58. package/data/build/dist/client/terminal/xterm-view.js +2 -1
  59. package/data/build/dist/client/theme.d.ts +6 -1
  60. package/data/build/dist/client/theme.js +32 -1
  61. package/data/build/dist/client/workspace-session-archive-dom.d.ts +12 -0
  62. package/data/build/dist/client/workspace-session-archive-dom.js +27 -5
  63. package/data/build/dist/client/workspace-session-visibility-dom.d.ts +30 -0
  64. package/data/build/dist/client/workspace-session-visibility-dom.js +604 -0
  65. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.d.ts +50 -0
  66. package/data/build/dist/dsh-capabilities/client/config-forms-adapter.js +139 -0
  67. package/data/build/dist/dsh-capabilities/client/conversation-adapter.d.ts +7 -0
  68. package/data/build/dist/dsh-capabilities/client/conversation-adapter.js +6 -0
  69. package/data/build/dist/dsh-capabilities/client/locale-adapter.d.ts +9 -0
  70. package/data/build/dist/dsh-capabilities/client/locale-adapter.js +4 -0
  71. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +10 -0
  72. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +14 -0
  73. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.d.ts +8 -0
  74. package/data/build/dist/dsh-capabilities/client/settings-scope-adapter.js +16 -0
  75. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.d.ts +7 -0
  76. package/data/build/dist/dsh-capabilities/client/sidebar-adapter.js +5 -0
  77. package/data/build/dist/dsh-capabilities/client/theme-adapter.d.ts +6 -0
  78. package/data/build/dist/dsh-capabilities/client/theme-adapter.js +4 -0
  79. package/data/build/dist/dsh-capabilities/client/typert-adapter.d.ts +5 -0
  80. package/data/build/dist/dsh-capabilities/client/typert-adapter.js +4 -0
  81. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.d.ts +19 -0
  82. package/data/build/dist/dsh-capabilities/host/config-forms-adapter.js +45 -0
  83. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.d.ts +19 -0
  84. package/data/build/dist/dsh-capabilities/host/connection-rpc-adapter.js +13 -0
  85. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.d.ts +8 -0
  86. package/data/build/dist/dsh-capabilities/host/settings-scope-adapter.js +47 -0
  87. package/data/build/dist/dsh-capabilities/host/typert-adapter.d.ts +6 -0
  88. package/data/build/dist/dsh-capabilities/host/typert-adapter.js +4 -0
  89. package/data/build/dist/dsh-capabilities/index.d.ts +6 -0
  90. package/data/build/dist/dsh-capabilities/index.js +6 -0
  91. package/data/build/dist/dsh-capabilities/matrix.d.ts +15 -0
  92. package/data/build/dist/dsh-capabilities/matrix.js +32 -0
  93. package/data/build/dist/dsh-capabilities/registry.d.ts +22 -0
  94. package/data/build/dist/dsh-capabilities/registry.js +196 -0
  95. package/data/build/dist/dsh-capabilities/routes.d.ts +10 -0
  96. package/data/build/dist/dsh-capabilities/routes.js +34 -0
  97. package/data/build/dist/dsh-capabilities/settings-store.d.ts +29 -0
  98. package/data/build/dist/dsh-capabilities/settings-store.js +5 -0
  99. package/data/build/dist/dsh-capabilities/types.d.ts +50 -0
  100. package/data/build/dist/dsh-capabilities/types.js +23 -0
  101. package/data/build/dist/features/registry.d.ts +8 -2
  102. package/data/build/dist/features/registry.js +104 -1
  103. package/data/build/dist/host/auth-session.d.ts +8 -1
  104. package/data/build/dist/host/auth-session.js +28 -6
  105. package/data/build/dist/host/cli-adapters/claude-driver.d.ts +2 -0
  106. package/data/build/dist/host/cli-adapters/claude-driver.js +19 -4
  107. package/data/build/dist/host/cli-adapters/claude-model-options.d.ts +14 -0
  108. package/data/build/dist/host/cli-adapters/claude-model-options.js +157 -0
  109. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +11 -0
  110. package/data/build/dist/host/cli-adapters/codex-driver.js +166 -14
  111. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  112. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +15 -0
  113. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +120 -29
  114. package/data/build/dist/host/cli-adapters/dsh-tool-history.d.ts +4 -1
  115. package/data/build/dist/host/cli-adapters/dsh-tool-history.js +25 -7
  116. package/data/build/dist/host/cli-adapters/feature.js +74 -8
  117. package/data/build/dist/host/cli-adapters/gemini-driver.js +2 -2
  118. package/data/build/dist/host/cli-adapters/grok-driver.js +2 -2
  119. package/data/build/dist/host/cli-adapters/json-rpc-process.d.ts +4 -2
  120. package/data/build/dist/host/cli-adapters/json-rpc-process.js +8 -9
  121. package/data/build/dist/host/cli-adapters/kimi-driver.js +1 -1
  122. package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts +17 -0
  123. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +88 -0
  124. package/data/build/dist/host/cli-adapters/legacy-session-settings.d.ts +24 -0
  125. package/data/build/dist/host/cli-adapters/legacy-session-settings.js +162 -0
  126. package/data/build/dist/host/cli-adapters/official-provider-subscription.d.ts +29 -0
  127. package/data/build/dist/host/cli-adapters/official-provider-subscription.js +361 -0
  128. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts +2 -0
  129. package/data/build/dist/host/cli-adapters/opencode-driver.js +97 -12
  130. package/data/build/dist/host/cli-adapters/pi-driver.js +69 -23
  131. package/data/build/dist/host/cli-adapters/provider-registry.d.ts +33 -0
  132. package/data/build/dist/host/cli-adapters/provider-registry.js +83 -0
  133. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +25 -3
  134. package/data/build/dist/host/cli-adapters/provider-subscription.js +346 -30
  135. package/data/build/dist/host/cli-adapters/registry.d.ts +6 -0
  136. package/data/build/dist/host/cli-adapters/registry.js +170 -13
  137. package/data/build/dist/host/cli-adapters/rpc-driver-utils.js +10 -0
  138. package/data/build/dist/host/cli-adapters/session-store.d.ts +7 -0
  139. package/data/build/dist/host/cli-adapters/session-store.js +39 -1
  140. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +3 -0
  141. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +3 -0
  142. package/data/build/dist/host/cli-adapters/stream-normalizer.js +22 -2
  143. package/data/build/dist/host/control-api-client.d.ts +3 -3
  144. package/data/build/dist/host/control-api-client.js +2 -1
  145. package/data/build/dist/host/credential-store.js +11 -1
  146. package/data/build/dist/host/dsh-device-runtime.d.ts +4 -0
  147. package/data/build/dist/host/dsh-device-runtime.js +20 -9
  148. package/data/build/dist/host/dsh-runtime-version.d.ts +3 -2
  149. package/data/build/dist/host/dsh-runtime-version.js +71 -2
  150. package/data/build/dist/host/features/auth.js +8 -5
  151. package/data/build/dist/host/features/debug.js +1 -1
  152. package/data/build/dist/host/features/git-management.d.ts +5 -0
  153. package/data/build/dist/host/features/git-management.js +290 -0
  154. package/data/build/dist/host/features/host-status.js +1 -1
  155. package/data/build/dist/host/features/index.d.ts +1 -0
  156. package/data/build/dist/host/features/index.js +3 -0
  157. package/data/build/dist/host/features/terminal-process.js +1 -1
  158. package/data/build/dist/host/features/terminal-status.d.ts +3 -1
  159. package/data/build/dist/host/features/terminal-status.js +3 -1
  160. package/data/build/dist/host/index.d.ts +7 -3
  161. package/data/build/dist/host/index.js +79 -15
  162. package/data/build/dist/host/lan-access-dsh.js +18 -5
  163. package/data/build/dist/host/native-session-bridge.d.ts +23 -1
  164. package/data/build/dist/host/native-session-bridge.js +157 -10
  165. package/data/build/dist/host/rpc-table.d.ts +1 -1
  166. package/data/build/dist/host/rpc.js +60 -10
  167. package/data/build/dist/host/session-migration-repair.d.ts +19 -0
  168. package/data/build/dist/host/session-migration-repair.js +395 -0
  169. package/data/build/dist/host/settings.d.ts +8 -1
  170. package/data/build/dist/host/settings.js +94 -3
  171. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +1 -0
  172. package/data/build/dist/host/terminal/backends/tmux-backend.js +4 -2
  173. package/data/build/dist/host/terminal/controller-factory.d.ts +4 -0
  174. package/data/build/dist/host/terminal/controller-factory.js +26 -7
  175. package/data/build/dist/host/terminal/runtime-manager.d.ts +2 -1
  176. package/data/build/dist/host/terminal/runtime-manager.js +3 -0
  177. package/data/build/dist/host/terminal/startup.d.ts +2 -0
  178. package/data/build/dist/host/terminal/startup.js +2 -1
  179. package/data/build/dist/host/terminal/terminal-controller.d.ts +10 -3
  180. package/data/build/dist/host/terminal/terminal-controller.js +101 -15
  181. package/data/build/dist/host/terminal/terminal-process-service.js +3 -1
  182. package/data/build/dist/host/terminal/terminal-service.d.ts +2 -0
  183. package/data/build/dist/host/terminal/terminal-service.js +10 -0
  184. package/data/build/dist/index.d.ts +6 -2
  185. package/data/build/dist/index.js +8 -2
  186. package/data/build/dist/shared/contracts/cli-adapter.d.ts +14 -0
  187. package/data/build/dist/shared/contracts/config.d.ts +31 -1
  188. package/data/build/dist/shared/contracts/config.js +25 -0
  189. package/data/build/dist/shared/contracts/dsh-device.d.ts +13 -1
  190. package/data/build/dist/shared/contracts/errors.d.ts +1 -1
  191. package/data/build/dist/shared/contracts/feature.d.ts +6 -0
  192. package/data/build/dist/shared/contracts/git.d.ts +82 -0
  193. package/data/build/dist/shared/contracts/git.js +3 -0
  194. package/data/build/dist/shared/contracts/subscription.d.ts +46 -0
  195. package/data/build/dist/shared/contracts/terminal.d.ts +4 -1
  196. package/data/build/dist/shared/contracts/version.d.ts +2 -2
  197. package/data/build/dist/shared/contracts/version.js +4 -6
  198. package/data/build/dist/shared/debug.d.ts +14 -0
  199. package/data/build/dist/shared/debug.js +53 -0
  200. package/data/build/dist/shared/index.d.ts +7 -4
  201. package/data/build/dist/shared/index.js +2 -1
  202. package/data/build/dist/transport/debug.d.ts +1 -1
  203. package/data/build/dist/transport/debug.js +3 -30
  204. package/data/build/dist/transport/dsh-session.js +3 -3
  205. package/data/build/dist/typert.host.d.ts +1 -1
  206. package/data/build/dist/typert.host.js +6 -3
  207. package/data/build/dist/typert.remote-client.d.ts +11 -0
  208. package/data/build/dist/typert.remote-client.js +7 -0
  209. package/dsh.bundle.patch +1 -1
  210. package/package.json +13 -5
  211. package/profile/README.md +4 -4
  212. package/profile/package.json +6 -4
  213. package/profile/scripts/check-dsh-install.mjs +2 -2
  214. package/profile/version.json +2 -2
  215. package/scripts/check-dsh-install.mjs +2 -2
  216. package/version.json +2 -2
@@ -1,5 +1,6 @@
1
1
  import { CodingNsRpcError } from '../rpc-table.js';
2
2
  import { CodingNsCliSessionStore } from './session-store.js';
3
+ import { readLegacyImportedAdapterPreferences } from './legacy-session-settings.js';
3
4
  const DEFAULT_INSTALLED_CACHE_TTL_MS = 5 * 60_000;
4
5
  const DEFAULT_UNINSTALLED_CACHE_TTL_MS = 30_000;
5
6
  const DEFAULT_MODEL_CACHE_TTL_MS = 10 * 60_000;
@@ -28,6 +29,8 @@ export class CodingNsCliAdapterRegistry {
28
29
  modelTimers = new Map();
29
30
  modelGenerations = new Map();
30
31
  requestedModelCatalogs = new Set();
32
+ /** 已切到下一个 DSH step、但仍在继续产出 Provider 流的驱动迭代器。 */
33
+ segmentedTurns = new Map();
31
34
  cacheGeneration = 0;
32
35
  disposed = false;
33
36
  constructor(drivers, enabled = {}, options = {}) {
@@ -42,7 +45,15 @@ export class CodingNsCliAdapterRegistry {
42
45
  this.uninstalledCacheTtlMs = positiveTtl(options.uninstalledCacheTtlMs, DEFAULT_UNINSTALLED_CACHE_TTL_MS);
43
46
  this.modelCacheTtlMs = positiveTtl(options.modelCacheTtlMs, DEFAULT_MODEL_CACHE_TTL_MS);
44
47
  this.modelRetryTtlMs = positiveTtl(options.modelRetryTtlMs, DEFAULT_MODEL_RETRY_TTL_MS);
45
- this.syncPreferences(options.settings?.get().agentAdapterPreferences);
48
+ const configuredPreferences = options.settings?.get().agentAdapterPreferences;
49
+ const legacyPreferences = options.settings === undefined ? {} : readLegacyImportedAdapterPreferences();
50
+ const mergedPreferences = mergePreferenceRecords(legacyPreferences, configuredPreferences);
51
+ this.syncPreferences(mergedPreferences);
52
+ // DSH 0.1.7 不会把旧 `codingns` 设置段自动映射到 scoped Config entry。
53
+ // 先用旧值恢复当前进程,再把缺失值写入新配置,后续重启即可走正常路径。
54
+ if (options.settings !== undefined && hasMissingPreferences(configuredPreferences, legacyPreferences)) {
55
+ void options.settings.update({ agentAdapterPreferences: preferenceSnapshot(this.preferences) }).catch(() => undefined);
56
+ }
46
57
  for (const driver of drivers) {
47
58
  if (this.drivers.has(driver.descriptor.id))
48
59
  throw new Error(`重复 Agent: ${driver.descriptor.id}`);
@@ -137,6 +148,11 @@ export class CodingNsCliAdapterRegistry {
137
148
  : remembered?.effortId
138
149
  ? { effortId: remembered.effortId }
139
150
  : {}),
151
+ ...(config.providerId?.trim()
152
+ ? { providerId: config.providerId.trim() }
153
+ : sameAdapter && previous?.providerId
154
+ ? { providerId: previous.providerId }
155
+ : {}),
140
156
  ...(providerSessionId ? { providerSessionId } : sameAdapter && previous?.providerSessionId ? { providerSessionId: previous.providerSessionId } : {}),
141
157
  ...(config.rawStoreRef?.trim()
142
158
  ? { rawStoreRef: config.rawStoreRef.trim() }
@@ -153,6 +169,10 @@ export class CodingNsCliAdapterRegistry {
153
169
  syncPreferences(value) {
154
170
  if (value === undefined)
155
171
  return;
172
+ // 0.1.7 ConfigForms 在 entry 重新描述的瞬间可能只返回默认空字典;
173
+ // 不能让这次短暂快照抹掉当前进程已经恢复的选择。
174
+ if (Object.keys(value).length === 0 && this.preferences.size > 0)
175
+ return;
156
176
  this.preferences.clear();
157
177
  for (const [adapterId, preference] of Object.entries(value)) {
158
178
  const modelId = preference?.modelId?.trim();
@@ -194,19 +214,24 @@ export class CodingNsCliAdapterRegistry {
194
214
  this.preferences.set(adapterId, preference);
195
215
  if (this.settings === undefined)
196
216
  return;
197
- const snapshot = Object.fromEntries([...this.preferences.entries()].map(([id, value]) => [id, { ...value }]));
217
+ const snapshot = preferenceSnapshot(this.preferences);
198
218
  void this.settings.update({ agentAdapterPreferences: snapshot }).catch(() => undefined);
199
219
  }
200
220
  getSession(sessionId) {
201
221
  const session = this.sessions.get(sessionId);
202
- if (session !== undefined && (session.adapterId === 'dsh' || this.isEnabled(session.adapterId)))
203
- return session;
222
+ if (session !== undefined && (session.adapterId === 'dsh' || this.isEnabled(session.adapterId))) {
223
+ return session.adapterId === 'dsh' ? mergeDshNativeSelection(session, this.nativeSessions?.get(sessionId)) : session;
224
+ }
204
225
  const remembered = this.preferences.get('dsh') ?? this.findRememberedPreference('dsh');
205
- return {
226
+ return mergeDshNativeSelection({
206
227
  adapterId: 'dsh',
207
228
  ...(remembered?.modelId ? { modelId: remembered.modelId } : {}),
208
229
  ...(remembered?.effortId ? { effortId: remembered.effortId } : {}),
209
- };
230
+ }, this.nativeSessions?.get(sessionId));
231
+ }
232
+ /** 只有驱动自己维护 Provider turn 边界时,Host 才能把它映射到 DSH step。 */
233
+ supportsSegmentedTurns(adapterId) {
234
+ return this.drivers.get(adapterId)?.supportsSegmentedTurns === true;
210
235
  }
211
236
  async *execute(input) {
212
237
  const driver = this.requireEnabledDriver(input.adapterId);
@@ -237,13 +262,15 @@ export class CodingNsCliAdapterRegistry {
237
262
  throw new CodingNsRpcError('CODINGNS_CLI_SESSION_MISSING', '外部 Agent 原始会话已删除,无法继续恢复');
238
263
  }
239
264
  }
265
+ const suspended = this.segmentedTurns.get(input.sessionId);
266
+ const resumingSegmentedTurn = suspended?.adapterId === input.adapterId;
240
267
  this.sessions.set(input.sessionId, current);
241
268
  this.sessionStore?.upsert(input.sessionId, {
242
269
  ...current,
243
270
  adapterId: input.adapterId,
244
271
  ...(input.cwd ? { cwd: input.cwd } : {}),
245
272
  status: 'active',
246
- title: input.prompt,
273
+ ...(resumingSegmentedTurn ? {} : { title: input.prompt }),
247
274
  });
248
275
  // Agent Loop 通常已经创建了同名 DSH 原生会话;直接调用 Registry 时才按需补建。
249
276
  // 原生服务失败不能阻断外部 Agent,消息仍由现有 llm/stream 链路处理。
@@ -251,11 +278,31 @@ export class CodingNsCliAdapterRegistry {
251
278
  await this.nativeSessions?.ensure(input.sessionId, input.cwd);
252
279
  }
253
280
  catch { /* 可选服务降级 */ }
254
- for await (const event of driver.executeTurn(input)) {
281
+ // 每轮都写入稳定的适配器身份。旧日志只有通用 codingns-external 标记,
282
+ // 这条 request/context 是升级后自动迁移时唯一可靠的回填依据。
283
+ try {
284
+ this.nativeSessions?.appendRequestContext?.(input.sessionId, {
285
+ provider: input.adapterId,
286
+ model: input.modelId ?? input.adapterId,
287
+ });
288
+ }
289
+ catch { /* 原生历史写入失败不应阻断外部 Agent */ }
290
+ if (suspended !== undefined && suspended.adapterId !== input.adapterId) {
291
+ this.segmentedTurns.delete(input.sessionId);
292
+ await closeAgentIterator(suspended.iterator);
293
+ }
294
+ const iterator = suspended?.adapterId === input.adapterId
295
+ ? (this.segmentedTurns.delete(input.sessionId), suspended.iterator)
296
+ : driver.executeTurn(input)[Symbol.asyncIterator]();
297
+ while (true) {
298
+ const next = await iterator.next();
299
+ if (next.done)
300
+ break;
301
+ const event = next.value;
255
302
  if (event.type === 'session-binding') {
256
303
  const providerIdentityChanged = event.providerSessionId !== current.providerSessionId;
257
304
  const { rawStoreRef: previousRawStoreRef, ...currentWithoutRawStoreRef } = current;
258
- const next = {
305
+ const nextConfig = {
259
306
  ...currentWithoutRawStoreRef,
260
307
  providerSessionId: event.providerSessionId,
261
308
  ...(event.rawStoreRef
@@ -264,19 +311,40 @@ export class CodingNsCliAdapterRegistry {
264
311
  ? { rawStoreRef: previousRawStoreRef }
265
312
  : {}),
266
313
  };
267
- this.sessions.set(input.sessionId, next);
268
- current = next;
314
+ this.sessions.set(input.sessionId, nextConfig);
315
+ current = nextConfig;
269
316
  this.sessionStore?.upsert(input.sessionId, {
270
- ...next,
317
+ ...nextConfig,
271
318
  status: 'active',
272
319
  providerState: 'available',
273
320
  providerCheckedAt: new Date().toISOString(),
274
321
  });
275
322
  }
276
- if (event.type === 'finish')
323
+ if (event.type === 'finish') {
324
+ this.segmentedTurns.delete(input.sessionId);
277
325
  this.sessionStore?.upsert(input.sessionId, { ...this.sessions.get(input.sessionId) ?? current, status: event.reason === 'error' ? 'error' : 'idle' });
326
+ }
327
+ // Codex 等驱动已经把同一个 Provider turn 切成了多个段。边界本身
328
+ // 直接交给 Feature,不能再次按工具完成事件切一遍。
329
+ if (event.type === 'step-boundary') {
330
+ await closeAgentIterator(iterator);
331
+ yield event;
332
+ return;
333
+ }
334
+ if (input.splitToolSteps
335
+ && driver.supportsSegmentedTurns !== true
336
+ && event.type === 'tool-event'
337
+ && (event.status === 'completed' || event.status === 'failed')) {
338
+ // 先保存迭代器再 yield。即使调用方在工具事件后提前关闭流,
339
+ // 下一次 DSH step 仍能从同一个 Provider 进程继续读取正文。
340
+ this.segmentedTurns.set(input.sessionId, { adapterId: input.adapterId, iterator });
341
+ yield event;
342
+ yield { type: 'step-boundary' };
343
+ return;
344
+ }
278
345
  yield event;
279
346
  }
347
+ this.segmentedTurns.delete(input.sessionId);
280
348
  }
281
349
  catch (error) {
282
350
  this.sessionStore?.upsert(input.sessionId, {
@@ -359,6 +427,14 @@ export class CodingNsCliAdapterRegistry {
359
427
  throw new CodingNsRpcError('CODINGNS_CLI_UNSUPPORTED', '当前 Agent 不支持中断');
360
428
  await driver.interrupt(sessionId);
361
429
  }
430
+ /** 丢弃等待下一步的 Provider 流,避免取消后的旧正文进入新回合。 */
431
+ discardSegmentedTurn(sessionId) {
432
+ const suspended = this.segmentedTurns.get(sessionId);
433
+ if (suspended === undefined)
434
+ return;
435
+ this.segmentedTurns.delete(sessionId);
436
+ void closeAgentIterator(suspended.iterator);
437
+ }
362
438
  async dispose() {
363
439
  if (this.disposed)
364
440
  return;
@@ -371,6 +447,8 @@ export class CodingNsCliAdapterRegistry {
371
447
  this.modelFailures.clear();
372
448
  this.modelGenerations.clear();
373
449
  this.requestedModelCatalogs.clear();
450
+ await Promise.all([...this.segmentedTurns.values()].map(({ iterator }) => closeAgentIterator(iterator)));
451
+ this.segmentedTurns.clear();
374
452
  await Promise.all([...this.drivers.values()].map((driver) => driver.dispose?.()));
375
453
  }
376
454
  async readDetection(driver) {
@@ -631,6 +709,12 @@ function providerProbeKey(record) {
631
709
  function delay(milliseconds) {
632
710
  return milliseconds <= 0 ? Promise.resolve() : new Promise((resolve) => setTimeout(resolve, milliseconds));
633
711
  }
712
+ async function closeAgentIterator(iterator) {
713
+ try {
714
+ await iterator.return?.();
715
+ }
716
+ catch { /* 驱动清理失败不能阻断 Registry 释放 */ }
717
+ }
634
718
  async function forEachConcurrent(values, concurrency, task) {
635
719
  let nextIndex = 0;
636
720
  const workerCount = Math.min(concurrency, values.length);
@@ -652,6 +736,79 @@ function detectionFingerprint(detection) {
652
736
  function catalogHasModels(catalog) {
653
737
  return catalog.groups.some((group) => group.models.length > 0);
654
738
  }
739
+ function mergePreferenceRecords(legacy, configured) {
740
+ const merged = {};
741
+ for (const [adapterId, preference] of Object.entries(legacy)) {
742
+ const normalized = normalizePreference(preference);
743
+ if (normalized !== undefined)
744
+ merged[adapterId] = normalized;
745
+ }
746
+ for (const [adapterId, preference] of Object.entries(configured ?? {})) {
747
+ const normalized = normalizePreference(preference);
748
+ if (normalized === undefined)
749
+ continue;
750
+ merged[adapterId] = { ...merged[adapterId], ...normalized };
751
+ }
752
+ return merged;
753
+ }
754
+ function normalizePreference(value) {
755
+ if (value === undefined)
756
+ return undefined;
757
+ const modelId = value.modelId?.trim();
758
+ const effortId = value.effortId?.trim();
759
+ if (modelId === undefined && effortId === undefined)
760
+ return undefined;
761
+ return {
762
+ ...(modelId ? { modelId } : {}),
763
+ ...(effortId ? { effortId } : {}),
764
+ };
765
+ }
766
+ function hasMissingPreferences(configured, legacy) {
767
+ for (const [adapterId, legacyPreference] of Object.entries(legacy)) {
768
+ const current = configured?.[adapterId];
769
+ if (legacyPreference.modelId !== undefined && !hasText(current?.modelId))
770
+ return true;
771
+ if (legacyPreference.effortId !== undefined && !hasText(current?.effortId))
772
+ return true;
773
+ }
774
+ return false;
775
+ }
776
+ function hasText(value) {
777
+ return value?.trim() !== '' && value !== undefined;
778
+ }
779
+ function preferenceSnapshot(preferences) {
780
+ return Object.fromEntries([...preferences.entries()].map(([adapterId, preference]) => [adapterId, { ...preference }]));
781
+ }
782
+ /** 从 DSH 原生会话快照补齐当前模型提供商,供订阅分流使用。 */
783
+ function mergeDshNativeSelection(config, session) {
784
+ if (config.adapterId !== 'dsh' || config.providerId !== undefined && config.modelId !== undefined)
785
+ return config;
786
+ const root = asRecord(session);
787
+ const rows = asRecord(root?.record)?.rows;
788
+ const selectionRow = asRecord(asRecord(rows)?.modelSelection);
789
+ const selection = asRecord(root?.modelSelection) ?? selectionRow;
790
+ const value = asRecord(selection?.val) ?? selection;
791
+ const lastUsed = asRecord(value?.lastUsed);
792
+ const next = asRecord(value?.next);
793
+ const candidate = lastUsed ?? next;
794
+ if (candidate === null)
795
+ return config;
796
+ const providerId = stringValue(candidate.provider);
797
+ const modelId = stringValue(candidate.model);
798
+ const effortId = stringValue(candidate.reasoningEffort);
799
+ return {
800
+ ...config,
801
+ ...(config.providerId === undefined && providerId !== undefined ? { providerId } : {}),
802
+ ...(config.modelId === undefined && modelId !== undefined ? { modelId } : {}),
803
+ ...(config.effortId === undefined && effortId !== undefined ? { effortId } : {}),
804
+ };
805
+ }
806
+ function asRecord(value) {
807
+ return typeof value === 'object' && value !== null && !Array.isArray(value) ? value : null;
808
+ }
809
+ function stringValue(value) {
810
+ return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
811
+ }
655
812
  function unrefTimer(timer) {
656
813
  const nodeTimer = timer;
657
814
  nodeTimer.unref?.();
@@ -92,6 +92,13 @@ export function usageChunk(value) {
92
92
  if (inputTokens === 0 && outputTokens === 0 && cacheReadTokens === undefined && cacheWriteTokens === undefined)
93
93
  return null;
94
94
  const totalTokens = optionalNumberValue(usage.totalTokens ?? usage.total_tokens ?? usage.totalTokenCount);
95
+ const contextWindow = optionalNumberValue(usage.contextWindow ?? usage.context_window ?? usage.contextLimit ?? usage.context_limit);
96
+ const contextTokens = optionalNumberValue(usage.contextTokens ?? usage.context_tokens);
97
+ const explicitContextUsageRatio = optionalNumberValue(usage.contextUsageRatio ?? usage.context_usage_ratio);
98
+ const contextUsageRatio = explicitContextUsageRatio
99
+ ?? (contextWindow !== undefined && contextTokens !== undefined && contextWindow > 0
100
+ ? Number(Math.min(1, contextTokens / contextWindow).toFixed(6))
101
+ : undefined);
95
102
  const hasCacheBreakdown = explicitUncachedInputTokens !== undefined || cacheReadTokens !== undefined || cacheWriteTokens !== undefined;
96
103
  const cachedInputTokens = (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0);
97
104
  const inputExcludesCache = explicitUncachedInputTokens !== undefined || cacheReadInputTokens !== undefined || cacheWriteInputTokens !== undefined;
@@ -104,6 +111,9 @@ export function usageChunk(value) {
104
111
  ...(cacheWriteTokens === undefined ? {} : { cacheWriteTokens }),
105
112
  ...(hasCacheBreakdown ? { uncachedInputTokens: inputExcludesCache ? inputTokens : Math.max(0, inputTokens - cachedInputTokens) } : {}),
106
113
  ...(totalTokens === undefined && !hasCacheBreakdown ? {} : { totalTokens: totalTokens ?? fullInputTokens + outputTokens }),
114
+ ...(contextWindow === undefined ? {} : { contextWindow }),
115
+ ...(contextTokens === undefined ? {} : { contextTokens }),
116
+ ...(contextUsageRatio === undefined ? {} : { contextUsageRatio }),
107
117
  ...(cacheReadTokens === undefined || fullInputTokens <= 0
108
118
  ? {}
109
119
  : { cacheHitRate: Number((cacheReadTokens / fullInputTokens * 100).toFixed(4)) }),
@@ -18,6 +18,7 @@ export interface CodingNsCliSessionPatch {
18
18
  readonly adapterId?: CodingNsCliAdapterId;
19
19
  readonly modelId?: string;
20
20
  readonly effortId?: string;
21
+ readonly providerId?: string;
21
22
  readonly providerSessionId?: string;
22
23
  readonly rawStoreRef?: string;
23
24
  readonly title?: string;
@@ -45,6 +46,7 @@ export declare class CodingNsCliSessionStore {
45
46
  private readonly records;
46
47
  private readonly settings;
47
48
  private readonly persistence;
49
+ private readonly legacyImportedRecords;
48
50
  private writeTail;
49
51
  constructor(options?: CodingNsCliSessionStoreOptions);
50
52
  /** 将设置变更重新载入内存;非法或不完整记录会被忽略。 */
@@ -56,6 +58,11 @@ export declare class CodingNsCliSessionStore {
56
58
  }): CodingNsCliSessionRecord[];
57
59
  /** 浏览器会话行只需要这两个字段,Host-only 恢复信息不得跨过 RPC 边界。 */
58
60
  adapterBindings(): CodingNsSessionAdapterBinding[];
61
+ /** 启动时把旧 DSH 原生会话中有明确证据的适配器回填到 Host 索引。 */
62
+ migrateLegacySessions(sessions: readonly unknown[]): {
63
+ migrated: number;
64
+ unresolved: number;
65
+ };
59
66
  /** 创建或更新记录;返回值是内存中的规范化记录,持久化在后台串行完成。 */
60
67
  upsert(sessionId: string, patch: CodingNsCliSessionPatch & CodingNsCliSessionConfig): CodingNsCliSessionRecord;
61
68
  archive(sessionId: string): CodingNsCliSessionRecord | undefined;
@@ -1,3 +1,5 @@
1
+ import { inspectLegacySessionAdapter } from './legacy-session-adapter.js';
2
+ import { readLegacyImportedSessionRecords } from './legacy-session-settings.js';
1
3
  /**
2
4
  * Host-only 的外部 Agent 会话索引。
3
5
  *
@@ -9,10 +11,16 @@ export class CodingNsCliSessionStore {
9
11
  records = new Map();
10
12
  settings;
11
13
  persistence;
14
+ legacyImportedRecords;
12
15
  writeTail = Promise.resolve();
13
16
  constructor(options = {}) {
14
17
  this.settings = options.settings;
15
18
  this.persistence = options.persistence;
19
+ // DSH 升级时会把旧设置保存为 settings.yaml.imported;先恢复其中的
20
+ // 外部会话索引,再用当前设置覆盖同 ID 记录,保证用户后续修改优先。
21
+ this.legacyImportedRecords = options.settings === undefined ? [] : readLegacyImportedSessionRecords();
22
+ for (const record of this.legacyImportedRecords)
23
+ this.hydrateRecord(record, true);
16
24
  for (const record of options.settings?.get().cliSessions ?? [])
17
25
  this.hydrateRecord(record, true);
18
26
  }
@@ -21,6 +29,8 @@ export class CodingNsCliSessionStore {
21
29
  if (records === undefined)
22
30
  return;
23
31
  this.records.clear();
32
+ for (const record of this.legacyImportedRecords)
33
+ this.hydrateRecord(record, false);
24
34
  for (const record of records)
25
35
  this.hydrateRecord(record, false);
26
36
  }
@@ -36,11 +46,33 @@ export class CodingNsCliSessionStore {
36
46
  }
37
47
  /** 浏览器会话行只需要这两个字段,Host-only 恢复信息不得跨过 RPC 边界。 */
38
48
  adapterBindings() {
39
- return this.list().map((record) => ({
49
+ return this.list({ includeArchived: true }).map((record) => ({
40
50
  sessionId: record.dshSessionId,
41
51
  adapterId: record.adapterId,
42
52
  }));
43
53
  }
54
+ /** 启动时把旧 DSH 原生会话中有明确证据的适配器回填到 Host 索引。 */
55
+ migrateLegacySessions(sessions) {
56
+ let migrated = 0;
57
+ let unresolved = 0;
58
+ for (const session of sessions) {
59
+ const evidence = inspectLegacySessionAdapter(session);
60
+ const existing = evidence === undefined ? undefined : this.records.get(evidence.sessionId);
61
+ if (evidence === undefined || existing !== undefined && existing.adapterId !== 'dsh')
62
+ continue;
63
+ if (evidence.adapterId === undefined) {
64
+ unresolved += 1;
65
+ continue;
66
+ }
67
+ this.upsert(evidence.sessionId, {
68
+ adapterId: evidence.adapterId,
69
+ ...(evidence.cwd === undefined ? {} : { cwd: evidence.cwd }),
70
+ status: existing?.status ?? 'idle',
71
+ });
72
+ migrated += 1;
73
+ }
74
+ return { migrated, unresolved };
75
+ }
44
76
  /** 创建或更新记录;返回值是内存中的规范化记录,持久化在后台串行完成。 */
45
77
  upsert(sessionId, patch) {
46
78
  const now = new Date().toISOString();
@@ -59,6 +91,7 @@ export class CodingNsCliSessionStore {
59
91
  adapterId,
60
92
  ...(patch.modelId?.trim() ? { modelId: patch.modelId.trim() } : base?.modelId ? { modelId: base.modelId } : {}),
61
93
  ...(patch.effortId?.trim() ? { effortId: patch.effortId.trim() } : base?.effortId ? { effortId: base.effortId } : {}),
94
+ ...(patch.providerId?.trim() ? { providerId: patch.providerId.trim() } : base?.providerId ? { providerId: base.providerId } : {}),
62
95
  ...(providerSessionId ? { providerSessionId } : base?.providerSessionId ? { providerSessionId: base.providerSessionId } : {}),
63
96
  ...(patch.rawStoreRef?.trim() ? { rawStoreRef: patch.rawStoreRef.trim() } : providerBase?.rawStoreRef ? { rawStoreRef: providerBase.rawStoreRef } : {}),
64
97
  ...(patch.title?.trim() ? { title: patch.title.trim() } : base?.title ? { title: base.title } : {}),
@@ -132,6 +165,7 @@ export class CodingNsCliSessionStore {
132
165
  adapterId: value.adapterId,
133
166
  ...(stringValue(value.modelId) ? { modelId: stringValue(value.modelId) } : {}),
134
167
  ...(stringValue(value.effortId) ? { effortId: stringValue(value.effortId) } : {}),
168
+ ...(stringValue(value.providerId) ? { providerId: stringValue(value.providerId) } : {}),
135
169
  ...(stringValue(value.providerSessionId) ? { providerSessionId: stringValue(value.providerSessionId) } : {}),
136
170
  ...(stringValue(value.rawStoreRef) ? { rawStoreRef: stringValue(value.rawStoreRef) } : {}),
137
171
  ...(stringValue(value.title) ? { title: stringValue(value.title) } : {}),
@@ -156,6 +190,10 @@ export class CodingNsCliSessionStore {
156
190
  await this.persistence.write(snapshot);
157
191
  if (this.settings !== undefined)
158
192
  await this.settings.update({ cliSessions: snapshot });
193
+ })
194
+ .catch((error) => {
195
+ // 会话索引是辅助缓存;设置服务短暂不可用不能杀死 Host 或模型探测任务。
196
+ console.warn('codingns4dsh: 外部会话索引持久化失败', error);
159
197
  });
160
198
  }
161
199
  }
@@ -9,6 +9,9 @@ export interface StandardStreamDriverOptions {
9
9
  readonly spawn?: typeof spawn;
10
10
  readonly versionArgs?: readonly string[];
11
11
  readonly modelArgs?: readonly string[];
12
+ /** Claude 模型发现的配置目录和网络请求注入点。 */
13
+ readonly claudeConfigDir?: string;
14
+ readonly fetch?: typeof fetch;
12
15
  }
13
16
  /**
14
17
  * 把采用 JSONL/stream-json 的 CLI 统一成 Codingns4DSH 的最小驱动契约。
@@ -14,12 +14,15 @@ export declare class CodingNsAgentEventNormalizer {
14
14
  private text;
15
15
  private reasoningBoundary;
16
16
  private textBoundary;
17
+ private reasoningMessageId;
18
+ private textMessageId;
17
19
  private usage;
18
20
  push(chunk: CodingNsAgentEvent): readonly CodingNsNormalizedAgentEvent[];
19
21
  /** 流未携带 finish 时仍返回最后一次统计,避免正常结束路径丢失 usage。 */
20
22
  flush(): readonly CodingNsNormalizedAgentEvent[];
21
23
  private appendSnapshot;
22
24
  private takeUsage;
25
+ private messageBoundary;
23
26
  }
24
27
  export {};
25
28
  //# sourceMappingURL=stream-normalizer.d.ts.map
@@ -9,17 +9,21 @@ export class CodingNsAgentEventNormalizer {
9
9
  text = '';
10
10
  reasoningBoundary = false;
11
11
  textBoundary = false;
12
+ reasoningMessageId;
13
+ textMessageId;
12
14
  usage = null;
13
15
  push(chunk) {
14
16
  if (chunk.type === 'reasoning-delta') {
17
+ const boundary = this.messageBoundary('reasoning', chunk.messageId);
15
18
  this.reasoning = this.reasoningBoundary ? chunk.text : this.reasoning + chunk.text;
16
19
  this.reasoningBoundary = false;
17
- return chunk.text === '' ? [] : [chunk];
20
+ return chunk.text === '' ? boundary : [...boundary, chunk];
18
21
  }
19
22
  if (chunk.type === 'text-delta') {
23
+ const boundary = this.messageBoundary('text', chunk.messageId);
20
24
  this.text = this.textBoundary ? chunk.text : this.text + chunk.text;
21
25
  this.textBoundary = false;
22
- return chunk.text === '' ? [] : [chunk];
26
+ return chunk.text === '' ? boundary : [...boundary, chunk];
23
27
  }
24
28
  if (chunk.type === 'reasoning-snapshot')
25
29
  return this.appendSnapshot('reasoning', chunk.text);
@@ -79,5 +83,21 @@ export class CodingNsAgentEventNormalizer {
79
83
  this.usage = null;
80
84
  return usage;
81
85
  }
86
+ messageBoundary(channel, messageId) {
87
+ if (messageId === undefined)
88
+ return [];
89
+ const previous = channel === 'reasoning' ? this.reasoningMessageId : this.textMessageId;
90
+ if (channel === 'reasoning')
91
+ this.reasoningMessageId = messageId;
92
+ else
93
+ this.textMessageId = messageId;
94
+ if (previous === undefined || previous === messageId)
95
+ return [];
96
+ if (channel === 'reasoning')
97
+ this.reasoning = '';
98
+ else
99
+ this.text = '';
100
+ return [{ type: 'message-boundary', channel, messageId }];
101
+ }
82
102
  }
83
103
  //# sourceMappingURL=stream-normalizer.js.map
@@ -1,5 +1,5 @@
1
1
  import type { AuthDeviceManagementSnapshotDto, HostBindRequest, HostBindResponse, HostBindingsResponse, HostLabelAvailabilityResponse, HostUnbindResponse, LoginByEmailRequest } from '../shared/contracts/auth.js';
2
- import type { DshDeviceHeartbeatResponse, DshDeviceListResponse, DshDeviceRegistrationRequest, DshDeviceRegistrationResponse, DshRelayTicketRequest, DshRelayTicketResponse } from '../shared/contracts/dsh-device.js';
2
+ import type { DshDeviceHeartbeatResponse, DshDeviceHeartbeatRequest, DshDeviceListResponse, DshDeviceRegistrationRequest, DshDeviceRegistrationResponse, DshRelayTicketRequest, DshRelayTicketResponse } from '../shared/contracts/dsh-device.js';
3
3
  import type { RelaySignalingTicketRequest, RelaySignalingTicketResponse } from '../shared/contracts/signaling.js';
4
4
  /** 仅 Host 使用的控制面会话响应;refresh token 不进入 shared/client 类型。 */
5
5
  export interface LoginByEmailResponse {
@@ -45,7 +45,7 @@ export interface CodingNsControlApiClient {
45
45
  createSignalingTicket(accessToken: string, request: RelaySignalingTicketRequest): Promise<RelaySignalingTicketResponse>;
46
46
  registerDshDevice(accessToken: string, request: DshDeviceRegistrationRequest): Promise<DshDeviceRegistrationResponse>;
47
47
  listDshDevices(accessToken: string): Promise<DshDeviceListResponse>;
48
- heartbeatDshDevice(accessToken: string, deviceId: string, deviceCredential: string): Promise<DshDeviceHeartbeatResponse>;
48
+ heartbeatDshDevice(accessToken: string, deviceId: string, deviceCredential: string, details?: DshDeviceHeartbeatRequest): Promise<DshDeviceHeartbeatResponse>;
49
49
  createDshRelayTicket(accessToken: string, request: DshRelayTicketRequest): Promise<DshRelayTicketResponse>;
50
50
  }
51
51
  /** 设计文档中的简写名称;保留长名称以明确其 Control API 边界。 */
@@ -77,7 +77,7 @@ export declare class HttpCodingNsControlApiClient implements CodingNsControlApiC
77
77
  createSignalingTicket(accessToken: string, request: RelaySignalingTicketRequest): Promise<RelaySignalingTicketResponse>;
78
78
  registerDshDevice(accessToken: string, request: DshDeviceRegistrationRequest): Promise<DshDeviceRegistrationResponse>;
79
79
  listDshDevices(accessToken: string): Promise<DshDeviceListResponse>;
80
- heartbeatDshDevice(accessToken: string, deviceId: string, deviceCredential: string): Promise<DshDeviceHeartbeatResponse>;
80
+ heartbeatDshDevice(accessToken: string, deviceId: string, deviceCredential: string, details?: DshDeviceHeartbeatRequest): Promise<DshDeviceHeartbeatResponse>;
81
81
  createDshRelayTicket(accessToken: string, request: DshRelayTicketRequest): Promise<DshRelayTicketResponse>;
82
82
  private request;
83
83
  }
@@ -74,10 +74,11 @@ export class HttpCodingNsControlApiClient {
74
74
  listDshDevices(accessToken) {
75
75
  return this.request(this.controlBaseUrl, CODINGNS_CONTROL_API_PATHS.dshDevices, { token: accessToken });
76
76
  }
77
- heartbeatDshDevice(accessToken, deviceId, deviceCredential) {
77
+ heartbeatDshDevice(accessToken, deviceId, deviceCredential, details) {
78
78
  return this.request(this.controlBaseUrl, `${CODINGNS_CONTROL_API_PATHS.dshDevices}/${encodeURIComponent(deviceId)}/heartbeat`, {
79
79
  method: 'POST',
80
80
  token: accessToken,
81
+ ...(details === undefined ? {} : { body: details }),
81
82
  headers: { 'x-dsh-device-credential': deviceCredential },
82
83
  });
83
84
  }
@@ -103,7 +103,17 @@ function parseDshCredential(value) {
103
103
  if (!isRecord(value) || typeof value.deviceId !== 'string' || typeof value.deviceCredential !== 'string' || typeof value.credentialVersion !== 'number' || !Number.isInteger(value.credentialVersion) || value.credentialVersion < 1 || typeof value.dtlsFingerprint !== 'string' || (typeof value.tunnelDomain !== 'string' && value.tunnelDomain !== null) || typeof value.displayName !== 'string' || typeof value.savedAt !== 'string') {
104
104
  throw new Error('DSH device credential 文件格式无效');
105
105
  }
106
- return { deviceId: value.deviceId, deviceCredential: value.deviceCredential, credentialVersion: value.credentialVersion, dtlsFingerprint: value.dtlsFingerprint, tunnelDomain: value.tunnelDomain, displayName: value.displayName, savedAt: value.savedAt };
106
+ return {
107
+ deviceId: value.deviceId,
108
+ deviceCredential: value.deviceCredential,
109
+ credentialVersion: value.credentialVersion,
110
+ dtlsFingerprint: value.dtlsFingerprint,
111
+ tunnelDomain: value.tunnelDomain,
112
+ displayName: value.displayName,
113
+ ...(typeof value.dshVersion === 'string' && value.dshVersion.trim() ? { dshVersion: value.dshVersion } : {}),
114
+ ...(typeof value.computerName === 'string' && value.computerName.trim() ? { computerName: value.computerName } : {}),
115
+ savedAt: value.savedAt,
116
+ };
107
117
  }
108
118
  function isRecord(value) {
109
119
  return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -10,12 +10,16 @@ export interface DshHostDeviceRuntimeOptions {
10
10
  readonly accessToken: string;
11
11
  /** 认证续期后动态读取最新 access token;未提供时沿用 accessToken。 */
12
12
  readonly accessTokenProvider?: () => string | null;
13
+ /** 对控制面请求统一处理 401 续期;未提供时直接使用当前 token。 */
14
+ readonly withAccessToken?: <T>(operation: (accessToken: string) => Promise<T>) => Promise<T>;
13
15
  readonly credentialStore?: DshDeviceCredentialStore;
14
16
  readonly dtlsStore?: HostDtlsIdentityStore;
15
17
  readonly displayName?: string;
16
18
  readonly protocolVersion?: string;
17
19
  /** 当前 DSH 宿主的真实版本,用于 Relay Transport 握手。 */
18
20
  readonly dshVersion?: string;
21
+ /** 覆盖自动获取的计算机名,主要用于测试和受控运行环境。 */
22
+ readonly computerName?: string;
19
23
  readonly capabilities?: readonly string[];
20
24
  readonly resources?: Pick<FeatureResourceScope, 'add'>;
21
25
  readonly signalingSocketFactory?: Parameters<typeof startHostRelayRuntime>[0]['signalingSocketFactory'];