@jingyi0605/codingns4dsh 0.2.1-rc.3 → 0.2.1-rc.5

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 (220) hide show
  1. package/README.en.md +3 -3
  2. package/README.md +3 -3
  3. package/data/build/dist/client/bundle.js +1838 -223
  4. package/data/build/dist/client/bundle.js.map +1 -1
  5. package/data/build/dist/client/cli-session-config-cache.d.ts.map +1 -1
  6. package/data/build/dist/client/cli-session-config-cache.js +9 -3
  7. package/data/build/dist/client/cli-session-config-cache.js.map +1 -1
  8. package/data/build/dist/client/client.native-workspace-store.js +7 -1
  9. package/data/build/dist/client/client.native-workspace-store.js.map +1 -1
  10. package/data/build/dist/client/client.transport.js +276 -60
  11. package/data/build/dist/client/client.transport.js.map +1 -1
  12. package/data/build/dist/client/desktop-assistant.js +274 -58
  13. package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
  14. package/data/build/dist/client/features/cli-adapters.js +13 -1
  15. package/data/build/dist/client/features/cli-adapters.js.map +1 -1
  16. package/data/build/dist/client/features/index.d.ts.map +1 -1
  17. package/data/build/dist/client/features/index.js +2 -0
  18. package/data/build/dist/client/features/index.js.map +1 -1
  19. package/data/build/dist/client/features/magpie-management-controls.d.ts.map +1 -1
  20. package/data/build/dist/client/features/magpie-management-controls.js +45 -10
  21. package/data/build/dist/client/features/magpie-management-controls.js.map +1 -1
  22. package/data/build/dist/client/features/magpie-management-page.d.ts +6 -1
  23. package/data/build/dist/client/features/magpie-management-page.d.ts.map +1 -1
  24. package/data/build/dist/client/features/magpie-management-page.js +109 -37
  25. package/data/build/dist/client/features/magpie-management-page.js.map +1 -1
  26. package/data/build/dist/client/features/magpie-management.d.ts.map +1 -1
  27. package/data/build/dist/client/features/magpie-management.js +3 -2
  28. package/data/build/dist/client/features/magpie-management.js.map +1 -1
  29. package/data/build/dist/client/features/plugin-update.d.ts +3 -0
  30. package/data/build/dist/client/features/plugin-update.d.ts.map +1 -0
  31. package/data/build/dist/client/features/plugin-update.js +8 -0
  32. package/data/build/dist/client/features/plugin-update.js.map +1 -0
  33. package/data/build/dist/client/features/types.d.ts +3 -0
  34. package/data/build/dist/client/features/types.d.ts.map +1 -1
  35. package/data/build/dist/client/locale.d.ts.map +1 -1
  36. package/data/build/dist/client/locale.js +3 -0
  37. package/data/build/dist/client/locale.js.map +1 -1
  38. package/data/build/dist/client/locales/magpie.d.ts.map +1 -1
  39. package/data/build/dist/client/locales/magpie.js +92 -12
  40. package/data/build/dist/client/locales/magpie.js.map +1 -1
  41. package/data/build/dist/client/locales/pluginUpdate.d.ts +3 -0
  42. package/data/build/dist/client/locales/pluginUpdate.d.ts.map +1 -0
  43. package/data/build/dist/client/locales/pluginUpdate.js +131 -0
  44. package/data/build/dist/client/locales/pluginUpdate.js.map +1 -0
  45. package/data/build/dist/client/locales/usage.d.ts.map +1 -1
  46. package/data/build/dist/client/locales/usage.js +2 -0
  47. package/data/build/dist/client/locales/usage.js.map +1 -1
  48. package/data/build/dist/client/magpie-context-meter.d.ts +18 -0
  49. package/data/build/dist/client/magpie-context-meter.d.ts.map +1 -0
  50. package/data/build/dist/client/magpie-context-meter.js +274 -0
  51. package/data/build/dist/client/magpie-context-meter.js.map +1 -0
  52. package/data/build/dist/client/magpie-context-store.d.ts +32 -0
  53. package/data/build/dist/client/magpie-context-store.d.ts.map +1 -0
  54. package/data/build/dist/client/magpie-context-store.js +165 -0
  55. package/data/build/dist/client/magpie-context-store.js.map +1 -0
  56. package/data/build/dist/client/settings-promo-styles.d.ts +5 -0
  57. package/data/build/dist/client/settings-promo-styles.d.ts.map +1 -1
  58. package/data/build/dist/client/settings-promo-styles.js +15 -2
  59. package/data/build/dist/client/settings-promo-styles.js.map +1 -1
  60. package/data/build/dist/client/settings-promo.d.ts +4 -2
  61. package/data/build/dist/client/settings-promo.d.ts.map +1 -1
  62. package/data/build/dist/client/settings-promo.js +4 -4
  63. package/data/build/dist/client/settings-promo.js.map +1 -1
  64. package/data/build/dist/client/settings-section.d.ts +2 -3
  65. package/data/build/dist/client/settings-section.d.ts.map +1 -1
  66. package/data/build/dist/client/settings-section.js +7 -7
  67. package/data/build/dist/client/settings-section.js.map +1 -1
  68. package/data/build/dist/client/settings-update.d.ts +8 -0
  69. package/data/build/dist/client/settings-update.d.ts.map +1 -0
  70. package/data/build/dist/client/settings-update.js +57 -0
  71. package/data/build/dist/client/settings-update.js.map +1 -0
  72. package/data/build/dist/client/subscription-slot.d.ts +3 -2
  73. package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
  74. package/data/build/dist/client/subscription-slot.js +115 -43
  75. package/data/build/dist/client/subscription-slot.js.map +1 -1
  76. package/data/build/dist/client/update-panel-styles.d.ts +29 -0
  77. package/data/build/dist/client/update-panel-styles.d.ts.map +1 -0
  78. package/data/build/dist/client/update-panel-styles.js +69 -0
  79. package/data/build/dist/client/update-panel-styles.js.map +1 -0
  80. package/data/build/dist/client/update-panel.d.ts +7 -0
  81. package/data/build/dist/client/update-panel.d.ts.map +1 -0
  82. package/data/build/dist/client/update-panel.js +52 -0
  83. package/data/build/dist/client/update-panel.js.map +1 -0
  84. package/data/build/dist/client/update-store.d.ts +43 -0
  85. package/data/build/dist/client/update-store.d.ts.map +1 -0
  86. package/data/build/dist/client/update-store.js +207 -0
  87. package/data/build/dist/client/update-store.js.map +1 -0
  88. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.d.ts +4 -0
  89. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.d.ts.map +1 -0
  90. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.js +6 -0
  91. package/data/build/dist/dsh-capabilities/client/plugin-reload-adapter.js.map +1 -0
  92. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts +15 -1
  93. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.d.ts.map +1 -1
  94. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js +53 -21
  95. package/data/build/dist/dsh-capabilities/client/session-performance-adapter.js.map +1 -1
  96. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts +22 -0
  97. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.d.ts.map +1 -0
  98. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js +71 -0
  99. package/data/build/dist/dsh-capabilities/host/plugin-update-adapter.js.map +1 -0
  100. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts +4 -0
  101. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -1
  102. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +16 -1
  103. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -1
  104. package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
  105. package/data/build/dist/dsh-capabilities/matrix.js +4 -0
  106. package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
  107. package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
  108. package/data/build/dist/dsh-capabilities/routes.js +10 -0
  109. package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
  110. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  111. package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
  112. package/data/build/dist/dsh-capabilities/types.js.map +1 -1
  113. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  114. package/data/build/dist/host/cli-adapters/feature.js +230 -34
  115. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  116. package/data/build/dist/host/cli-adapters/magpie-context.d.ts +9 -0
  117. package/data/build/dist/host/cli-adapters/magpie-context.d.ts.map +1 -0
  118. package/data/build/dist/host/cli-adapters/magpie-context.js +128 -0
  119. package/data/build/dist/host/cli-adapters/magpie-context.js.map +1 -0
  120. package/data/build/dist/host/cli-adapters/magpie-detection.d.ts +3 -3
  121. package/data/build/dist/host/cli-adapters/magpie-detection.d.ts.map +1 -1
  122. package/data/build/dist/host/cli-adapters/magpie-detection.js +18 -4
  123. package/data/build/dist/host/cli-adapters/magpie-detection.js.map +1 -1
  124. package/data/build/dist/host/cli-adapters/magpie-management.d.ts.map +1 -1
  125. package/data/build/dist/host/cli-adapters/magpie-management.js +95 -20
  126. package/data/build/dist/host/cli-adapters/magpie-management.js.map +1 -1
  127. package/data/build/dist/host/cli-adapters/magpie-update.d.ts.map +1 -1
  128. package/data/build/dist/host/cli-adapters/magpie-update.js +14 -4
  129. package/data/build/dist/host/cli-adapters/magpie-update.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/magpie-usage.d.ts +17 -3
  131. package/data/build/dist/host/cli-adapters/magpie-usage.d.ts.map +1 -1
  132. package/data/build/dist/host/cli-adapters/magpie-usage.js +382 -92
  133. package/data/build/dist/host/cli-adapters/magpie-usage.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/magpie-web.d.ts +15 -0
  135. package/data/build/dist/host/cli-adapters/magpie-web.d.ts.map +1 -1
  136. package/data/build/dist/host/cli-adapters/magpie-web.js +100 -22
  137. package/data/build/dist/host/cli-adapters/magpie-web.js.map +1 -1
  138. package/data/build/dist/host/cli-adapters/process-utils.d.ts.map +1 -1
  139. package/data/build/dist/host/cli-adapters/process-utils.js +4 -0
  140. package/data/build/dist/host/cli-adapters/process-utils.js.map +1 -1
  141. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  142. package/data/build/dist/host/cli-adapters/registry.js +6 -5
  143. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  144. package/data/build/dist/host/cli-adapters/session-performance-projection.d.ts +6 -0
  145. package/data/build/dist/host/cli-adapters/session-performance-projection.d.ts.map +1 -1
  146. package/data/build/dist/host/cli-adapters/session-performance-projection.js +25 -0
  147. package/data/build/dist/host/cli-adapters/session-performance-projection.js.map +1 -1
  148. package/data/build/dist/host/features/index.d.ts.map +1 -1
  149. package/data/build/dist/host/features/index.js +2 -0
  150. package/data/build/dist/host/features/index.js.map +1 -1
  151. package/data/build/dist/host/features/plugin-update.d.ts +6 -0
  152. package/data/build/dist/host/features/plugin-update.d.ts.map +1 -0
  153. package/data/build/dist/host/features/plugin-update.js +45 -0
  154. package/data/build/dist/host/features/plugin-update.js.map +1 -0
  155. package/data/build/dist/host/index.d.ts.map +1 -1
  156. package/data/build/dist/host/index.js +31 -1
  157. package/data/build/dist/host/index.js.map +1 -1
  158. package/data/build/dist/host/native-session-bridge.d.ts +8 -0
  159. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  160. package/data/build/dist/host/native-session-bridge.js +43 -0
  161. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  162. package/data/build/dist/host/rpc.d.ts +4 -1
  163. package/data/build/dist/host/rpc.d.ts.map +1 -1
  164. package/data/build/dist/host/rpc.js +33 -7
  165. package/data/build/dist/host/rpc.js.map +1 -1
  166. package/data/build/dist/host/session-handle-eviction.d.ts +29 -0
  167. package/data/build/dist/host/session-handle-eviction.d.ts.map +1 -0
  168. package/data/build/dist/host/session-handle-eviction.js +413 -0
  169. package/data/build/dist/host/session-handle-eviction.js.map +1 -0
  170. package/data/build/dist/host/settings.d.ts.map +1 -1
  171. package/data/build/dist/host/settings.js +12 -1
  172. package/data/build/dist/host/settings.js.map +1 -1
  173. package/data/build/dist/host/update/application-result.d.ts +3 -0
  174. package/data/build/dist/host/update/application-result.d.ts.map +1 -0
  175. package/data/build/dist/host/update/application-result.js +13 -0
  176. package/data/build/dist/host/update/application-result.js.map +1 -0
  177. package/data/build/dist/host/update/http.d.ts +7 -0
  178. package/data/build/dist/host/update/http.d.ts.map +1 -0
  179. package/data/build/dist/host/update/http.js +55 -0
  180. package/data/build/dist/host/update/http.js.map +1 -0
  181. package/data/build/dist/host/update/metadata.d.ts +14 -0
  182. package/data/build/dist/host/update/metadata.d.ts.map +1 -0
  183. package/data/build/dist/host/update/metadata.js +75 -0
  184. package/data/build/dist/host/update/metadata.js.map +1 -0
  185. package/data/build/dist/host/update/release-notes.d.ts +6 -0
  186. package/data/build/dist/host/update/release-notes.d.ts.map +1 -0
  187. package/data/build/dist/host/update/release-notes.js +52 -0
  188. package/data/build/dist/host/update/release-notes.js.map +1 -0
  189. package/data/build/dist/host/update/service.d.ts +25 -0
  190. package/data/build/dist/host/update/service.d.ts.map +1 -0
  191. package/data/build/dist/host/update/service.js +209 -0
  192. package/data/build/dist/host/update/service.js.map +1 -0
  193. package/data/build/dist/shared/contracts/config.d.ts +17 -0
  194. package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
  195. package/data/build/dist/shared/contracts/config.js +8 -0
  196. package/data/build/dist/shared/contracts/config.js.map +1 -1
  197. package/data/build/dist/shared/contracts/magpie-management.d.ts +59 -0
  198. package/data/build/dist/shared/contracts/magpie-management.d.ts.map +1 -1
  199. package/data/build/dist/shared/contracts/plugin-update.d.ts +93 -0
  200. package/data/build/dist/shared/contracts/plugin-update.d.ts.map +1 -0
  201. package/data/build/dist/shared/contracts/plugin-update.js +128 -0
  202. package/data/build/dist/shared/contracts/plugin-update.js.map +1 -0
  203. package/data/build/dist/shared/contracts/subscription.d.ts +5 -0
  204. package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
  205. package/data/build/dist/shared/contracts/subscription.js.map +1 -1
  206. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  207. package/data/build/dist/shared/contracts/version.js +1 -1
  208. package/data/build/dist/shared/index.d.ts +2 -1
  209. package/data/build/dist/shared/index.d.ts.map +1 -1
  210. package/data/build/dist/shared/index.js +1 -0
  211. package/data/build/dist/shared/index.js.map +1 -1
  212. package/data/build/dist/shared/magpie-provider-id.d.ts +12 -0
  213. package/data/build/dist/shared/magpie-provider-id.d.ts.map +1 -0
  214. package/data/build/dist/shared/magpie-provider-id.js +16 -0
  215. package/data/build/dist/shared/magpie-provider-id.js.map +1 -0
  216. package/package.json +2 -1
  217. package/profile/README.md +2 -2
  218. package/profile/package.json +2 -2
  219. package/profile/version.json +1 -1
  220. package/version.json +1 -1
