@jingyi0605/codingns4dsh 0.2.1-beta.10 → 0.2.1-beta.12

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 (248) hide show
  1. package/README.en.md +7 -5
  2. package/README.md +12 -6
  3. package/data/build/dist/bootstrap/dsh-connection-adapter.d.ts +1 -1
  4. package/data/build/dist/bootstrap/index.d.ts +1 -1
  5. package/data/build/dist/client/bundle.js +9713 -7350
  6. package/data/build/dist/client/bundle.js.map +1 -1
  7. package/data/build/dist/client/cli-slots.d.ts +13 -3
  8. package/data/build/dist/client/cli-slots.d.ts.map +1 -1
  9. package/data/build/dist/client/cli-slots.js +93 -18
  10. package/data/build/dist/client/cli-slots.js.map +1 -1
  11. package/data/build/dist/client/client.assistant-configuration-view.js +4 -3
  12. package/data/build/dist/client/client.assistant-configuration-view.js.map +1 -1
  13. package/data/build/dist/client/client.moss-voice-output.js +415 -0
  14. package/data/build/dist/client/client.moss-voice-output.js.map +1 -0
  15. package/data/build/dist/client/client.native-workspace-store.js +4 -4
  16. package/data/build/dist/client/client.native-workspace-store.js.map +1 -1
  17. package/data/build/dist/client/client.primitives-adapter.js.map +1 -1
  18. package/data/build/dist/client/client.react-dom.js +0 -404
  19. package/data/build/dist/client/client.react-dom.js.map +1 -1
  20. package/data/build/dist/client/client.session-translation.js +105 -2
  21. package/data/build/dist/client/client.session-translation.js.map +1 -1
  22. package/data/build/dist/client/client.transport.js +103 -11
  23. package/data/build/dist/client/client.transport.js.map +1 -1
  24. package/data/build/dist/client/conversation-translation-settings.d.ts +10 -0
  25. package/data/build/dist/client/conversation-translation-settings.d.ts.map +1 -0
  26. package/data/build/dist/client/conversation-translation-settings.js +52 -0
  27. package/data/build/dist/client/conversation-translation-settings.js.map +1 -0
  28. package/data/build/dist/client/desktop-assistant.js +569 -450
  29. package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
  30. package/data/build/dist/client/features/cli-adapters.js +3 -1
  31. package/data/build/dist/client/features/cli-adapters.js.map +1 -1
  32. package/data/build/dist/client/features/session-translation.d.ts.map +1 -1
  33. package/data/build/dist/client/features/session-translation.js +49 -48
  34. package/data/build/dist/client/features/session-translation.js.map +1 -1
  35. package/data/build/dist/client/locales/panels.d.ts.map +1 -1
  36. package/data/build/dist/client/locales/panels.js +103 -11
  37. package/data/build/dist/client/locales/panels.js.map +1 -1
  38. package/data/build/dist/client/native-model-translation-menu.d.ts +6 -0
  39. package/data/build/dist/client/native-model-translation-menu.d.ts.map +1 -0
  40. package/data/build/dist/client/native-model-translation-menu.js +57 -0
  41. package/data/build/dist/client/native-model-translation-menu.js.map +1 -0
  42. package/data/build/dist/client/native-translation-default.d.ts +6 -0
  43. package/data/build/dist/client/native-translation-default.d.ts.map +1 -0
  44. package/data/build/dist/client/native-translation-default.js +30 -0
  45. package/data/build/dist/client/native-translation-default.js.map +1 -0
  46. package/data/build/dist/client/session-translation-chat.d.ts +23 -2
  47. package/data/build/dist/client/session-translation-chat.d.ts.map +1 -1
  48. package/data/build/dist/client/session-translation-chat.js +238 -72
  49. package/data/build/dist/client/session-translation-chat.js.map +1 -1
  50. package/data/build/dist/client/session-translation-interactions.d.ts +31 -0
  51. package/data/build/dist/client/session-translation-interactions.d.ts.map +1 -0
  52. package/data/build/dist/client/session-translation-interactions.js +158 -0
  53. package/data/build/dist/client/session-translation-interactions.js.map +1 -0
  54. package/data/build/dist/client/session-translation-model-pool.d.ts +16 -0
  55. package/data/build/dist/client/session-translation-model-pool.d.ts.map +1 -0
  56. package/data/build/dist/client/session-translation-model-pool.js +246 -0
  57. package/data/build/dist/client/session-translation-model-pool.js.map +1 -0
  58. package/data/build/dist/client/session-translation-pending-dom.d.ts +8 -0
  59. package/data/build/dist/client/session-translation-pending-dom.d.ts.map +1 -0
  60. package/data/build/dist/client/session-translation-pending-dom.js +58 -0
  61. package/data/build/dist/client/session-translation-pending-dom.js.map +1 -0
  62. package/data/build/dist/client/session-translation-retry.d.ts +16 -0
  63. package/data/build/dist/client/session-translation-retry.d.ts.map +1 -0
  64. package/data/build/dist/client/session-translation-retry.js +66 -0
  65. package/data/build/dist/client/session-translation-retry.js.map +1 -0
  66. package/data/build/dist/client/session-translation-rules.d.ts +13 -0
  67. package/data/build/dist/client/session-translation-rules.d.ts.map +1 -0
  68. package/data/build/dist/client/session-translation-rules.js +58 -0
  69. package/data/build/dist/client/session-translation-rules.js.map +1 -0
  70. package/data/build/dist/client/style.css +276 -276
  71. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.d.ts +30 -0
  72. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.d.ts.map +1 -0
  73. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.js +93 -0
  74. package/data/build/dist/dsh-capabilities/client/interaction-presentation-adapter.js.map +1 -0
  75. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.d.ts +12 -0
  76. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.d.ts.map +1 -0
  77. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.js +181 -0
  78. package/data/build/dist/dsh-capabilities/client/interaction-text-dom.js.map +1 -0
  79. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.d.ts +19 -0
  80. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.d.ts.map +1 -0
  81. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.js +39 -0
  82. package/data/build/dist/dsh-capabilities/client/message-presentation-adapter.js.map +1 -0
  83. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts +1 -0
  84. package/data/build/dist/dsh-capabilities/client/primitives-adapter.d.ts.map +1 -1
  85. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js +1 -0
  86. package/data/build/dist/dsh-capabilities/client/primitives-adapter.js.map +1 -1
  87. package/data/build/dist/dsh-capabilities/client/question-history-adapter.d.ts +20 -0
  88. package/data/build/dist/dsh-capabilities/client/question-history-adapter.d.ts.map +1 -0
  89. package/data/build/dist/dsh-capabilities/client/question-history-adapter.js +65 -0
  90. package/data/build/dist/dsh-capabilities/client/question-history-adapter.js.map +1 -0
  91. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.d.ts +32 -0
  92. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.d.ts.map +1 -0
  93. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.js +90 -0
  94. package/data/build/dist/dsh-capabilities/client/slot-presentation-mirror.js.map +1 -0
  95. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.d.ts +10 -0
  96. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.d.ts.map +1 -0
  97. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.js +34 -0
  98. package/data/build/dist/dsh-capabilities/host/connection-peer-adapter.js.map +1 -0
  99. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.d.ts +21 -0
  100. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.d.ts.map +1 -0
  101. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.js +11 -0
  102. package/data/build/dist/dsh-capabilities/host/native-dispatch-adapter.js.map +1 -0
  103. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.d.ts +17 -0
  104. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.d.ts.map +1 -0
  105. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.js +37 -0
  106. package/data/build/dist/dsh-capabilities/host/session-activation-adapter.js.map +1 -0
  107. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts +17 -0
  108. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.d.ts.map +1 -0
  109. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js +45 -0
  110. package/data/build/dist/dsh-capabilities/host/session-model-selection-adapter.js.map +1 -0
  111. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.d.ts +28 -0
  112. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.d.ts.map +1 -0
  113. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.js +31 -0
  114. package/data/build/dist/dsh-capabilities/host/session-prompt-adapter.js.map +1 -0
  115. package/data/build/dist/dsh-capabilities/matrix.d.ts.map +1 -1
  116. package/data/build/dist/dsh-capabilities/matrix.js +12 -3
  117. package/data/build/dist/dsh-capabilities/matrix.js.map +1 -1
  118. package/data/build/dist/dsh-capabilities/routes.d.ts.map +1 -1
  119. package/data/build/dist/dsh-capabilities/routes.js +26 -3
  120. package/data/build/dist/dsh-capabilities/routes.js.map +1 -1
  121. package/data/build/dist/dsh-capabilities/types.d.ts +1 -1
  122. package/data/build/dist/dsh-capabilities/types.d.ts.map +1 -1
  123. package/data/build/dist/dsh-capabilities/types.js.map +1 -1
  124. package/data/build/dist/host/cli-adapters/antigravity-driver.js +1 -1
  125. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -1
  126. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts +4 -3
  127. package/data/build/dist/host/cli-adapters/attachment-utils.d.ts.map +1 -1
  128. package/data/build/dist/host/cli-adapters/attachment-utils.js +9 -8
  129. package/data/build/dist/host/cli-adapters/attachment-utils.js.map +1 -1
  130. package/data/build/dist/host/cli-adapters/claude-driver.js +1 -1
  131. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  132. package/data/build/dist/host/cli-adapters/command-code-driver.js +1 -1
  133. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  134. package/data/build/dist/host/cli-adapters/driver.d.ts +2 -0
  135. package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
  136. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts +2 -0
  137. package/data/build/dist/host/cli-adapters/dsh-message-projector.d.ts.map +1 -1
  138. package/data/build/dist/host/cli-adapters/dsh-message-projector.js +28 -11
  139. package/data/build/dist/host/cli-adapters/dsh-message-projector.js.map +1 -1
  140. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  141. package/data/build/dist/host/cli-adapters/feature.js +369 -70
  142. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  143. package/data/build/dist/host/cli-adapters/gemini-driver.js +1 -1
  144. package/data/build/dist/host/cli-adapters/gemini-driver.js.map +1 -1
  145. package/data/build/dist/host/cli-adapters/grok-driver.d.ts +1 -0
  146. package/data/build/dist/host/cli-adapters/grok-driver.d.ts.map +1 -1
  147. package/data/build/dist/host/cli-adapters/grok-driver.js +128 -40
  148. package/data/build/dist/host/cli-adapters/grok-driver.js.map +1 -1
  149. package/data/build/dist/host/cli-adapters/kimi-driver.js +2 -2
  150. package/data/build/dist/host/cli-adapters/kimi-driver.js.map +1 -1
  151. package/data/build/dist/host/cli-adapters/mcode-driver.js +1 -1
  152. package/data/build/dist/host/cli-adapters/mcode-driver.js.map +1 -1
  153. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js +2 -2
  154. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js.map +1 -1
  155. package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts.map +1 -1
  156. package/data/build/dist/host/cli-adapters/native-team-subagent.js +2 -1
  157. package/data/build/dist/host/cli-adapters/native-team-subagent.js.map +1 -1
  158. package/data/build/dist/host/cli-adapters/pi-driver.js +1 -1
  159. package/data/build/dist/host/cli-adapters/pi-driver.js.map +1 -1
  160. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  161. package/data/build/dist/host/cli-adapters/registry.js +25 -16
  162. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  163. package/data/build/dist/host/cli-adapters/zcode-driver.js +1 -1
  164. package/data/build/dist/host/cli-adapters/zcode-driver.js.map +1 -1
  165. package/data/build/dist/host/features/global-voice-rpc.d.ts.map +1 -1
  166. package/data/build/dist/host/features/global-voice-rpc.js +2 -3
  167. package/data/build/dist/host/features/global-voice-rpc.js.map +1 -1
  168. package/data/build/dist/host/features/interaction-translation.d.ts +9 -0
  169. package/data/build/dist/host/features/interaction-translation.d.ts.map +1 -0
  170. package/data/build/dist/host/features/interaction-translation.js +168 -0
  171. package/data/build/dist/host/features/interaction-translation.js.map +1 -0
  172. package/data/build/dist/host/features/peer-host.js +6 -6
  173. package/data/build/dist/host/features/peer-host.js.map +1 -1
  174. package/data/build/dist/host/features/session-prompt-translation.d.ts +10 -0
  175. package/data/build/dist/host/features/session-prompt-translation.d.ts.map +1 -0
  176. package/data/build/dist/host/features/session-prompt-translation.js +197 -0
  177. package/data/build/dist/host/features/session-prompt-translation.js.map +1 -0
  178. package/data/build/dist/host/features/session-translation-protection.d.ts +16 -0
  179. package/data/build/dist/host/features/session-translation-protection.d.ts.map +1 -0
  180. package/data/build/dist/host/features/session-translation-protection.js +183 -0
  181. package/data/build/dist/host/features/session-translation-protection.js.map +1 -0
  182. package/data/build/dist/host/features/session-translation-recovery.d.ts +20 -0
  183. package/data/build/dist/host/features/session-translation-recovery.d.ts.map +1 -0
  184. package/data/build/dist/host/features/session-translation-recovery.js +122 -0
  185. package/data/build/dist/host/features/session-translation-recovery.js.map +1 -0
  186. package/data/build/dist/host/features/session-translation-runtime.d.ts +16 -0
  187. package/data/build/dist/host/features/session-translation-runtime.d.ts.map +1 -0
  188. package/data/build/dist/host/features/session-translation-runtime.js +85 -0
  189. package/data/build/dist/host/features/session-translation-runtime.js.map +1 -0
  190. package/data/build/dist/host/features/session-translation-service.d.ts +14 -4
  191. package/data/build/dist/host/features/session-translation-service.d.ts.map +1 -1
  192. package/data/build/dist/host/features/session-translation-service.js +217 -211
  193. package/data/build/dist/host/features/session-translation-service.js.map +1 -1
  194. package/data/build/dist/host/features/session-translation.d.ts.map +1 -1
  195. package/data/build/dist/host/features/session-translation.js +5 -2
  196. package/data/build/dist/host/features/session-translation.js.map +1 -1
  197. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.d.ts +2 -2
  198. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.d.ts.map +1 -1
  199. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js +10 -9
  200. package/data/build/dist/host/modules/peer-host/peer-host-native-dispatch.js.map +1 -1
  201. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.d.ts +3 -3
  202. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.d.ts.map +1 -1
  203. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.js +26 -14
  204. package/data/build/dist/host/modules/peer-host/peer-host-native-streams.js.map +1 -1
  205. package/data/build/dist/host/native-session-bridge.d.ts +2 -1
  206. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  207. package/data/build/dist/host/native-session-bridge.js +7 -15
  208. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  209. package/data/build/dist/host/rpc.d.ts.map +1 -1
  210. package/data/build/dist/host/rpc.js +14 -8
  211. package/data/build/dist/host/rpc.js.map +1 -1
  212. package/data/build/dist/host/settings.d.ts.map +1 -1
  213. package/data/build/dist/host/settings.js +10 -2
  214. package/data/build/dist/host/settings.js.map +1 -1
  215. package/data/build/dist/shared/attachment-prompts.d.ts +11 -0
  216. package/data/build/dist/shared/attachment-prompts.d.ts.map +1 -0
  217. package/data/build/dist/shared/attachment-prompts.js +17 -0
  218. package/data/build/dist/shared/attachment-prompts.js.map +1 -0
  219. package/data/build/dist/shared/contracts/cli-adapter.d.ts +8 -1
  220. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  221. package/data/build/dist/shared/contracts/errors.d.ts +2 -2
  222. package/data/build/dist/shared/contracts/interaction-presentation.d.ts +32 -0
  223. package/data/build/dist/shared/contracts/interaction-presentation.d.ts.map +1 -0
  224. package/data/build/dist/shared/contracts/interaction-presentation.js +2 -0
  225. package/data/build/dist/shared/contracts/interaction-presentation.js.map +1 -0
  226. package/data/build/dist/shared/contracts/interaction-translation.d.ts +53 -0
  227. package/data/build/dist/shared/contracts/interaction-translation.d.ts.map +1 -0
  228. package/data/build/dist/shared/contracts/interaction-translation.js +44 -0
  229. package/data/build/dist/shared/contracts/interaction-translation.js.map +1 -0
  230. package/data/build/dist/shared/contracts/session-translation.d.ts +75 -7
  231. package/data/build/dist/shared/contracts/session-translation.d.ts.map +1 -1
  232. package/data/build/dist/shared/contracts/session-translation.js +132 -2
  233. package/data/build/dist/shared/contracts/session-translation.js.map +1 -1
  234. package/data/build/dist/shared/contracts/version.d.ts +4 -4
  235. package/data/build/dist/shared/contracts/version.js +3 -3
  236. package/data/build/dist/shared/index.d.ts +1 -1
  237. package/data/build/dist/shared/index.d.ts.map +1 -1
  238. package/data/build/dist/shared/index.js +1 -1
  239. package/data/build/dist/shared/index.js.map +1 -1
  240. package/data/build/dist/shared/translation-indicator.d.ts +26 -0
  241. package/data/build/dist/shared/translation-indicator.d.ts.map +1 -0
  242. package/data/build/dist/shared/translation-indicator.js +29 -0
  243. package/data/build/dist/shared/translation-indicator.js.map +1 -0
  244. package/package.json +35 -34
  245. package/profile/README.md +2 -2
  246. package/profile/package.json +3 -3
  247. package/profile/version.json +3 -3
  248. package/version.json +3 -3
@@ -31,7 +31,14 @@ import { containsDelegationCarrier, delegationAdapterIds, rewriteDelegationMessa
31
31
  import { clearDelegationAuthorization, setDelegationAuthorization } from './delegation-authorization.js';
32
32
  import { debugInfo } from '../../shared/debug.js';
33
33
  import { hasLegacySessionAdapterHint } from './legacy-session-adapter.js';
34
- import {} from '../../shared/contracts/session-translation.js';
34
+ import { DSH_NATIVE_SESSION_ADAPTER_ID, formatSessionTranslationOutput, readSessionTranslationMetrics, } from '../../shared/contracts/session-translation.js';
35
+ import { createInteractionTranslation } from '../features/interaction-translation.js';
36
+ import { createSessionTranslationRecovery, SessionTranslationCancelled } from '../features/session-translation-recovery.js';
37
+ import { ASSISTANT_AGENT_PREFIX } from '../../dsh-capabilities/host/assistant-agent-adapter.js';
38
+ import { createDshCapabilityRegistry } from '../../dsh-capabilities/routes.js';
39
+ import { attachmentFileNotice } from '../../shared/attachment-prompts.js';
40
+ import { sessionPromptScope } from '../../dsh-capabilities/host/session-prompt-adapter.js';
41
+ import { createSessionPromptTranslation } from '../features/session-prompt-translation.js';
35
42
  export function createCliAdaptersFeature(options = {}) {
36
43
  return {
37
44
  descriptor: {
@@ -40,8 +47,10 @@ export function createCliAdaptersFeature(options = {}) {
40
47
  enabledByDefault: true,
41
48
  dependencies: [],
42
49
  runtime: 'host',
50
+ requires: [{ capability: 'session.external-model-selection', required: false, fallback: 'degrade' }],
43
51
  },
44
52
  async start(context) {
53
+ const lifetime = new AbortController();
45
54
  // 用量查询超时对所有适配器统一生效:同一个 timeoutMs 下发给全部网络读取器,
46
55
  // 设置变更时整体重建,避免旧超时继续生效。
47
56
  const buildSubscriptions = (settings) => {
@@ -91,6 +100,18 @@ export function createCliAdaptersFeature(options = {}) {
91
100
  virtualProviders?.setProviders(registry.enabledAdapterIds());
92
101
  if (virtualProviders !== undefined)
93
102
  context.resources.add(() => virtualProviders.dispose());
103
+ if (context.services.dshContext !== undefined && context.services.dshVersion !== undefined) {
104
+ const selection = createDshCapabilityRegistry(context.services.dshVersion, 'host', context.services.dshContext)
105
+ .getProfile(context.services.dshContext).capabilities.get('session.external-model-selection')
106
+ ?.value;
107
+ if (selection !== undefined)
108
+ context.resources.add(selection.install((sessionId) => {
109
+ const config = registry.getSession(sessionId);
110
+ if (config.adapterId === 'dsh' || !registry.isEnabled(config.adapterId))
111
+ return undefined;
112
+ return { provider: config.adapterId, model: config.modelId?.trim() || 'provider-default' };
113
+ }));
114
+ }
94
115
  registry.warmCatalog();
95
116
  // 外部适配器没有经过 DSH 原生 Agent Loop,必须把同一套运行状态投影给
96
117
  // subagentCatalog。这里集中去重,避免 llm/stream、原生 session/event 和
@@ -130,8 +151,27 @@ export function createCliAdaptersFeature(options = {}) {
130
151
  });
131
152
  context.resources.add(disposeNativeEvents);
132
153
  }
133
- registerDelegationAgentHooks(context.services.dshContext, registry, context.resources, publishExternalStatus, translation);
134
- context.resources.add(context.services.rpc.register('cli', (action, payload) => {
154
+ const translationRecovery = createSessionTranslationRecovery(translation, sessionId => registry.getSession(sessionId));
155
+ context.resources.add(() => translationRecovery.dispose());
156
+ const promptTranslation = createSessionPromptTranslation(translation, sessionId => {
157
+ if (!sessionId || isAssistantSession(sessionId) || readNativeSessionHeader(nativeSessions, sessionId)?.origin === 'subagent')
158
+ return undefined;
159
+ const config = registry.getSession(sessionId);
160
+ return (config.translationEnabled ?? (config.adapterId === 'dsh' && translation?.nativeEnabled === true)) ? config.adapterId : undefined;
161
+ });
162
+ context.resources.add(() => promptTranslation.dispose());
163
+ if (context.services.dshContext !== undefined && context.services.dshVersion !== undefined) {
164
+ const adapter = createDshCapabilityRegistry(context.services.dshVersion, 'host', context.services.dshContext)
165
+ .getProfile(context.services.dshContext).capabilities.get('session.prompt-assembly')?.value;
166
+ if (adapter !== undefined)
167
+ context.resources.add(adapter.install((assembly, scope) => promptTranslation.translateAssembly(assembly, scope)));
168
+ }
169
+ registerDelegationAgentHooks(context.services.dshContext, registry, context.resources, publishExternalStatus, translation, translationRecovery, promptTranslation, nativeSessions);
170
+ const interactionTranslation = createInteractionTranslation(translation, sessionId => registry.getSession(sessionId));
171
+ context.resources.add(() => interactionTranslation.dispose());
172
+ context.resources.add(context.services.rpc.register('cli', (action, payload, rpcContext) => {
173
+ const rpcSignal = asRecord(rpcContext)?.signal;
174
+ const signal = isAbortSignal(rpcSignal) ? rpcSignal : undefined;
135
175
  switch (action) {
136
176
  case 'catalog': return registry.catalog();
137
177
  case 'catalog/refresh': return registry.refreshCatalog(asRecord(payload)?.adapterId === undefined ? undefined : readAdapterId(payload)).then((catalog) => {
@@ -143,6 +183,10 @@ export function createCliAdaptersFeature(options = {}) {
143
183
  return adapterId === 'dsh' ? readDshModelCatalog(context.services.dshContext) : registry.models(adapterId);
144
184
  }
145
185
  case 'translation/models': return context.services.sessionTranslation?.catalog() ?? { models: [], default: null, errors: ['翻译模型目录不可用'] };
186
+ case 'translation/pending': return translationRecovery.pending(payload);
187
+ case 'translation/resolve': return translationRecovery.resolve(payload);
188
+ case 'translation/retry-output': return translationRecovery.retryOutput(payload, signal);
189
+ case 'translation/fields': return interactionTranslation.translate(payload, signal);
146
190
  case 'skills': {
147
191
  const request = readSkillsRequest(payload);
148
192
  const cwd = resolveSessionCwd(context.services.nativeSessions, request.sessionId, null);
@@ -160,15 +204,26 @@ export function createCliAdaptersFeature(options = {}) {
160
204
  }
161
205
  return registry.getSession(sessionId);
162
206
  }
163
- case 'session/set': return registry.setSession(readSessionId(payload), readSessionConfig(payload));
207
+ case 'session/set': {
208
+ const sessionId = readSessionId(payload);
209
+ const previous = registry.getSession(sessionId);
210
+ const config = registry.setSession(sessionId, readSessionConfig(payload));
211
+ if (config.translationEnabled === false || previous.adapterId !== config.adapterId)
212
+ translationRecovery.cancelSession(sessionId);
213
+ return config;
214
+ }
164
215
  case 'session/list': return registry.listSessions(readSessionListOptions(payload));
165
216
  case 'session/adapter-map':
166
217
  // 列表查询只返回持久化索引,不能触发会话加载或历史解析。
167
218
  return sessionStore.adapterBindings();
168
219
  case 'session/archive': return registry.archiveSession(readSessionId(payload));
169
- case 'session/steer': return translateSteerPrompt(translation, registry, readSessionId(payload), readPrompt(payload), false);
170
- case 'session/follow-up': return translateSteerPrompt(translation, registry, readSessionId(payload), readPrompt(payload), true);
171
- case 'session/interrupt': return registry.interrupt(readSessionId(payload));
220
+ case 'session/steer': return translateSteerPrompt(translationRecovery, registry, readSessionId(payload), readPrompt(payload), false, signal);
221
+ case 'session/follow-up': return translateSteerPrompt(translationRecovery, registry, readSessionId(payload), readPrompt(payload), true, signal);
222
+ case 'session/interrupt': {
223
+ const sessionId = readSessionId(payload);
224
+ translationRecovery.cancelSession(sessionId);
225
+ return registry.interrupt(sessionId);
226
+ }
172
227
  case 'team/status': return context.services.nativeTeam?.diagnostic() ?? registry.teamDiagnostic();
173
228
  case 'team/members': return requireTeam(context).invoke('members', payload);
174
229
  case 'team/tasks': return requireTeam(context).invoke('tasks', payload);
@@ -259,6 +314,7 @@ export function createCliAdaptersFeature(options = {}) {
259
314
  virtualProviders?.setProviders(registry.enabledAdapterIds());
260
315
  registry.syncPreferences(next.agentAdapterPreferences);
261
316
  sessionStore.sync(next.cliSessions);
317
+ translationRecovery.cancelDisabled(next.sessionTranslation?.nativeEnabled === true, next.sessionTranslation?.inputEnabled !== false);
262
318
  const nextSubscriptionSettings = JSON.stringify(next.subscriptionUsage ?? null);
263
319
  if (nextSubscriptionSettings !== subscriptionSettings) {
264
320
  subscriptionSettings = nextSubscriptionSettings;
@@ -344,6 +400,18 @@ export function createCliAdaptersFeature(options = {}) {
344
400
  if (sessionId !== '' && (selection.modelId !== undefined || selection.effortId !== undefined)) {
345
401
  registry.setSession(sessionId, { adapterId: 'dsh', ...selection });
346
402
  }
403
+ // Native-session output translation wraps only DSH's own Provider stream:
404
+ // preserve text/reasoning and attach the translation record; the client renders it.
405
+ if (!isSessionTranslationLlmRequest(value)
406
+ && translation !== undefined
407
+ && translation.enabled
408
+ && (config.translationEnabled ?? translation.nativeEnabled === true)
409
+ && sessionId !== ''
410
+ && !isAssistantSession(sessionId)
411
+ && readNativeSessionHeader(nativeSessions, sessionId)?.origin !== 'subagent') {
412
+ yield* guardDshNativeStream(() => translateNativeStream(next(), translation, translation.bilingual === true, isAbortSignal(value?.signal) ? value.signal : undefined));
413
+ return;
414
+ }
347
415
  yield* guardDshNativeStream(next);
348
416
  return;
349
417
  }
@@ -351,19 +419,24 @@ export function createCliAdaptersFeature(options = {}) {
351
419
  const turnInput = extractTurnInput(messages, context.services.dshContext);
352
420
  const turnOptions = readCliTurnOptions(value);
353
421
  const runtimeEnv = readCliRuntimeEnv(value);
354
- const requestSignal = isAbortSignal(value?.signal) ? value.signal : undefined;
422
+ const requestSignal = isAbortSignal(value?.signal)
423
+ ? AbortSignal.any([value.signal, lifetime.signal])
424
+ : lifetime.signal;
425
+ const translationLanguage = translation?.enabled && translation.inputEnabled !== false && config.translationEnabled === true ? readInputTargetLanguage(translation) : undefined;
355
426
  let translatedPrompt = turnInput.translationPrompt;
356
- if (turnInput.translatePrompt && translation !== undefined) {
427
+ if (turnInput.translatePrompt && turnInput.translationPrompt.trim() !== '' && translationLanguage !== undefined) {
357
428
  try {
358
- translatedPrompt = await translation.translateInput(config.adapterId, turnInput.translationPrompt, requestSignal, config.translationEnabled === true);
429
+ translatedPrompt = (await translationRecovery.translateInput(sessionId, config.adapterId, turnInput.translationPrompt, requestSignal)).translated;
359
430
  }
360
- catch {
361
- // 翻译服务异常不能阻断外部 Agent 主任务;本轮直接发送原文。
362
- translatedPrompt = turnInput.translationPrompt;
431
+ catch (error) {
432
+ if (!(error instanceof SessionTranslationCancelled) && !requestSignal?.aborted)
433
+ throw error;
434
+ yield { type: 'finish', reason: { kind: 'stop' } };
435
+ return;
363
436
  }
364
437
  }
365
438
  const prompt = turnInput.translatePrompt
366
- ? [translatedPrompt, turnInput.untranslatedPrompt].filter((part) => part.trim() !== '').join('\n\n')
439
+ ? [translatedPrompt, translationLanguage === undefined ? turnInput.untranslatedPrompt : attachmentNotices(turnInput.attachments, translationLanguage)].filter((part) => part.trim() !== '').join('\n\n')
367
440
  : turnInput.prompt;
368
441
  // 权限状态必须与 DSH 会话当前生效值同源。驱动不能自行假设“完全权限”,
369
442
  // 也不能把缺省当成“已确认无限制”:解析失败时留空,由驱动沿用保守默认。
@@ -376,6 +449,7 @@ export function createCliAdaptersFeature(options = {}) {
376
449
  sessionId,
377
450
  messages: turnInput.messages,
378
451
  prompt,
452
+ ...(translationLanguage === undefined ? {} : { translationLanguage }),
379
453
  ...(turnInput.attachments.length === 0 ? {} : { attachments: turnInput.attachments }),
380
454
  ...(permission === undefined ? {} : { permission }),
381
455
  ...(turnOptions.plan === true ? { plan: true } : {}),
@@ -472,6 +546,8 @@ export function createCliAdaptersFeature(options = {}) {
472
546
  // DSH 先停用设置上下文再执行异步资源清理时,旧的写队列不能继续调用
473
547
  // configEditor,否则会出现 inactive context 并污染退出日志。
474
548
  sessionStore.dispose();
549
+ // Close native interaction cards before unloading their provider transports.
550
+ lifetime.abort();
475
551
  await registry.dispose();
476
552
  await sessionStore.flush();
477
553
  setAdapterRegistry(undefined);
@@ -526,12 +602,13 @@ function createExternalSessionStatusPublisher(events, registry, sessionStore) {
526
602
  * `messages`,会直接抛出只读属性异常。`agent/pre-step` 返回的新消息保留用户原文,并把
527
603
  * 译文写入 source 元数据;外部适配器再据此构造发给 Agent 的译文。
528
604
  */
529
- function registerDelegationAgentHooks(dshContext, registry, resources, publishExternalStatus, translation) {
605
+ function registerDelegationAgentHooks(dshContext, registry, resources, publishExternalStatus, translation, translationRecovery, promptTranslation, nativeSessions) {
530
606
  if (dshContext === undefined || typeof dshContext.on !== 'function')
531
607
  return;
532
608
  const disposers = [];
533
609
  const register = (name, listener) => {
534
- const disposer = dshContext.on(name, listener, { global: true });
610
+ // Wrap existing pre-step producers so their after-next catalogs are visible.
611
+ const disposer = dshContext.on(name, listener, { global: true, prepend: name === 'agent/pre-step' });
535
612
  if (typeof disposer === 'function')
536
613
  disposers.push(disposer);
537
614
  };
@@ -588,20 +665,44 @@ function registerDelegationAgentHooks(dshContext, registry, resources, publishEx
588
665
  messages = [...delegation.value.messages];
589
666
  }
590
667
  }
591
- const translatedInput = await translatePreStepInput(translation, registry, sessionId, messages, dshContext, isAbortSignal(record?.signal) ? record.signal : undefined);
668
+ let translatedInput;
669
+ try {
670
+ translatedInput = await translatePreStepInput(translation, translationRecovery, registry, sessionId, messages, dshContext, isAbortSignal(record?.signal) ? record.signal : undefined, nativePreStepContext(translation, registry, sessionId, nativeSessions));
671
+ }
672
+ catch (error) {
673
+ if (error instanceof SessionTranslationCancelled || record?.signal?.aborted === true)
674
+ return { kind: 'reject' };
675
+ throw error;
676
+ }
592
677
  if (translatedInput !== undefined)
593
678
  messages = [...translatedInput];
594
679
  const decision = await next();
595
680
  if (!isEnterPreStepDecision(decision))
596
681
  return decision;
597
- if (delegation?.kind !== 'rewritten' && translatedInput === undefined)
598
- return decision;
599
- // `next()` 可能已经追加系统上下文或模型切换通知,只替换它前面的本轮用户消息。
600
- const suffix = decision.messages.slice(messages.length);
601
- return {
602
- ...decision,
603
- messages: [...messages, ...suffix],
604
- };
682
+ // Match producer-owned messages by identity. Downstream middleware may insert
683
+ // instructions before the human message, so a positional prefix loses data.
684
+ const incoming = Array.isArray(record?.messages) ? record.messages.filter(isMessage) : [];
685
+ const replaced = new Set();
686
+ const accepted = delegation?.kind === 'rewritten' || translatedInput !== undefined
687
+ ? decision.messages.map(message => {
688
+ const index = incoming.findIndex((original, position) => !replaced.has(position) && (original === message || typeof original.id === 'string' && original.id === message.id
689
+ // Legacy callers can return detached messages without durable IDs.
690
+ || original.id === undefined && message.id === undefined && original.role === message.role
691
+ && original.source?.kind === message.source?.kind && original.source?.form === message.source?.form
692
+ && JSON.stringify(original.content) === JSON.stringify(message.content)));
693
+ if (index >= 0)
694
+ replaced.add(index);
695
+ return index < 0 ? message : messages[index] ?? message;
696
+ }) : decision.messages;
697
+ try {
698
+ const scope = sessionPromptScope(record?.agent, isAbortSignal(record?.signal) ? record.signal : undefined);
699
+ return { ...decision, messages: await promptTranslation.translateMessages(accepted, scope) };
700
+ }
701
+ catch (error) {
702
+ if (error instanceof SessionTranslationCancelled || record?.signal?.aborted === true)
703
+ return { kind: 'reject' };
704
+ throw error;
705
+ }
605
706
  });
606
707
  // 正常结束、异常结束和中断最终都会进入 idle;在此清理本轮授权,避免下一轮
607
708
  // 没有 carrier 时继承上一轮的目标白名单。
@@ -611,12 +712,14 @@ function registerDelegationAgentHooks(dshContext, registry, resources, publishEx
611
712
  if (record?.status === 'running')
612
713
  publishExternalStatus(sessionId, true);
613
714
  else if (record?.status === 'idle') {
715
+ translationRecovery.cancelSession(sessionId);
614
716
  publishExternalStatus(sessionId, false);
615
717
  clearDelegationAuthorization(sessionId);
616
718
  }
617
719
  });
618
720
  register('agent/disposed', (payload) => {
619
721
  const sessionId = readAgentSessionId(asRecord(payload)?.agent);
722
+ translationRecovery.cancelSession(sessionId);
620
723
  // disposed 只代表运行时释放;subagentCatalog 没有删除语义,保留目录项并
621
724
  // 把它标成 idle,后续若 DSH 重新激活会再次收到 running。
622
725
  publishExternalStatus(sessionId, false);
@@ -979,18 +1082,20 @@ async function setAdapterEnabled(settings, registry, value) {
979
1082
  return { adapterId, enabled };
980
1083
  }
981
1084
  /** 运行中的外部 Agent 插话也必须经过同一条输入翻译路径。 */
982
- async function translateSteerPrompt(translation, registry, sessionId, prompt, followUp) {
1085
+ async function translateSteerPrompt(translationRecovery, registry, sessionId, prompt, followUp, signal) {
983
1086
  const session = registry.getSession(sessionId);
984
1087
  let translated = prompt;
985
- if (translation !== undefined) {
1088
+ if (session.translationEnabled === true) {
986
1089
  try {
987
- translated = await translation.translateInput(session.adapterId, prompt, undefined, session.translationEnabled === true);
1090
+ translated = (await translationRecovery.translateInput(sessionId, session.adapterId, prompt, signal)).translated;
988
1091
  }
989
- catch {
990
- // 插话翻译失败时仍把用户原文交给外部 Agent。
991
- translated = prompt;
1092
+ catch (error) {
1093
+ if (error instanceof SessionTranslationCancelled)
1094
+ return;
1095
+ throw error;
992
1096
  }
993
1097
  }
1098
+ signal?.throwIfAborted();
994
1099
  await registry.steer(sessionId, translated, followUp);
995
1100
  }
996
1101
  /**
@@ -999,9 +1104,42 @@ async function translateSteerPrompt(translation, registry, sessionId, prompt, fo
999
1104
  * DSH 随后会把返回的消息写入 `user/message`。正文只放实际发送的译文;原文和模型
1000
1105
  * 记录在 source 元数据中供 Client 详情按钮读取。外部 CLI 边界也会移除该元数据。
1001
1106
  */
1002
- async function translatePreStepInput(translation, registry, sessionId, messages, dshContext, signal) {
1003
- if (translation === undefined || !translation.enabled || sessionId.trim() === '')
1107
+ async function translatePreStepInput(translation, translationRecovery, registry, sessionId, messages, dshContext, signal, nativeSession) {
1108
+ if (translation === undefined || !translation.enabled || translation.inputEnabled === false || sessionId.trim() === '')
1109
+ return undefined;
1110
+ const messageIndex = findLatestHumanMessage(messages);
1111
+ const current = messageIndex < 0 ? undefined : messages[messageIndex];
1112
+ if (current === undefined || readInputTranslation(current.source) !== undefined)
1004
1113
  return undefined;
1114
+ if (nativeSession !== undefined) {
1115
+ // DSH 原生会话:消息本身就是发给 Provider 的正文,直接替换为译文并把原文留在元数据。
1116
+ if (nativeSession.translationEnabled !== true)
1117
+ return undefined;
1118
+ const input = extractTurnInput(messages, dshContext);
1119
+ if (input.prompt.trim() === '')
1120
+ return undefined;
1121
+ const result = await translationRecovery.translateInput(sessionId, nativeSession.adapterId, input.translationPrompt, signal);
1122
+ const translated = [result.translated, attachmentNotices(input.attachments, readInputTargetLanguage(translation))].filter((part) => part.trim() !== '').join('\n\n').trim();
1123
+ if (translated === '')
1124
+ return undefined;
1125
+ const record = {
1126
+ original: input.prompt,
1127
+ translated,
1128
+ ...(result.sourceLanguage === undefined ? {} : { sourceLanguage: result.sourceLanguage }),
1129
+ targetLanguage: readInputTargetLanguage(translation),
1130
+ ...(result.alreadyTargetLanguage === true ? { alreadyTargetLanguage: true } : {}),
1131
+ ...(result.model === undefined ? {} : { model: result.model }),
1132
+ ...readSessionTranslationMetrics(result),
1133
+ ...(result.failed === true ? { failed: true } : {}),
1134
+ };
1135
+ const next = [...messages];
1136
+ next[messageIndex] = {
1137
+ ...current,
1138
+ content: result.failed ? current.content : replaceInputTranslationContent(nativeTranslationContent(current.content, dshContext), translated),
1139
+ source: { ...(current.source ?? {}), kind: 'user', codingnsTranslationInput: record },
1140
+ };
1141
+ return next;
1142
+ }
1005
1143
  let session;
1006
1144
  try {
1007
1145
  session = registry.getSession(sessionId);
@@ -1011,37 +1149,11 @@ async function translatePreStepInput(translation, registry, sessionId, messages,
1011
1149
  }
1012
1150
  if (session.adapterId === 'dsh' || session.translationEnabled !== true)
1013
1151
  return undefined;
1014
- const messageIndex = findLatestHumanMessage(messages);
1015
- const current = messageIndex < 0 ? undefined : messages[messageIndex];
1016
- if (current === undefined || readInputTranslation(current.source) !== undefined)
1017
- return undefined;
1018
1152
  const input = extractTurnInput(messages, dshContext);
1019
- if (input.translationPrompt.trim() === '')
1153
+ if (input.prompt.trim() === '')
1020
1154
  return undefined;
1021
- let translatedNatural;
1022
- let translationSourceLanguage = undefined;
1023
- let alreadyTargetLanguage = false;
1024
- let translationModel;
1025
- let translationFailed = false;
1026
- try {
1027
- if (translation.translateInputDetailed !== undefined) {
1028
- const detailed = await translation.translateInputDetailed(session.adapterId, input.translationPrompt, signal, true);
1029
- translatedNatural = detailed.translated;
1030
- translationSourceLanguage = detailed.sourceLanguage;
1031
- alreadyTargetLanguage = detailed.alreadyTargetLanguage === true;
1032
- translationModel = detailed.model;
1033
- translationFailed = detailed.failed === true;
1034
- }
1035
- else {
1036
- translatedNatural = await translation.translateInput(session.adapterId, input.translationPrompt, signal, true);
1037
- }
1038
- }
1039
- catch {
1040
- // 翻译只是增强能力;异常时保持现有外部 Agent 回合继续使用原文。
1041
- translatedNatural = input.translationPrompt;
1042
- translationFailed = true;
1043
- }
1044
- const translated = [translatedNatural, input.untranslatedPrompt]
1155
+ const result = await translationRecovery.translateInput(sessionId, session.adapterId, input.translationPrompt, signal);
1156
+ const translated = [result.translated, attachmentNotices(input.attachments, readInputTargetLanguage(translation))]
1045
1157
  .filter((part) => part.trim() !== '')
1046
1158
  .join('\n\n')
1047
1159
  .trim();
@@ -1050,11 +1162,12 @@ async function translatePreStepInput(translation, registry, sessionId, messages,
1050
1162
  const record = {
1051
1163
  original: input.prompt,
1052
1164
  translated,
1053
- ...(translationSourceLanguage === undefined ? {} : { sourceLanguage: translationSourceLanguage }),
1054
- targetLanguage: translation.inputLanguage ?? 'en',
1055
- ...(alreadyTargetLanguage ? { alreadyTargetLanguage: true } : {}),
1056
- ...(translationModel === undefined ? {} : { model: translationModel }),
1057
- ...(translationFailed ? { failed: true } : {}),
1165
+ ...(result.sourceLanguage === undefined ? {} : { sourceLanguage: result.sourceLanguage }),
1166
+ targetLanguage: readInputTargetLanguage(translation),
1167
+ ...(result.alreadyTargetLanguage === true ? { alreadyTargetLanguage: true } : {}),
1168
+ ...(result.model === undefined ? {} : { model: result.model }),
1169
+ ...readSessionTranslationMetrics(result),
1170
+ ...(result.failed === true ? { failed: true } : {}),
1058
1171
  };
1059
1172
  const next = [...messages];
1060
1173
  next[messageIndex] = {
@@ -1067,6 +1180,177 @@ async function translatePreStepInput(translation, registry, sessionId, messages,
1067
1180
  };
1068
1181
  return next;
1069
1182
  }
1183
+ /** 旧测试替身没有 inputLanguage 时沿用历史默认值英语。 */
1184
+ function readInputTargetLanguage(translation) {
1185
+ const language = translation.inputLanguage;
1186
+ return language === undefined ? 'en' : language;
1187
+ }
1188
+ /**
1189
+ * 判断本次 pre-step 是否按 DSH 原生会话翻译。
1190
+ *
1191
+ * 原生会话的翻译记录来自全局 `nativeEnabled` 与适配器偏好(会话索引对 `dsh` 不落盘),
1192
+ * 子代理与插件自己的助理会话必须排除,避免把内部执行流水线翻译掉。
1193
+ */
1194
+ function nativePreStepContext(translation, registry, sessionId, nativeSessions) {
1195
+ if (translation === undefined || !translation.enabled || sessionId.trim() === '')
1196
+ return undefined;
1197
+ if (isAssistantSession(sessionId))
1198
+ return undefined;
1199
+ if (readNativeSessionHeader(nativeSessions, sessionId)?.origin === 'subagent')
1200
+ return undefined;
1201
+ let session;
1202
+ try {
1203
+ session = registry.getSession(sessionId);
1204
+ }
1205
+ catch {
1206
+ return undefined;
1207
+ }
1208
+ if (session.adapterId !== DSH_NATIVE_SESSION_ADAPTER_ID)
1209
+ return undefined;
1210
+ return {
1211
+ adapterId: DSH_NATIVE_SESSION_ADAPTER_ID,
1212
+ // 会话级开关优先;用户没有单独设置过该会话时跟随「DSH 原生会话」总开关。
1213
+ translationEnabled: session.translationEnabled ?? translation.nativeEnabled === true,
1214
+ };
1215
+ }
1216
+ /** 读取原生会话 header;测试替身或会话尚未装载时返回 undefined。 */
1217
+ function readNativeSessionHeader(nativeSessions, sessionId) {
1218
+ if (nativeSessions === undefined)
1219
+ return undefined;
1220
+ try {
1221
+ return asRecord(asRecord(nativeSessions.get(sessionId))?.header) ?? undefined;
1222
+ }
1223
+ catch {
1224
+ return undefined;
1225
+ }
1226
+ }
1227
+ /** 插件自己的全局助理会话不属于用户对话,不能被翻译改写。 */
1228
+ function isAssistantSession(sessionId) {
1229
+ // 复用助理适配器导出的会话前缀常量:会话身份的唯一定义在那里,重复字面量会漂移。
1230
+ return sessionId.startsWith(ASSISTANT_AGENT_PREFIX);
1231
+ }
1232
+ /**
1233
+ * DSH 原生会话的输出翻译。
1234
+ *
1235
+ * 原生回复由 DSH 自己组装成 `assistant/message`,插件只能在 Provider 流边界改写:
1236
+ * 文本与思考增量先缓存,块结束时一次性发出原文,并把译文放进记录供客户端显示;
1237
+ * 工具调用、usage 与 finish 原样透传。翻译失败、流中断或异常都会补发原文块,保证回复不丢失。
1238
+ */
1239
+ async function* translateNativeStream(stream, translation, bilingual, signal) {
1240
+ const blocks = new Map();
1241
+ const bufferOf = (index, kind) => {
1242
+ const existing = blocks.get(index);
1243
+ if (existing !== undefined)
1244
+ return existing.kind === kind && !existing.closed ? existing : undefined;
1245
+ const created = {
1246
+ index, kind, text: '', closed: false,
1247
+ translate: translation.shouldTranslate(DSH_NATIVE_SESSION_ADAPTER_ID, kind === 'reasoning' ? 'reasoning' : 'assistant', true),
1248
+ };
1249
+ blocks.set(index, created);
1250
+ return created;
1251
+ };
1252
+ const translateBlock = async (block) => {
1253
+ const original = block.text;
1254
+ if (original.trim() === '')
1255
+ return [];
1256
+ const kind = block.kind === 'reasoning' ? 'reasoning' : 'assistant';
1257
+ let record;
1258
+ try {
1259
+ const result = await translation.translateOutput(DSH_NATIVE_SESSION_ADAPTER_ID, kind, original, signal, true);
1260
+ if (result !== null && result !== undefined) {
1261
+ record = {
1262
+ original: result.original,
1263
+ translated: result.translated,
1264
+ ...(result.sourceLanguage === undefined ? {} : { sourceLanguage: result.sourceLanguage }),
1265
+ targetLanguage: result.targetLanguage,
1266
+ outputKind: kind,
1267
+ bilingual: result.bilingual,
1268
+ ...(result.alreadyTargetLanguage === true ? { alreadyTargetLanguage: true } : {}),
1269
+ ...(result.model === undefined ? {} : { model: result.model }),
1270
+ ...readSessionTranslationMetrics(result),
1271
+ ...(result.failed === true ? { failed: true } : {}),
1272
+ };
1273
+ }
1274
+ }
1275
+ catch {
1276
+ record = { original, translated: original, targetLanguage: translation.outputLanguage ?? 'zh', outputKind: kind, bilingual, failed: true };
1277
+ }
1278
+ if (record === undefined)
1279
+ return [];
1280
+ const visible = formatSessionTranslationOutput(record);
1281
+ if (visible.trim() === '' || visible === original && record.failed !== true)
1282
+ return [];
1283
+ return [
1284
+ // Keep the provider text in the native history. The client chooses the translated
1285
+ // text for display from codingnsTranslation.translated.
1286
+ { type: `${block.kind}-delta`, index: block.index, text: original, codingnsTranslation: record },
1287
+ { type: 'block-end', index: block.index, block: { type: block.kind, text: original, codingnsTranslation: record }, codingnsTranslation: record },
1288
+ ];
1289
+ };
1290
+ try {
1291
+ for await (const chunk of stream) {
1292
+ const record = asRecord(chunk);
1293
+ const type = typeof record?.type === 'string' ? record.type : '';
1294
+ const index = typeof record?.index === 'number' ? record.index : undefined;
1295
+ if (type === 'text-delta' || type === 'reasoning-delta') {
1296
+ if (index === undefined || typeof record?.text !== 'string') {
1297
+ yield chunk;
1298
+ continue;
1299
+ }
1300
+ const kind = type === 'text-delta' ? 'text' : 'reasoning';
1301
+ const block = bufferOf(index, kind);
1302
+ // Decide once per block: changing settings must not translate only a suffix
1303
+ // or drop a prefix that was already buffered.
1304
+ if (block === undefined || !block.translate) {
1305
+ yield chunk;
1306
+ continue;
1307
+ }
1308
+ block.text += record.text;
1309
+ continue;
1310
+ }
1311
+ if (type === 'block-end' && index !== undefined) {
1312
+ const block = blocks.get(index);
1313
+ if (block !== undefined && !block.closed) {
1314
+ block.closed = true;
1315
+ if (block.text.trim() !== '') {
1316
+ const emitted = await translateBlock(block);
1317
+ for (const item of emitted)
1318
+ yield item;
1319
+ if (emitted.length > 0)
1320
+ continue;
1321
+ yield { type: `${block.kind}-delta`, index: block.index, text: block.text };
1322
+ }
1323
+ }
1324
+ yield chunk;
1325
+ continue;
1326
+ }
1327
+ yield chunk;
1328
+ }
1329
+ }
1330
+ catch (error) {
1331
+ // 流异常时先把缓存正文按原文补发,再把原始错误交给 DSH,保证回复不丢失。
1332
+ for (const item of flushBuffered())
1333
+ yield item;
1334
+ throw error;
1335
+ }
1336
+ for (const item of flushBuffered())
1337
+ yield item;
1338
+ /**
1339
+ * Provider 未发送 block-end(中断或异常)时,把缓存正文按原文补发。
1340
+ *
1341
+ * 不能用 `finally` 里的 `yield`:异常正在传播时生成器已经结束,后续 yield 会被
1342
+ * 直接丢弃,用户会看不到已经生成的半截回复。
1343
+ */
1344
+ function* flushBuffered() {
1345
+ for (const block of [...blocks.values()]) {
1346
+ if (block.closed || block.text.trim() === '')
1347
+ continue;
1348
+ block.closed = true;
1349
+ yield { type: `${block.kind}-delta`, index: block.index, text: block.text };
1350
+ yield { type: 'block-end', index: block.index, block: { type: block.kind, text: block.text } };
1351
+ }
1352
+ }
1353
+ }
1070
1354
  function extractTurnInput(messages, dshContext) {
1071
1355
  for (let index = messages.length - 1; index >= 0; index -= 1) {
1072
1356
  const message = messages[index];
@@ -1125,7 +1409,7 @@ function readInputTranslation(value) {
1125
1409
  return undefined;
1126
1410
  if (candidate.original.trim() === '' || candidate.translated.trim() === '')
1127
1411
  return undefined;
1128
- const targetLanguage = candidate.targetLanguage === 'fr' || candidate.targetLanguage === 'de' || candidate.targetLanguage === 'ja'
1412
+ const targetLanguage = candidate.targetLanguage === 'fr' || candidate.targetLanguage === 'de' || candidate.targetLanguage === 'ja' || candidate.targetLanguage === 'zh'
1129
1413
  ? candidate.targetLanguage
1130
1414
  : 'en';
1131
1415
  const sourceLanguage = candidate.sourceLanguage === 'zh' || candidate.sourceLanguage === 'en' || candidate.sourceLanguage === 'fr' || candidate.sourceLanguage === 'de' || candidate.sourceLanguage === 'ja'
@@ -1175,6 +1459,19 @@ function replaceInputTranslationContent(content, text) {
1175
1459
  });
1176
1460
  return replaced ? blocks : [{ type: 'text', text }, ...content];
1177
1461
  }
1462
+ /** Resolved files already have localized path notices. Leaving their file blocks
1463
+ * would make DSH append another, English-only file notice at adapter dispatch.
1464
+ * Preserve images and unresolved files so DSH retains modality/error handling.
1465
+ */
1466
+ function nativeTranslationContent(content, dshContext) {
1467
+ if (!Array.isArray(content))
1468
+ return content;
1469
+ return content.filter(part => {
1470
+ if (!isRecord(part) || part.type !== 'file')
1471
+ return true;
1472
+ return resolveDshAttachmentPath(dshContext, 'file', isRecord(part.attachment) ? part.attachment : part) === undefined;
1473
+ });
1474
+ }
1178
1475
  /** DSH 为工具分段注入的继续提示必须成为下一次 Provider 请求的 prompt。 */
1179
1476
  function isInjectedStepNotice(message) {
1180
1477
  if (message.role !== 'user' || !isRecord(message.source))
@@ -1230,11 +1527,13 @@ function extractMessageInput(content, dshContext) {
1230
1527
  const mimeType = typeof reference.mediaType === 'string' && reference.mediaType.trim() !== '' ? reference.mediaType.trim() : undefined;
1231
1528
  attachments.push({ kind, path, ...(name === undefined ? {} : { name }), ...(mimeType === undefined ? {} : { mimeType }) });
1232
1529
  }
1233
- const fileNotes = attachments.filter((attachment) => attachment.kind === 'file').map((attachment) => `附件文件${attachment.name === undefined ? '' : `「${attachment.name}」`}位于:${attachment.path}\n请使用工具读取该文件的内容。`);
1234
1530
  const translationPrompt = text.join('\n\n').trim();
1235
- const untranslatedPrompt = fileNotes.join('\n\n').trim();
1531
+ const untranslatedPrompt = attachmentNotices(attachments);
1236
1532
  return { prompt: [translationPrompt, untranslatedPrompt].filter(value => value !== '').join('\n\n'), translationPrompt, untranslatedPrompt, attachments };
1237
1533
  }
1534
+ function attachmentNotices(attachments, language) {
1535
+ return attachments.filter(attachment => attachment.kind === 'file').map(attachment => attachmentFileNotice(attachment, language)).join('\n\n');
1536
+ }
1238
1537
  function resolveDshAttachmentPath(dshContext, kind, reference) {
1239
1538
  if (dshContext === undefined || typeof reference.attachmentId !== 'string')
1240
1539
  return undefined;