@@ -21,7 +21,7 @@ import { CodingNsCliSessionStore } from './session-store.js';
21
21
  import { CodingNsDshMessageProjector } from './dsh-message-projector.js';
22
22
  import { CommandCodeSubscriptionService } from './command-code-subscription.js';
23
23
  import { ProviderSubscriptionService } from './provider-subscription.js';
24
- import { attachMagpieSessionNames, MagpieUsageService, magpieAgentForAdapter } from './magpie-usage.js';
24
+ import { attachMagpieSessionNames, MagpieUsageService, magpieAgentForAdapter, magpieUsageAgentAliases } from './magpie-usage.js';
25
25
  import { readDshProviderSource } from './dsh-config.js';
26
26
  import { MagpieManagementService } from './magpie-management.js';
27
27
  import { MagpieWebService } from './magpie-web.js';
@@ -55,6 +55,7 @@ import { sessionPromptScope } from '../../dsh-capabilities/host/session-prompt-a
55
55
  import { createSessionPromptTranslation } from '../features/session-prompt-translation.js';
56
56
  import { codexPerformanceProjection } from './codex-performance-projection.js';
57
57
  import { grokContextProjection, grokPerformanceProjection } from './grok-performance-projection.js';
58
+ import { countSessionTurnSteps } from './session-performance-projection.js';
58
59
  import { fromCliProviderId, toCliProviderId } from '../../shared/cli-provider-route.js';
59
60
  import { DshAgentTeamAdapter } from '../../dsh-capabilities/host/agent-team-adapter.js';
60
61
  import { CodingNsTeamDispatcher } from '../cli-bridge/team-dispatch.js';
@@ -88,6 +89,7 @@ export function createCliAdaptersFeature(options = {}) {
88
89
  };
89
90
  let subscriptions = options.subscriptions ?? buildSubscriptions(context.services.settings?.get());
90
91
  let magpieDetection = options.magpieDetection ?? new MagpieInstallDetectionService(options.magpieDetectionOptions);
92
+ let magpieDetectionRequest = 0;
91
93
  // 最近一次可用的 Magpie 入口;探测会随用户指定的路径变化而重建,Web 服务总是读最新值。
92
94
  let magpieCommand = null;
93
95
  let magpieVersion = null;
@@ -386,6 +388,14 @@ export function createCliAdaptersFeature(options = {}) {
386
388
  case 'subscription': {
387
389
  const subscription = readSubscriptionRequest(payload);
388
390
  const requestSignal = signal ?? lifetime.signal;
391
+ // A newly opened native DSH session may not be in the Host bridge's
392
+ // local store yet. Adopt it before reading the Registry, otherwise
393
+ // the native model/provider selection is invisible and the request
394
+ // incorrectly falls back to the official DSH balance.
395
+ if (subscription.adapterId === 'dsh' && subscription.sessionId !== undefined && nativeSessions !== undefined) {
396
+ await nativeSessions.ensure(subscription.sessionId);
397
+ requestSignal.throwIfAborted();
398
+ }
389
399
  const session = subscription.sessionId === undefined ? undefined : registry.getSession(subscription.sessionId);
390
400
  if (context.services.settings?.get().modules.magpieManagement === true
391
401
  && session !== undefined && session.adapterId === subscription.adapterId) {
@@ -393,9 +403,9 @@ export function createCliAdaptersFeature(options = {}) {
393
403
  requestSignal.throwIfAborted();
394
404
  const snapshot = magpie ?? (subscription.sessionId === undefined
395
405
  ? null
396
- : await pendingMagpieUsage(magpieUsage, nativeSessions, subscription.sessionId, session, requestSignal));
406
+ : await pendingMagpieUsage(magpieUsage, nativeSessions, subscription.sessionId, session, requestSignal, subscription.includeRoute));
397
407
  if (snapshot !== null) {
398
- const sessionName = subscription.sessionId === undefined ? undefined : readDshSessionDisplayName(subscription.sessionId, sessionStore, nativeSessions);
408
+ const sessionName = subscription.sessionId === undefined ? undefined : readDshSessionDisplayName(subscription.sessionId, sessionStore, nativeSessions, context.services.dshContext);
399
409
  return { providerUsage: null, magpie: sessionName === undefined ? snapshot : { ...snapshot, sessionName } };
400
410
  }
401
411
  }
@@ -429,34 +439,66 @@ export function createCliAdaptersFeature(options = {}) {
429
439
  const signal = isAbortSignal(rpcSignal) ? rpcSignal : lifetime.signal;
430
440
  // 安装探测本身就是启用前的判断依据,因此不要求模块已启用。
431
441
  if (action === 'management/detection') {
442
+ const requestId = ++magpieDetectionRequest;
432
443
  const request = readMagpieDetectionRequest(payload);
433
444
  const force = readMagpieDetectionForce(payload);
434
- // A custom path is request-scoped; it must not poison later automatic detection.
445
+ // Only a successful custom probe replaces the Host-session selection.
435
446
  const customCommand = normalizeCustomCommand(request.commandPath);
436
447
  const detectionService = customCommand === null
437
448
  ? magpieDetection
438
449
  : new MagpieInstallDetectionService({ ...options.magpieDetectionOptions, customCommand });
439
450
  const detection = await detectionService.detect(customCommand === null ? {} : { commandPath: customCommand }, { force: force || customCommand !== null });
440
- magpieCommand = MagpieInstallDetectionService.isUsable(detection) ? detection.command : null;
441
- magpieVersion = MagpieInstallDetectionService.isUsable(detection) ? detection.version : null;
451
+ if (requestId !== magpieDetectionRequest || lifetime.signal.aborted)
452
+ return detection;
453
+ const usable = MagpieInstallDetectionService.isUsable(detection);
454
+ if (usable || customCommand === null) {
455
+ if (usable && customCommand !== null)
456
+ magpieDetection = detectionService;
457
+ magpieInstalled = usable;
458
+ magpieCommand = usable ? detection.command : null;
459
+ magpieVersion = usable ? detection.version : null;
460
+ scheduleMagpieWebSync(context.services.settings?.get().modules.magpieManagement === true && usable);
461
+ }
442
462
  return detection;
443
463
  }
464
+ if (action === 'context') {
465
+ const request = readMagpieContextRequest(payload);
466
+ const target = await resolveMagpieContextTarget(request.sessionId, sessionStore, nativeSessions, magpieUsage, signal);
467
+ if (target === null || !magpieInstalled)
468
+ return null;
469
+ try {
470
+ await magpieWeb.start();
471
+ }
472
+ catch {
473
+ return null;
474
+ }
475
+ return magpieUsage.readContext(target, signal);
476
+ }
444
477
  if (context.services.settings?.get().modules.magpieManagement !== true) {
445
478
  throw new CodingNsRpcError('CODINGNS_MAGPIE_DISABLED', 'Magpie management is disabled');
446
479
  }
447
480
  switch (action) {
448
481
  case 'usage/dashboard': {
449
482
  const dashboard = await magpieUsage.readDashboard(readMagpieDashboardPeriod(payload), signal);
450
- return attachMagpieSessionNames(dashboard, await magpieSessionNameBindings(sessionStore, nativeSessions, signal));
483
+ return attachMagpieSessionNames(dashboard, await magpieSessionNameBindings(sessionStore, nativeSessions, signal, context.services.dshContext));
451
484
  }
452
485
  case 'usage/dashboard/stream': {
453
486
  const input = asRecord(payload);
454
487
  const period = readMagpieDashboardPeriod(payload);
455
488
  const force = input?.force === true;
456
- return streamMagpieDashboard(magpieUsage, period, signal, force, sessionStore, nativeSessions);
489
+ return streamMagpieDashboard(magpieUsage, period, signal, force, sessionStore, nativeSessions, context.services.dshContext);
490
+ }
491
+ case 'usage/requests': {
492
+ const request = readMagpieRequestOptions(payload);
493
+ const page = await magpieUsage.readRequestPage(request, signal);
494
+ if (page === null || request.sessionId === undefined)
495
+ return page;
496
+ return {
497
+ ...page,
498
+ sessionName: readDshSessionDisplayName(request.sessionId, sessionStore, nativeSessions, context.services.dshContext) ?? null,
499
+ sessionMetrics: readMagpieSessionMetrics(sessionStore, nativeSessions, request.sessionId, request.sessionIds),
500
+ };
457
501
  }
458
- case 'usage/requests':
459
- return magpieUsage.readRequestPage(readMagpieRequestOptions(payload), signal);
460
502
  case 'management/snapshot':
461
503
  return magpieManagement.readSnapshot(signal);
462
504
  case 'management/provider/update':
@@ -1535,6 +1577,11 @@ function readOptionalText(value) {
1535
1577
  function readNativeModelSelection(nativeSessions, sessionId) {
1536
1578
  if (nativeSessions === undefined)
1537
1579
  return undefined;
1580
+ const current = nativeSessions.modelSelection?.(sessionId);
1581
+ if (current !== undefined && current.model.trim() !== '') {
1582
+ const provider = current.provider.trim();
1583
+ return provider === '' ? { modelId: current.model.trim() } : { providerId: provider, modelId: current.model.trim() };
1584
+ }
1538
1585
  let session;
1539
1586
  try {
1540
1587
  session = nativeSessions.get(sessionId);
@@ -1578,6 +1625,43 @@ function isMagpieGatewayProvider(providerId) {
1578
1625
  return isMagpieGateway(source.baseUrl);
1579
1626
  return /^magpie(?:[-_.].*)?$/iu.test(providerId);
1580
1627
  }
1628
+ /** Resolve the exact identities needed for one Magpie context read. */
1629
+ async function resolveMagpieContextTarget(sessionId, sessionStore, nativeSessions, magpieUsage, signal) {
1630
+ const normalizedSessionId = sessionId.trim();
1631
+ if (normalizedSessionId === '')
1632
+ return null;
1633
+ let stored = sessionStore.get(normalizedSessionId);
1634
+ if (stored === undefined && nativeSessions !== undefined) {
1635
+ try {
1636
+ await nativeSessions.ensure(normalizedSessionId);
1637
+ signal.throwIfAborted();
1638
+ }
1639
+ catch {
1640
+ return null;
1641
+ }
1642
+ stored = sessionStore.get(normalizedSessionId);
1643
+ }
1644
+ const adapterId = stored?.adapterId ?? DSH_NATIVE_SESSION_ADAPTER_ID;
1645
+ const selection = adapterId === DSH_NATIVE_SESSION_ADAPTER_ID
1646
+ ? readNativeModelSelection(nativeSessions, normalizedSessionId)
1647
+ : undefined;
1648
+ const modelId = stored?.modelId?.trim() || selection?.modelId;
1649
+ if (modelId === undefined || modelId === '')
1650
+ return null;
1651
+ const providerId = stored?.providerId?.trim() || selection?.providerId;
1652
+ if (adapterId === DSH_NATIVE_SESSION_ADAPTER_ID) {
1653
+ if (providerId === undefined || !isMagpieGatewayProvider(providerId))
1654
+ return null;
1655
+ }
1656
+ else if (await magpieUsage.readModelMetadata(adapterId, modelId, signal) === null) {
1657
+ return null;
1658
+ }
1659
+ const agents = magpieUsageAgentAliases(adapterId);
1660
+ if (agents.length === 0)
1661
+ return null;
1662
+ const sessionIds = [...new Set([normalizedSessionId, stored?.providerSessionId?.trim() ?? ''].filter(Boolean))];
1663
+ return { agents, sessionIds };
1664
+ }
1581
1665
  /**
1582
1666
  * 读到用量之后仍然要判断「这个模型是不是 Magpie 的」。
1583
1667
  *
@@ -1586,21 +1670,25 @@ function isMagpieGatewayProvider(providerId) {
1586
1670
  * 会话 ID 在滚动升级期间并不相等,这时返回 null 会让 Host 直接回退到官方余量。
1587
1671
  * 只有确认模型由 Magpie 提供时才下发占位快照,避免官方数字抢占首屏。
1588
1672
  */
1589
- async function pendingMagpieUsage(magpie, nativeSessions, sessionId, session, signal) {
1673
+ async function pendingMagpieUsage(magpie, nativeSessions, sessionId, session, signal, includeRoute = false) {
1590
1674
  const agent = magpieAgentForAdapter(session.adapterId);
1591
1675
  const modelId = session.modelId?.trim();
1592
1676
  if (agent === null || modelId === undefined || modelId === '')
1593
1677
  return null;
1594
1678
  try {
1595
- const selectedProviderId = session.providerId?.trim();
1596
- const providerId = selectedProviderId === undefined || selectedProviderId === ''
1597
- ? readNativeModelSelection(nativeSessions, sessionId)?.providerId
1598
- : selectedProviderId;
1599
- if (providerId === undefined || !isMagpieGatewayProvider(providerId))
1600
- return null;
1679
+ if (session.adapterId === DSH_NATIVE_SESSION_ADAPTER_ID) {
1680
+ const providerId = session.providerId?.trim() || readNativeModelSelection(nativeSessions, sessionId)?.providerId;
1681
+ if (providerId === undefined || !isMagpieGatewayProvider(providerId))
1682
+ return null;
1683
+ }
1601
1684
  const confirmed = await magpie.readModelMetadata(session.adapterId, modelId, signal);
1602
1685
  signal.throwIfAborted();
1603
- return confirmed === null ? null : pendingMagpieSnapshot(agent, modelId);
1686
+ if (confirmed === null)
1687
+ return null;
1688
+ // Gateway totals do not depend on a provider session binding. Keep them
1689
+ // while the exact session row is pending, without attributing other rows.
1690
+ return await magpie.read(session, signal, includeRoute, sessionId, { allowPendingSession: true })
1691
+ ?? pendingMagpieSnapshot(agent, modelId);
1604
1692
  }
1605
1693
  catch {
1606
1694
  // 目录不可读、超时或请求被取消:保持原有回退语义,不伪造 Magpie 用量。
@@ -2128,8 +2216,8 @@ function readMagpieDashboardPeriod(payload) {
2128
2216
  return value;
2129
2217
  throw new TypeError('Magpie usage period must be today, week, month, or all');
2130
2218
  }
2131
- async function* streamMagpieDashboard(service, period, signal, force, sessionStore, nativeSessions) {
2132
- const names = magpieSessionNameBindings(sessionStore, nativeSessions, signal).catch(() => []);
2219
+ async function* streamMagpieDashboard(service, period, signal, force, sessionStore, nativeSessions, dshContext) {
2220
+ const names = magpieSessionNameBindings(sessionStore, nativeSessions, signal, dshContext).catch(() => []);
2133
2221
  let latest;
2134
2222
  for await (const chunk of service.readDashboardStream(period, signal, force)) {
2135
2223
  latest = chunk.dashboard;
@@ -2153,6 +2241,12 @@ function readMagpieDetectionRequest(payload) {
2153
2241
  function readMagpieDetectionForce(payload) {
2154
2242
  return asRecord(payload)?.force === true;
2155
2243
  }
2244
+ function readMagpieContextRequest(payload) {
2245
+ const sessionId = readOptionalMagpieIdentity(asRecord(payload)?.sessionId, 512);
2246
+ if (sessionId === undefined)
2247
+ throw new TypeError('Magpie context requires a session ID');
2248
+ return { sessionId };
2249
+ }
2156
2250
  function readMagpieRequestOptions(payload) {
2157
2251
  const record = asRecord(payload);
2158
2252
  if (record === null)
@@ -2167,10 +2261,16 @@ function readMagpieRequestOptions(payload) {
2167
2261
  throw new TypeError('Magpie request limit must be between 1 and 100');
2168
2262
  }
2169
2263
  const sessionId = readOptionalMagpieIdentity(record.sessionId, 512);
2264
+ const sessionIds = record.sessionIds === undefined
2265
+ ? undefined
2266
+ : readOptionalMagpieIdentityList(record.sessionIds, 16, 512);
2170
2267
  const sessionAgent = readOptionalMagpieIdentity(record.sessionAgent, 64);
2171
2268
  if ((sessionId === undefined) !== (sessionAgent === undefined)) {
2172
2269
  throw new TypeError('Magpie session details require both sessionId and sessionAgent');
2173
2270
  }
2271
+ if (sessionIds !== undefined && sessionId === undefined) {
2272
+ throw new TypeError('Magpie session IDs require a primary session ID');
2273
+ }
2174
2274
  if (sessionAgent !== undefined && !/^[a-z0-9][a-z0-9-]*$/u.test(sessionAgent)) {
2175
2275
  throw new TypeError('Magpie session Agent is invalid');
2176
2276
  }
@@ -2179,9 +2279,38 @@ function readMagpieRequestOptions(payload) {
2179
2279
  offset,
2180
2280
  limit,
2181
2281
  ...(sessionId === undefined ? {} : { sessionId }),
2282
+ ...(sessionIds === undefined ? {} : { sessionIds }),
2182
2283
  ...(sessionAgent === undefined ? {} : { sessionAgent }),
2183
2284
  };
2184
2285
  }
2286
+ function readMagpieSessionMetrics(sessionStore, nativeSessions, sessionId, sessionIds) {
2287
+ if (nativeSessions === undefined)
2288
+ return null;
2289
+ const identities = new Set([sessionId, ...(sessionIds ?? [])]);
2290
+ const bindings = sessionStore.list({ includeArchived: true }).filter((record) => identities.has(record.dshSessionId) || (record.providerSessionId !== undefined && identities.has(record.providerSessionId)));
2291
+ const direct = bindings.find((record) => record.dshSessionId === sessionId);
2292
+ const boundSessionIds = [...new Set(bindings.map((record) => record.dshSessionId))];
2293
+ if (direct === undefined && boundSessionIds.length > 1)
2294
+ return null;
2295
+ const dshSessionId = direct?.dshSessionId ?? boundSessionIds[0] ?? sessionId;
2296
+ let session;
2297
+ try {
2298
+ session = nativeSessions.get(dshSessionId);
2299
+ }
2300
+ catch {
2301
+ return null;
2302
+ }
2303
+ const snapshotEvents = asRecord(session)?.snapshotEvents;
2304
+ if (typeof snapshotEvents !== 'function')
2305
+ return null;
2306
+ try {
2307
+ const events = snapshotEvents.call(session);
2308
+ return Array.isArray(events) ? countSessionTurnSteps(events) : null;
2309
+ }
2310
+ catch {
2311
+ return null;
2312
+ }
2313
+ }
2185
2314
  function readOptionalMagpieIdentity(value, maxLength) {
2186
2315
  if (value === undefined)
2187
2316
  return undefined;
@@ -2193,7 +2322,15 @@ function readOptionalMagpieIdentity(value, maxLength) {
2193
2322
  }
2194
2323
  return text;
2195
2324
  }
2196
- async function magpieSessionNameBindings(sessionStore, nativeSessions, signal) {
2325
+ function readOptionalMagpieIdentityList(value, maxItems, maxLength) {
2326
+ if (!Array.isArray(value) || value.length > maxItems)
2327
+ throw new TypeError('Magpie session IDs are invalid');
2328
+ const values = value.map((item) => readOptionalMagpieIdentity(item, maxLength));
2329
+ if (values.some((item) => item === undefined))
2330
+ throw new TypeError('Magpie session IDs are invalid');
2331
+ return [...new Set(values)];
2332
+ }
2333
+ async function magpieSessionNameBindings(sessionStore, nativeSessions, signal, dshContext) {
2197
2334
  const records = sessionStore.list({ includeArchived: true });
2198
2335
  const bindings = [];
2199
2336
  const knownSessionIds = new Set();
@@ -2209,20 +2346,18 @@ async function magpieSessionNameBindings(sessionStore, nativeSessions, signal) {
2209
2346
  const nativeNames = new Map();
2210
2347
  for (const value of nativeValues) {
2211
2348
  const sessionId = nativeSessionId(value);
2212
- const name = readNativeSessionDisplayName(value);
2349
+ const name = readNativeSessionDisplayName(value, dshContext);
2213
2350
  if (sessionId !== undefined && name !== undefined)
2214
2351
  nativeNames.set(sessionId, name);
2215
2352
  }
2216
2353
  for (const record of records) {
2217
2354
  knownSessionIds.add(record.dshSessionId);
2218
- const name = nativeNames.get(record.dshSessionId) ?? readDshSessionDisplayName(record.dshSessionId, sessionStore, nativeSessions);
2219
- if (name === undefined)
2220
- continue;
2355
+ const name = nativeNames.get(record.dshSessionId) ?? readDshSessionDisplayName(record.dshSessionId, sessionStore, nativeSessions, dshContext);
2221
2356
  bindings.push({
2222
2357
  dshSessionId: record.dshSessionId,
2223
2358
  adapterId: record.adapterId,
2224
2359
  ...(record.providerSessionId === undefined ? {} : { providerSessionId: record.providerSessionId }),
2225
- name,
2360
+ ...(name === undefined ? {} : { name }),
2226
2361
  });
2227
2362
  }
2228
2363
  // Native DSH sessions do not always have a CodingNS session-index record. They
@@ -2232,33 +2367,94 @@ async function magpieSessionNameBindings(sessionStore, nativeSessions, signal) {
2232
2367
  const sessionId = nativeSessionId(value);
2233
2368
  if (sessionId === undefined || knownSessionIds.has(sessionId))
2234
2369
  continue;
2235
- const name = readNativeSessionDisplayName(value);
2236
- if (name === undefined)
2237
- continue;
2238
- bindings.push({ dshSessionId: sessionId, adapterId: 'dsh', name });
2370
+ const name = readNativeSessionDisplayName(value, dshContext);
2371
+ bindings.push({ dshSessionId: sessionId, adapterId: 'dsh', ...(name === undefined ? {} : { name }) });
2239
2372
  }
2240
2373
  return bindings;
2241
2374
  }
2242
- function readDshSessionDisplayName(sessionId, sessionStore, nativeSessions) {
2243
- return readNativeSessionDisplayName(nativeSessions?.get(sessionId)) ?? cleanDisplayName(sessionStore.get(sessionId)?.title);
2375
+ function readDshSessionDisplayName(sessionId, sessionStore, nativeSessions, dshContext) {
2376
+ const direct = readNativeSession(nativeSessions, sessionId);
2377
+ const directName = readNativeSessionDisplayName(direct, dshContext);
2378
+ if (directName !== undefined)
2379
+ return directName;
2380
+ const directRecord = sessionStore.get(sessionId);
2381
+ const directRecordName = cleanDisplayName(directRecord?.title);
2382
+ if (directRecordName !== undefined)
2383
+ return directRecordName;
2384
+ const binding = sessionStore.list({ includeArchived: true }).find((record) => record.dshSessionId === sessionId || record.providerSessionId === sessionId);
2385
+ if (binding === undefined)
2386
+ return undefined;
2387
+ return readNativeSessionDisplayName(readNativeSession(nativeSessions, binding.dshSessionId), dshContext)
2388
+ ?? cleanDisplayName(binding.title);
2244
2389
  }
2245
- function readNativeSessionDisplayName(value) {
2390
+ function readNativeSessionDisplayName(value, dshContext) {
2246
2391
  const root = asRecord(value);
2247
2392
  if (root === null)
2248
2393
  return undefined;
2249
2394
  const projection = asRecord(root.projections) ?? asRecord(asRecord(root.record)?.projections);
2250
2395
  const values = asRecord(projection?.values);
2251
2396
  const record = asRecord(root.record);
2397
+ const header = asRecord(root.header);
2398
+ const recordHeader = asRecord(record?.header);
2252
2399
  const rows = asRecord(record?.rows);
2253
2400
  const rowProjection = asRecord(rows?.projections);
2254
2401
  const rowValues = asRecord(rowProjection?.values);
2255
- for (const candidate of [values?.title, rowValues?.title, root.title, root.name, root.displayName, record?.title, record?.name]) {
2402
+ const titleService = asRecord(readOptionalDshService(dshContext, 'sessionTitle'));
2403
+ if (typeof titleService?.get === 'function') {
2404
+ try {
2405
+ const snapshot = titleService.get(value);
2406
+ const title = cleanDisplayName(asRecord(snapshot)?.title ?? snapshot);
2407
+ if (title !== undefined)
2408
+ return title;
2409
+ }
2410
+ catch { /* The optional native title service may not accept this session shape. */ }
2411
+ }
2412
+ for (const candidate of [
2413
+ values?.title, rowValues?.title, root.title, root.name, root.displayName,
2414
+ header?.title, header?.name, header?.displayName,
2415
+ record?.title, record?.name, recordHeader?.title, recordHeader?.name,
2416
+ ]) {
2256
2417
  const title = cleanDisplayName(candidate);
2257
2418
  if (title !== undefined)
2258
2419
  return title;
2259
2420
  }
2421
+ if (typeof root.snapshotEvents === 'function') {
2422
+ try {
2423
+ const events = root.snapshotEvents.call(value);
2424
+ if (Array.isArray(events)) {
2425
+ for (let index = events.length - 1; index >= 0; index -= 1) {
2426
+ const event = asRecord(events[index]);
2427
+ if (event?.type !== 'session/title')
2428
+ continue;
2429
+ const title = cleanDisplayName(asRecord(event.data)?.title ?? event.title);
2430
+ if (title !== undefined)
2431
+ return title;
2432
+ }
2433
+ }
2434
+ }
2435
+ catch { /* A missing or closed native history cannot block usage rendering. */ }
2436
+ }
2260
2437
  return undefined;
2261
2438
  }
2439
+ function readNativeSession(nativeSessions, sessionId) {
2440
+ try {
2441
+ return nativeSessions?.get(sessionId);
2442
+ }
2443
+ catch {
2444
+ return undefined;
2445
+ }
2446
+ }
2447
+ function readOptionalDshService(context, name) {
2448
+ const get = context?.get;
2449
+ if (typeof get !== 'function')
2450
+ return undefined;
2451
+ try {
2452
+ return get.call(context, name);
2453
+ }
2454
+ catch {
2455
+ return undefined;
2456
+ }
2457
+ }
2262
2458
  function cleanDisplayName(value) {
2263
2459
  if (typeof value !== 'string')
2264
2460
  return undefined;