@jingyi0605/codingns4dsh 0.2.0-rc.2 → 0.2.0-rc.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (353) hide show
  1. package/README.en.md +4 -4
  2. package/README.md +15 -6
  3. package/assets/provider-icons/antigravity.svg +39 -0
  4. package/assets/provider-icons/codebuddy.svg +37 -0
  5. package/assets/provider-icons/cursor-cli.svg +1 -0
  6. package/assets/provider-icons/kiro-cli.svg +11 -0
  7. package/assets/provider-icons/qoder-cn.svg +5 -0
  8. package/assets/provider-icons/qoder.svg +5 -0
  9. package/assets/provider-icons/workbuddy.svg +29 -0
  10. package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.d.ts.map +1 -1
  11. package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js +78 -1
  12. package/data/build/dist/bootstrap/dsh-peer-host-preboot-shim.js.map +1 -1
  13. package/data/build/dist/client/bundle.js +3864 -507
  14. package/data/build/dist/client/bundle.js.map +1 -1
  15. package/data/build/dist/client/cli-catalog.d.ts +1 -1
  16. package/data/build/dist/client/cli-catalog.d.ts.map +1 -1
  17. package/data/build/dist/client/cli-catalog.js +11 -5
  18. package/data/build/dist/client/cli-catalog.js.map +1 -1
  19. package/data/build/dist/client/cli-slots.d.ts.map +1 -1
  20. package/data/build/dist/client/cli-slots.js +111 -13
  21. package/data/build/dist/client/cli-slots.js.map +1 -1
  22. package/data/build/dist/client/debug/ui.d.ts.map +1 -1
  23. package/data/build/dist/client/debug/ui.js +2 -1
  24. package/data/build/dist/client/debug/ui.js.map +1 -1
  25. package/data/build/dist/client/delegate-command.d.ts +1 -0
  26. package/data/build/dist/client/delegate-command.d.ts.map +1 -1
  27. package/data/build/dist/client/delegate-command.js +486 -27
  28. package/data/build/dist/client/delegate-command.js.map +1 -1
  29. package/data/build/dist/client/delegate-plan.d.ts +13 -4
  30. package/data/build/dist/client/delegate-plan.d.ts.map +1 -1
  31. package/data/build/dist/client/delegate-plan.js +10 -6
  32. package/data/build/dist/client/delegate-plan.js.map +1 -1
  33. package/data/build/dist/client/delegate-ui-dom.d.ts +46 -0
  34. package/data/build/dist/client/delegate-ui-dom.d.ts.map +1 -1
  35. package/data/build/dist/client/delegate-ui-dom.js +291 -7
  36. package/data/build/dist/client/delegate-ui-dom.js.map +1 -1
  37. package/data/build/dist/client/features/cli-adapters.d.ts.map +1 -1
  38. package/data/build/dist/client/features/cli-adapters.js +117 -8
  39. package/data/build/dist/client/features/cli-adapters.js.map +1 -1
  40. package/data/build/dist/client/features/mobile-access-panel.d.ts.map +1 -1
  41. package/data/build/dist/client/features/mobile-access-panel.js +8 -7
  42. package/data/build/dist/client/features/mobile-access-panel.js.map +1 -1
  43. package/data/build/dist/client/features/peer-host.d.ts +6 -4
  44. package/data/build/dist/client/features/peer-host.d.ts.map +1 -1
  45. package/data/build/dist/client/features/peer-host.js +346 -18
  46. package/data/build/dist/client/features/peer-host.js.map +1 -1
  47. package/data/build/dist/client/git-history-graph.d.ts +2 -2
  48. package/data/build/dist/client/git-history-graph.d.ts.map +1 -1
  49. package/data/build/dist/client/git-history-graph.js +2 -2
  50. package/data/build/dist/client/git-history-graph.js.map +1 -1
  51. package/data/build/dist/client/git-management.d.ts.map +1 -1
  52. package/data/build/dist/client/git-management.js +21 -1
  53. package/data/build/dist/client/git-management.js.map +1 -1
  54. package/data/build/dist/client/locale.d.ts.map +1 -1
  55. package/data/build/dist/client/locale.js +60 -6
  56. package/data/build/dist/client/locale.js.map +1 -1
  57. package/data/build/dist/client/locales/terminalAuth.d.ts.map +1 -1
  58. package/data/build/dist/client/locales/terminalAuth.js +4 -0
  59. package/data/build/dist/client/locales/terminalAuth.js.map +1 -1
  60. package/data/build/dist/client/locales/usage.d.ts.map +1 -1
  61. package/data/build/dist/client/locales/usage.js +20 -0
  62. package/data/build/dist/client/locales/usage.js.map +1 -1
  63. package/data/build/dist/client/mobile-session-interaction.d.ts.map +1 -1
  64. package/data/build/dist/client/mobile-session-interaction.js +24 -7
  65. package/data/build/dist/client/mobile-session-interaction.js.map +1 -1
  66. package/data/build/dist/client/mobile-sidebar-gestures.d.ts +55 -7
  67. package/data/build/dist/client/mobile-sidebar-gestures.d.ts.map +1 -1
  68. package/data/build/dist/client/mobile-sidebar-gestures.js +339 -40
  69. package/data/build/dist/client/mobile-sidebar-gestures.js.map +1 -1
  70. package/data/build/dist/client/model-catalog-cache.d.ts +5 -0
  71. package/data/build/dist/client/model-catalog-cache.d.ts.map +1 -0
  72. package/data/build/dist/client/model-catalog-cache.js +58 -0
  73. package/data/build/dist/client/model-catalog-cache.js.map +1 -0
  74. package/data/build/dist/client/peer-host-aggregate-refresh.d.ts +7 -0
  75. package/data/build/dist/client/peer-host-aggregate-refresh.d.ts.map +1 -1
  76. package/data/build/dist/client/peer-host-aggregate-refresh.js +13 -1
  77. package/data/build/dist/client/peer-host-aggregate-refresh.js.map +1 -1
  78. package/data/build/dist/client/peer-host-native-projection.d.ts +16 -1
  79. package/data/build/dist/client/peer-host-native-projection.d.ts.map +1 -1
  80. package/data/build/dist/client/peer-host-native-projection.js +49 -8
  81. package/data/build/dist/client/peer-host-native-projection.js.map +1 -1
  82. package/data/build/dist/client/peer-host-native-store-projection.d.ts +6 -2
  83. package/data/build/dist/client/peer-host-native-store-projection.d.ts.map +1 -1
  84. package/data/build/dist/client/peer-host-native-store-projection.js +308 -10
  85. package/data/build/dist/client/peer-host-native-store-projection.js.map +1 -1
  86. package/data/build/dist/client/provider-icon-assets.js +14 -0
  87. package/data/build/dist/client/provider-icon-assets.js.map +1 -1
  88. package/data/build/dist/client/provider-icons.d.ts.map +1 -1
  89. package/data/build/dist/client/provider-icons.js +15 -3
  90. package/data/build/dist/client/provider-icons.js.map +1 -1
  91. package/data/build/dist/client/remote-web-context.js +10 -6
  92. package/data/build/dist/client/remote-web-context.js.map +1 -1
  93. package/data/build/dist/client/service-tier.d.ts +54 -0
  94. package/data/build/dist/client/service-tier.d.ts.map +1 -0
  95. package/data/build/dist/client/service-tier.js +78 -0
  96. package/data/build/dist/client/service-tier.js.map +1 -0
  97. package/data/build/dist/client/settings-promo-styles.d.ts +28 -0
  98. package/data/build/dist/client/settings-promo-styles.d.ts.map +1 -0
  99. package/data/build/dist/client/settings-promo-styles.js +58 -0
  100. package/data/build/dist/client/settings-promo-styles.js.map +1 -0
  101. package/data/build/dist/client/settings-promo.d.ts +14 -0
  102. package/data/build/dist/client/settings-promo.d.ts.map +1 -0
  103. package/data/build/dist/client/settings-promo.js +68 -0
  104. package/data/build/dist/client/settings-promo.js.map +1 -0
  105. package/data/build/dist/client/settings-section.d.ts +2 -1
  106. package/data/build/dist/client/settings-section.d.ts.map +1 -1
  107. package/data/build/dist/client/settings-section.js +7 -4
  108. package/data/build/dist/client/settings-section.js.map +1 -1
  109. package/data/build/dist/client/subscription-slot.d.ts.map +1 -1
  110. package/data/build/dist/client/subscription-slot.js +181 -16
  111. package/data/build/dist/client/subscription-slot.js.map +1 -1
  112. package/data/build/dist/client/terminal/model.d.ts +82 -3
  113. package/data/build/dist/client/terminal/model.d.ts.map +1 -1
  114. package/data/build/dist/client/terminal/model.js +368 -29
  115. package/data/build/dist/client/terminal/model.js.map +1 -1
  116. package/data/build/dist/client/terminal/recovery.d.ts +16 -1
  117. package/data/build/dist/client/terminal/recovery.d.ts.map +1 -1
  118. package/data/build/dist/client/terminal/recovery.js +101 -39
  119. package/data/build/dist/client/terminal/recovery.js.map +1 -1
  120. package/data/build/dist/client/terminal/styles.d.ts +10 -0
  121. package/data/build/dist/client/terminal/styles.d.ts.map +1 -1
  122. package/data/build/dist/client/terminal/styles.js +27 -3
  123. package/data/build/dist/client/terminal/styles.js.map +1 -1
  124. package/data/build/dist/client/terminal/ui.d.ts +3 -2
  125. package/data/build/dist/client/terminal/ui.d.ts.map +1 -1
  126. package/data/build/dist/client/terminal/ui.js +215 -104
  127. package/data/build/dist/client/terminal/ui.js.map +1 -1
  128. package/data/build/dist/client/terminal/xterm-view.d.ts +5 -1
  129. package/data/build/dist/client/terminal/xterm-view.d.ts.map +1 -1
  130. package/data/build/dist/client/terminal/xterm-view.js +378 -20
  131. package/data/build/dist/client/terminal/xterm-view.js.map +1 -1
  132. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts +58 -0
  133. package/data/build/dist/host/cli-adapters/acp-cli-driver.d.ts.map +1 -0
  134. package/data/build/dist/host/cli-adapters/acp-cli-driver.js +271 -0
  135. package/data/build/dist/host/cli-adapters/acp-cli-driver.js.map +1 -0
  136. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts +30 -0
  137. package/data/build/dist/host/cli-adapters/antigravity-driver.d.ts.map +1 -0
  138. package/data/build/dist/host/cli-adapters/antigravity-driver.js +210 -0
  139. package/data/build/dist/host/cli-adapters/antigravity-driver.js.map +1 -0
  140. package/data/build/dist/host/cli-adapters/claude-driver.js +1 -1
  141. package/data/build/dist/host/cli-adapters/claude-driver.js.map +1 -1
  142. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts +139 -0
  143. package/data/build/dist/host/cli-adapters/codebuddy-driver.d.ts.map +1 -0
  144. package/data/build/dist/host/cli-adapters/codebuddy-driver.js +1721 -0
  145. package/data/build/dist/host/cli-adapters/codebuddy-driver.js.map +1 -0
  146. package/data/build/dist/host/cli-adapters/codebuddy-subscription.d.ts +53 -0
  147. package/data/build/dist/host/cli-adapters/codebuddy-subscription.d.ts.map +1 -0
  148. package/data/build/dist/host/cli-adapters/codebuddy-subscription.js +778 -0
  149. package/data/build/dist/host/cli-adapters/codebuddy-subscription.js.map +1 -0
  150. package/data/build/dist/host/cli-adapters/codex-driver.d.ts +12 -0
  151. package/data/build/dist/host/cli-adapters/codex-driver.d.ts.map +1 -1
  152. package/data/build/dist/host/cli-adapters/codex-driver.js +153 -6
  153. package/data/build/dist/host/cli-adapters/codex-driver.js.map +1 -1
  154. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts +43 -3
  155. package/data/build/dist/host/cli-adapters/command-code-driver.d.ts.map +1 -1
  156. package/data/build/dist/host/cli-adapters/command-code-driver.js +637 -72
  157. package/data/build/dist/host/cli-adapters/command-code-driver.js.map +1 -1
  158. package/data/build/dist/host/cli-adapters/command-code-history.d.ts +147 -0
  159. package/data/build/dist/host/cli-adapters/command-code-history.d.ts.map +1 -0
  160. package/data/build/dist/host/cli-adapters/command-code-history.js +659 -0
  161. package/data/build/dist/host/cli-adapters/command-code-history.js.map +1 -0
  162. package/data/build/dist/host/cli-adapters/cursor-driver.d.ts +12 -0
  163. package/data/build/dist/host/cli-adapters/cursor-driver.d.ts.map +1 -0
  164. package/data/build/dist/host/cli-adapters/cursor-driver.js +20 -0
  165. package/data/build/dist/host/cli-adapters/cursor-driver.js.map +1 -0
  166. package/data/build/dist/host/cli-adapters/custom-upstream-classifier.d.ts +67 -0
  167. package/data/build/dist/host/cli-adapters/custom-upstream-classifier.d.ts.map +1 -0
  168. package/data/build/dist/host/cli-adapters/custom-upstream-classifier.js +169 -0
  169. package/data/build/dist/host/cli-adapters/custom-upstream-classifier.js.map +1 -0
  170. package/data/build/dist/host/cli-adapters/delegate-dispatch.d.ts +2 -1
  171. package/data/build/dist/host/cli-adapters/delegate-dispatch.d.ts.map +1 -1
  172. package/data/build/dist/host/cli-adapters/delegate-dispatch.js +14 -61
  173. package/data/build/dist/host/cli-adapters/delegate-dispatch.js.map +1 -1
  174. package/data/build/dist/host/cli-adapters/delegation-authorization.d.ts +19 -0
  175. package/data/build/dist/host/cli-adapters/delegation-authorization.d.ts.map +1 -0
  176. package/data/build/dist/host/cli-adapters/delegation-authorization.js +49 -0
  177. package/data/build/dist/host/cli-adapters/delegation-authorization.js.map +1 -0
  178. package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.d.ts +34 -0
  179. package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.d.ts.map +1 -0
  180. package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.js +121 -0
  181. package/data/build/dist/host/cli-adapters/delegation-mention-rewrite.js.map +1 -0
  182. package/data/build/dist/host/cli-adapters/driver.d.ts +6 -0
  183. package/data/build/dist/host/cli-adapters/driver.d.ts.map +1 -1
  184. package/data/build/dist/host/cli-adapters/feature.d.ts.map +1 -1
  185. package/data/build/dist/host/cli-adapters/feature.js +269 -18
  186. package/data/build/dist/host/cli-adapters/feature.js.map +1 -1
  187. package/data/build/dist/host/cli-adapters/kiro-driver.d.ts +12 -0
  188. package/data/build/dist/host/cli-adapters/kiro-driver.d.ts.map +1 -0
  189. package/data/build/dist/host/cli-adapters/kiro-driver.js +18 -0
  190. package/data/build/dist/host/cli-adapters/kiro-driver.js.map +1 -0
  191. package/data/build/dist/host/cli-adapters/legacy-session-adapter.d.ts.map +1 -1
  192. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js +11 -0
  193. package/data/build/dist/host/cli-adapters/legacy-session-adapter.js.map +1 -1
  194. package/data/build/dist/host/cli-adapters/model-catalog.d.ts +5 -0
  195. package/data/build/dist/host/cli-adapters/model-catalog.d.ts.map +1 -1
  196. package/data/build/dist/host/cli-adapters/model-catalog.js +130 -0
  197. package/data/build/dist/host/cli-adapters/model-catalog.js.map +1 -1
  198. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.d.ts +33 -3
  199. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.d.ts.map +1 -1
  200. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js +115 -9
  201. package/data/build/dist/host/cli-adapters/native-subagent-dispatch.js.map +1 -1
  202. package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts +14 -2
  203. package/data/build/dist/host/cli-adapters/native-team-subagent.d.ts.map +1 -1
  204. package/data/build/dist/host/cli-adapters/native-team-subagent.js +23 -4
  205. package/data/build/dist/host/cli-adapters/native-team-subagent.js.map +1 -1
  206. package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts +75 -0
  207. package/data/build/dist/host/cli-adapters/new-api-subscription.d.ts.map +1 -0
  208. package/data/build/dist/host/cli-adapters/new-api-subscription.js +959 -0
  209. package/data/build/dist/host/cli-adapters/new-api-subscription.js.map +1 -0
  210. package/data/build/dist/host/cli-adapters/opencode-driver.d.ts.map +1 -1
  211. package/data/build/dist/host/cli-adapters/opencode-driver.js +9 -7
  212. package/data/build/dist/host/cli-adapters/opencode-driver.js.map +1 -1
  213. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts +33 -3
  214. package/data/build/dist/host/cli-adapters/provider-subscription.d.ts.map +1 -1
  215. package/data/build/dist/host/cli-adapters/provider-subscription.js +158 -40
  216. package/data/build/dist/host/cli-adapters/provider-subscription.js.map +1 -1
  217. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts +71 -0
  218. package/data/build/dist/host/cli-adapters/qoder-driver.d.ts.map +1 -0
  219. package/data/build/dist/host/cli-adapters/qoder-driver.js +553 -0
  220. package/data/build/dist/host/cli-adapters/qoder-driver.js.map +1 -0
  221. package/data/build/dist/host/cli-adapters/qoder-subscription.d.ts +31 -0
  222. package/data/build/dist/host/cli-adapters/qoder-subscription.d.ts.map +1 -0
  223. package/data/build/dist/host/cli-adapters/qoder-subscription.js +178 -0
  224. package/data/build/dist/host/cli-adapters/qoder-subscription.js.map +1 -0
  225. package/data/build/dist/host/cli-adapters/registry.d.ts +41 -2
  226. package/data/build/dist/host/cli-adapters/registry.d.ts.map +1 -1
  227. package/data/build/dist/host/cli-adapters/registry.js +187 -18
  228. package/data/build/dist/host/cli-adapters/registry.js.map +1 -1
  229. package/data/build/dist/host/cli-adapters/session-store.d.ts +4 -0
  230. package/data/build/dist/host/cli-adapters/session-store.d.ts.map +1 -1
  231. package/data/build/dist/host/cli-adapters/session-store.js +18 -2
  232. package/data/build/dist/host/cli-adapters/session-store.js.map +1 -1
  233. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts +14 -1
  234. package/data/build/dist/host/cli-adapters/standard-stream-driver.d.ts.map +1 -1
  235. package/data/build/dist/host/cli-adapters/standard-stream-driver.js +121 -23
  236. package/data/build/dist/host/cli-adapters/standard-stream-driver.js.map +1 -1
  237. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts +10 -1
  238. package/data/build/dist/host/cli-adapters/stream-normalizer.d.ts.map +1 -1
  239. package/data/build/dist/host/cli-adapters/stream-normalizer.js +23 -0
  240. package/data/build/dist/host/cli-adapters/stream-normalizer.js.map +1 -1
  241. package/data/build/dist/host/cli-adapters/subagent-tool.d.ts.map +1 -1
  242. package/data/build/dist/host/cli-adapters/subagent-tool.js +53 -7
  243. package/data/build/dist/host/cli-adapters/subagent-tool.js.map +1 -1
  244. package/data/build/dist/host/cli-bridge/bridge-server.d.ts +5 -0
  245. package/data/build/dist/host/cli-bridge/bridge-server.d.ts.map +1 -1
  246. package/data/build/dist/host/cli-bridge/bridge-server.js +7 -2
  247. package/data/build/dist/host/cli-bridge/bridge-server.js.map +1 -1
  248. package/data/build/dist/host/cli-bridge/command-code-mod.d.ts +5 -1
  249. package/data/build/dist/host/cli-bridge/command-code-mod.d.ts.map +1 -1
  250. package/data/build/dist/host/cli-bridge/command-code-mod.js +43 -12
  251. package/data/build/dist/host/cli-bridge/command-code-mod.js.map +1 -1
  252. package/data/build/dist/host/cli-bridge/dispatch.d.ts.map +1 -1
  253. package/data/build/dist/host/cli-bridge/dispatch.js +43 -5
  254. package/data/build/dist/host/cli-bridge/dispatch.js.map +1 -1
  255. package/data/build/dist/host/cli-bridge/mcp-stdio-entry.js +24 -5
  256. package/data/build/dist/host/cli-bridge/mcp-stdio-entry.js.map +1 -1
  257. package/data/build/dist/host/features/peer-host.d.ts +21 -1
  258. package/data/build/dist/host/features/peer-host.d.ts.map +1 -1
  259. package/data/build/dist/host/features/peer-host.js +174 -30
  260. package/data/build/dist/host/features/peer-host.js.map +1 -1
  261. package/data/build/dist/host/features/types.d.ts +2 -0
  262. package/data/build/dist/host/features/types.d.ts.map +1 -1
  263. package/data/build/dist/host/index.d.ts +11 -1
  264. package/data/build/dist/host/index.d.ts.map +1 -1
  265. package/data/build/dist/host/index.js +12 -2
  266. package/data/build/dist/host/index.js.map +1 -1
  267. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js +6 -1
  268. package/data/build/dist/host/modules/peer-host/peer-host-native-protocol.js.map +1 -1
  269. package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.d.ts +2 -0
  270. package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.d.ts.map +1 -1
  271. package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.js +26 -7
  272. package/data/build/dist/host/modules/peer-host/peer-host-virtual-registry.js.map +1 -1
  273. package/data/build/dist/host/native-session-bridge.d.ts.map +1 -1
  274. package/data/build/dist/host/native-session-bridge.js +36 -7
  275. package/data/build/dist/host/native-session-bridge.js.map +1 -1
  276. package/data/build/dist/host/rpc.js +5 -2
  277. package/data/build/dist/host/rpc.js.map +1 -1
  278. package/data/build/dist/host/settings.d.ts.map +1 -1
  279. package/data/build/dist/host/settings.js +12 -5
  280. package/data/build/dist/host/settings.js.map +1 -1
  281. package/data/build/dist/host/terminal/backends/local-pty-backend.d.ts.map +1 -1
  282. package/data/build/dist/host/terminal/backends/local-pty-backend.js +14 -0
  283. package/data/build/dist/host/terminal/backends/local-pty-backend.js.map +1 -1
  284. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts +40 -1
  285. package/data/build/dist/host/terminal/backends/tmux-backend.d.ts.map +1 -1
  286. package/data/build/dist/host/terminal/backends/tmux-backend.js +291 -18
  287. package/data/build/dist/host/terminal/backends/tmux-backend.js.map +1 -1
  288. package/data/build/dist/host/terminal/runtime-adapter.d.ts +32 -0
  289. package/data/build/dist/host/terminal/runtime-adapter.d.ts.map +1 -1
  290. package/data/build/dist/host/terminal/runtime-adapter.js +8 -0
  291. package/data/build/dist/host/terminal/runtime-adapter.js.map +1 -1
  292. package/data/build/dist/host/terminal/runtime-manager.d.ts +3 -0
  293. package/data/build/dist/host/terminal/runtime-manager.d.ts.map +1 -1
  294. package/data/build/dist/host/terminal/runtime-manager.js +92 -4
  295. package/data/build/dist/host/terminal/runtime-manager.js.map +1 -1
  296. package/data/build/dist/host/terminal/terminal-controller.d.ts.map +1 -1
  297. package/data/build/dist/host/terminal/terminal-controller.js +58 -6
  298. package/data/build/dist/host/terminal/terminal-controller.js.map +1 -1
  299. package/data/build/dist/host/terminal/terminal-process-service.d.ts.map +1 -1
  300. package/data/build/dist/host/terminal/terminal-process-service.js +12 -2
  301. package/data/build/dist/host/terminal/terminal-process-service.js.map +1 -1
  302. package/data/build/dist/host/terminal/terminal-service.d.ts +41 -4
  303. package/data/build/dist/host/terminal/terminal-service.d.ts.map +1 -1
  304. package/data/build/dist/host/terminal/terminal-service.js +551 -61
  305. package/data/build/dist/host/terminal/terminal-service.js.map +1 -1
  306. package/data/build/dist/index.d.ts +2 -2
  307. package/data/build/dist/index.d.ts.map +1 -1
  308. package/data/build/dist/index.js +2 -2
  309. package/data/build/dist/index.js.map +1 -1
  310. package/data/build/dist/shared/contracts/cli-adapter.d.ts +64 -2
  311. package/data/build/dist/shared/contracts/cli-adapter.d.ts.map +1 -1
  312. package/data/build/dist/shared/contracts/config.d.ts +54 -11
  313. package/data/build/dist/shared/contracts/config.d.ts.map +1 -1
  314. package/data/build/dist/shared/contracts/config.js +52 -8
  315. package/data/build/dist/shared/contracts/config.js.map +1 -1
  316. package/data/build/dist/shared/contracts/subscription.d.ts +5 -1
  317. package/data/build/dist/shared/contracts/subscription.d.ts.map +1 -1
  318. package/data/build/dist/shared/contracts/subscription.js.map +1 -1
  319. package/data/build/dist/shared/contracts/terminal.d.ts +8 -1
  320. package/data/build/dist/shared/contracts/terminal.d.ts.map +1 -1
  321. package/data/build/dist/shared/contracts/version.d.ts +1 -1
  322. package/data/build/dist/shared/contracts/version.js +1 -1
  323. package/data/build/dist/shared/debug.d.ts +8 -0
  324. package/data/build/dist/shared/debug.d.ts.map +1 -1
  325. package/data/build/dist/shared/debug.js +43 -7
  326. package/data/build/dist/shared/debug.js.map +1 -1
  327. package/data/build/dist/shared/delegation-carrier.d.ts +24 -0
  328. package/data/build/dist/shared/delegation-carrier.d.ts.map +1 -0
  329. package/data/build/dist/shared/delegation-carrier.js +111 -0
  330. package/data/build/dist/shared/delegation-carrier.js.map +1 -0
  331. package/data/build/dist/shared/index.d.ts +2 -2
  332. package/data/build/dist/shared/index.d.ts.map +1 -1
  333. package/data/build/dist/shared/index.js +2 -2
  334. package/data/build/dist/shared/index.js.map +1 -1
  335. package/data/build/dist/shared/terminal-input.d.ts +8 -0
  336. package/data/build/dist/shared/terminal-input.d.ts.map +1 -0
  337. package/data/build/dist/shared/terminal-input.js +10 -0
  338. package/data/build/dist/shared/terminal-input.js.map +1 -0
  339. package/data/build/dist/transport/debug.d.ts +7 -0
  340. package/data/build/dist/transport/debug.d.ts.map +1 -1
  341. package/data/build/dist/transport/debug.js +33 -5
  342. package/data/build/dist/transport/debug.js.map +1 -1
  343. package/data/build/dist/transport/index.d.ts +1 -1
  344. package/data/build/dist/transport/index.d.ts.map +1 -1
  345. package/data/build/dist/transport/index.js +1 -1
  346. package/data/build/dist/transport/index.js.map +1 -1
  347. package/data/build/dist/typert.host.js +1 -1
  348. package/data/build/dist/typert.host.js.map +1 -1
  349. package/package.json +1 -1
  350. package/profile/README.md +2 -2
  351. package/profile/package.json +2 -2
  352. package/profile/version.json +1 -1
  353. package/version.json +1 -1
@@ -1,6 +1,7 @@
1
1
  import { Service } from '@deepseek-ai/cordis';
2
2
  import { debugInfo, debugWarn } from '../../shared/debug.js';
3
3
  import { resolveCodingNsTranslator } from '../locale.js';
4
+ import { stripTerminalDeviceAttributeResponses } from '../../shared/terminal-input.js';
4
5
  class ObservableValue {
5
6
  value;
6
7
  listeners = new Set();
@@ -18,7 +19,7 @@ class ObservableValue {
18
19
  listener();
19
20
  }
20
21
  }
21
- /** 一个 Sidebar 标签对应的终端模型;DOM 卸载只 detach,显式 close 才结束 Host 进程。 */
22
+ /** 一个 Sidebar 标签对应的终端模型;普通视图卸载只 detach,聚合视图可常驻 follow。 */
22
23
  export class CodingNsTerminalView {
23
24
  sessionId;
24
25
  remote;
@@ -39,9 +40,21 @@ export class CodingNsTerminalView {
39
40
  attachmentId;
40
41
  /** 最近一次已排队的尺寸;ResizeObserver 可能在同一布局周期内重复触发。 */
41
42
  lastResize;
43
+ reconnectTimer;
44
+ reconnectAttempts = 0;
45
+ /**
46
+ * 聚合终端由工作区库存拥有生命周期。
47
+ *
48
+ * 聚合页在切换右栏页签时可能会卸载 DOM,但 Host 里的终端仍然运行;
49
+ * 这类视图不能因为一次 DOM 卸载就释放 follow,否则再次点开标签必然
50
+ * 重新经历 environment/list/follow。显式 close 和 dispose 仍会强制释放。
51
+ */
52
+ keepAlive;
53
+ initialState;
54
+ initialStateConsumed = false;
42
55
  constructor(sessionId, id, remote, createWhenMissing, shellPath, onWorkspaceResolved,
43
56
  // 追加在末尾:既有调用方与测试按位置传 shellPath / onWorkspaceResolved,不能前移。
44
- t = resolveCodingNsTranslator()) {
57
+ t = resolveCodingNsTranslator(), keepAlive = false, initialState) {
45
58
  this.sessionId = sessionId;
46
59
  this.remote = remote;
47
60
  this.createWhenMissing = createWhenMissing;
@@ -49,21 +62,43 @@ export class CodingNsTerminalView {
49
62
  this.onWorkspaceResolved = onWorkspaceResolved;
50
63
  this.t = t;
51
64
  this.id = id;
65
+ this.keepAlive = keepAlive;
66
+ this.initialState = initialState;
52
67
  // 标签兜底标题必须走词典:字段初始化阶段还取不到构造参数,因此在构造函数里建 store。
53
- this.store = new ObservableValue({ phase: 'idle', title: t('terminal.title'), writable: false });
68
+ this.store = new ObservableValue({
69
+ phase: initialState?.info === undefined ? 'idle' : 'disconnected',
70
+ ...(initialState?.environment === undefined ? {} : { environment: initialState.environment }),
71
+ ...(initialState?.info === undefined ? {} : { info: initialState.info }),
72
+ title: initialState?.info?.title ?? t('terminal.title'),
73
+ writable: false,
74
+ });
54
75
  this.state = this.store;
55
76
  }
56
77
  mount() {
57
78
  this.mounted += 1;
58
- if (this.mounted === 1)
59
- void this.refresh();
79
+ if (this.mounted === 1) {
80
+ const initial = this.initialState;
81
+ if (!this.initialStateConsumed && initial?.info !== undefined) {
82
+ this.initialStateConsumed = true;
83
+ // 库存快照已经证明终端仍在运行时,直接订阅 Host 常驻连接,
84
+ // 不再为切换会话重新读取 environment/list。环境快照尚未到达时也
85
+ // 可以 follow:environment 只影响输入上限和外观默认值,不能阻塞恢复。
86
+ if (initial.info.state === 'running')
87
+ this.connect();
88
+ else
89
+ void this.refresh();
90
+ }
91
+ else {
92
+ void this.refresh();
93
+ }
94
+ }
60
95
  let active = true;
61
96
  return () => {
62
97
  if (!active)
63
98
  return;
64
99
  active = false;
65
100
  this.mounted = Math.max(0, this.mounted - 1);
66
- if (this.mounted === 0)
101
+ if (this.mounted === 0 && !this.keepAlive)
67
102
  this.detach();
68
103
  };
69
104
  }
@@ -75,6 +110,44 @@ export class CodingNsTerminalView {
75
110
  this.loading = this.load().finally(() => { this.loading = undefined; });
76
111
  return this.loading;
77
112
  }
113
+ /**
114
+ * 运行时丢失后按当前绑定重建终端。
115
+ *
116
+ * 这是"进程已经不存在"时的显式恢复动作:沿用同一个 terminalId 与工作区绑定,
117
+ * 让 Sidebar 标签、标题和尺寸都保持不变,而不是让用户去点"新建终端"。
118
+ */
119
+ async rebuild() {
120
+ if (this.loading !== undefined)
121
+ return this.loading;
122
+ this.clearReconnect();
123
+ this.reconnectAttempts = 0;
124
+ this.patch({ phase: 'loading', writable: false });
125
+ const task = this.loadWithRebuild().finally(() => { this.loading = undefined; });
126
+ this.loading = task;
127
+ return task;
128
+ }
129
+ async loadWithRebuild() {
130
+ try {
131
+ const remote = resolveRemote(this.remote);
132
+ const environment = unwrap(await remote.environment(this.sessionId, this.lifetime.signal));
133
+ this.patch({ phase: 'creating', environment });
134
+ const info = unwrap(await remote.create(this.sessionId, {
135
+ id: this.id,
136
+ ...(this.shellPath === undefined ? {} : { shellPath: this.shellPath }),
137
+ cols: Math.min(80, environment.maxCols),
138
+ rows: Math.min(24, environment.maxRows),
139
+ }, this.lifetime.signal));
140
+ debugInfo('codingns4dsh: client terminal rebuilt', { sessionId: this.sessionId, terminalId: info.id });
141
+ const { error: _error, ...rest } = this.store.getSnapshot();
142
+ this.store.set({ ...rest, environment, info, title: info.title });
143
+ if (this.mounted > 0)
144
+ this.connect();
145
+ }
146
+ catch (error) {
147
+ if (!this.lifetime.signal.aborted)
148
+ this.fail(error);
149
+ }
150
+ }
78
151
  acknowledge(revision) {
79
152
  if (this.pendingRender?.revision !== revision)
80
153
  return;
@@ -82,6 +155,9 @@ export class CodingNsTerminalView {
82
155
  this.pendingRender = undefined;
83
156
  }
84
157
  write(data) {
158
+ data = stripTerminalDeviceAttributeResponses(data);
159
+ if (data === '')
160
+ return;
85
161
  const state = this.store.getSnapshot();
86
162
  const attachmentId = this.attachmentId;
87
163
  if (!state.writable || attachmentId === undefined || data === '')
@@ -156,7 +232,10 @@ export class CodingNsTerminalView {
156
232
  if (binding.id !== this.id)
157
233
  this.id = binding.id;
158
234
  // 工作区已有绑定时,恢复必须失败闭合,不能用新 ID 偷建替代终端。
159
- createWhenMissing = !binding.existing;
235
+ // 显式传入 terminalId 或读取到会话绑定时同样不能新建:工作区绑定
236
+ // 可能刚被另一个会话的关闭动作删除,此时旧标签仍在卸载途中,不能
237
+ // 把已经关闭的 ID 当成新终端重新 create。
238
+ createWhenMissing = createWhenMissing && !binding.existing;
160
239
  }
161
240
  }
162
241
  const listed = unwrap(await remote.list(this.sessionId));
@@ -186,6 +265,7 @@ export class CodingNsTerminalView {
186
265
  connect() {
187
266
  if (this.followController !== undefined || this.lifetime.signal.aborted || this.mounted === 0)
188
267
  return;
268
+ this.clearReconnect();
189
269
  const controller = new AbortController();
190
270
  this.followController = controller;
191
271
  this.attachmentId = crypto.randomUUID();
@@ -207,12 +287,22 @@ export class CodingNsTerminalView {
207
287
  }
208
288
  await this.deliver(frame);
209
289
  }
210
- if (!signal.aborted)
211
- this.patch({ phase: 'disconnected', writable: false });
290
+ if (!signal.aborted) {
291
+ // 流正常结束但终端仍在运行:这是连接层断开(Host 重连、服务器短暂不可达),
292
+ // 必须自动重连;只有真正结束的终端才停在终态。
293
+ if (this.isRunning())
294
+ this.scheduleReconnect();
295
+ else
296
+ this.patch({ phase: 'disconnected', writable: false });
297
+ }
212
298
  }
213
299
  catch (error) {
214
- if (!signal.aborted)
215
- this.fail(error);
300
+ if (!signal.aborted) {
301
+ if (this.isRunning())
302
+ this.scheduleReconnect();
303
+ else
304
+ this.fail(error);
305
+ }
216
306
  }
217
307
  finally {
218
308
  if (this.followController?.signal === signal) {
@@ -222,6 +312,9 @@ export class CodingNsTerminalView {
222
312
  }
223
313
  }
224
314
  async deliver(frame) {
315
+ // 真正收到画面才算连接成功,退避计数在这里归零。
316
+ this.reconnectAttempts = 0;
317
+ this.clearReconnect();
225
318
  this.pendingRender?.resolve();
226
319
  this.revision += 1;
227
320
  let resolveWaiting = () => { };
@@ -231,6 +324,7 @@ export class CodingNsTerminalView {
231
324
  await waiting;
232
325
  }
233
326
  detach() {
327
+ this.clearReconnect();
234
328
  this.pendingRender?.resolve();
235
329
  this.pendingRender = undefined;
236
330
  this.followController?.abort(new Error('终端视图已 detach'));
@@ -242,6 +336,54 @@ export class CodingNsTerminalView {
242
336
  this.store.set({ ...state, phase: 'disconnected', writable: false });
243
337
  }
244
338
  }
339
+ /** 终端是否仍被认为在运行;终态(exited/failed/closed)不参与自动重连。 */
340
+ isRunning() {
341
+ const state = this.store.getSnapshot();
342
+ if (state.phase === 'closed' || state.phase === 'failed')
343
+ return false;
344
+ if (state.info === undefined)
345
+ return true;
346
+ return state.info.state === 'running';
347
+ }
348
+ /**
349
+ * 恢复阶段 Host 列表为空时,判断当前标签是否仍有本地生命周期保护。
350
+ * 新建终端在 create 完成前不能被恢复流程关闭;已连接且 Host 报告 running
351
+ * 的终端也不能因为一次空列表响应被误判为残留。
352
+ */
353
+ isRecoveryProtected() {
354
+ const state = this.store.getSnapshot();
355
+ if (state.phase === 'closed' || state.phase === 'failed')
356
+ return false;
357
+ if (this.createWhenMissing && (state.phase === 'idle' || state.phase === 'loading' || state.phase === 'creating'))
358
+ return true;
359
+ return state.info?.id === this.id && state.info.state === 'running';
360
+ }
361
+ /**
362
+ * 连接层断开后的自动重连。
363
+ *
364
+ * 指数退避到 5 秒封顶:DSH 重启、中继抖动或 tmux 服务器短暂不可达时,用户
365
+ * 不需要手动点"重新连接",也不会看到刷屏式的重试。
366
+ */
367
+ scheduleReconnect() {
368
+ if (this.reconnectTimer !== undefined || this.lifetime.signal.aborted || this.mounted === 0)
369
+ return;
370
+ const delay = Math.min(500 * 2 ** this.reconnectAttempts, 5000);
371
+ this.reconnectAttempts += 1;
372
+ this.patch({ phase: 'disconnected', writable: false });
373
+ debugInfo('codingns4dsh: client terminal reconnect scheduled', { sessionId: this.sessionId, terminalId: this.id, delay, attempt: this.reconnectAttempts });
374
+ this.reconnectTimer = setTimeout(() => {
375
+ this.reconnectTimer = undefined;
376
+ if (this.lifetime.signal.aborted || this.mounted === 0)
377
+ return;
378
+ void this.refresh();
379
+ }, delay);
380
+ }
381
+ clearReconnect() {
382
+ if (this.reconnectTimer !== undefined) {
383
+ clearTimeout(this.reconnectTimer);
384
+ this.reconnectTimer = undefined;
385
+ }
386
+ }
245
387
  patch(patch) {
246
388
  this.store.set({ ...this.store.getSnapshot(), ...patch });
247
389
  }
@@ -263,6 +405,16 @@ export class CodingNsWebTerminals extends Service {
263
405
  closeFailures = this.closeFailureStore;
264
406
  remoteReadyStore = new ObservableValue(false);
265
407
  remoteReadyState = this.remoteReadyStore;
408
+ inventoryRevisionStore = new ObservableValue(0);
409
+ /** 任一工作区库存变化都会递增;聚合页用它触发跨会话刷新。 */
410
+ inventoryRevision = this.inventoryRevisionStore;
411
+ inventories = new Map();
412
+ /** 已解析过的会话环境;切换回已有会话时无需再次请求 Host environment。 */
413
+ environments = new Map();
414
+ /** 每个会话最近一次库存请求的序号;旧请求返回时不得覆盖新建结果。 */
415
+ inventoryRequestIds = new Map();
416
+ /** 任意工作区刷新都会递增;尚未解析工作区的旧请求也必须随之失效。 */
417
+ inventoryEpoch = 0;
266
418
  closeRequests = new Map();
267
419
  /** Remote 注入前不能执行关闭请求;就绪后统一冲刷,避免把启动竞态显示成永久错误。 */
268
420
  cleanupQueued = false;
@@ -274,28 +426,66 @@ export class CodingNsWebTerminals extends Service {
274
426
  this.closeRequests.set(String(request.id), request);
275
427
  void this.flushCleanup();
276
428
  }
277
- view(sessionId, key, contentId, terminalId, shellPath) {
429
+ view(sessionId, key, contentId, terminalId, shellPath, createFresh = false, persistBinding = true, keepAlive = false, initialState) {
278
430
  const mapKey = JSON.stringify([sessionId, key]);
279
431
  const existing = this.views.get(mapKey);
280
432
  if (existing !== undefined)
281
433
  return existing.view;
282
434
  const workspaceId = this.workspaceIds.get(sessionId);
283
- const saved = terminalId
284
- ?? (workspaceId === undefined ? undefined : readWorkspaceBindingWithMigration(workspaceId, contentId))
285
- ?? readBinding(sessionId, contentId);
435
+ const sessionBinding = readBinding(sessionId, contentId);
436
+ // “新建”是一次性动作:首次消费标记时忽略工作区单终端绑定,生成新的身份;
437
+ // 标签重载后若已有会话绑定,则恢复刚创建的终端,避免再次创建重复进程。
438
+ const fresh = createFresh && sessionBinding === undefined;
439
+ const saved = fresh
440
+ ? terminalId
441
+ : terminalId
442
+ ?? sessionBinding
443
+ ?? (workspaceId === undefined ? undefined : readWorkspaceBindingWithMigration(workspaceId, contentId));
286
444
  const id = saved ?? crypto.randomUUID();
287
445
  debugInfo('codingns4dsh: client terminal view', { sessionId, key, contentId, workspaceId: workspaceId ?? null, savedId: saved ?? null, terminalId: id, bindingSource: terminalId !== undefined ? 'argument' : workspaceId !== undefined && readWorkspaceBindingWithMigration(workspaceId, contentId) !== undefined ? 'workspace-storage' : readBinding(sessionId, contentId) !== undefined ? 'session-storage' : 'new' });
288
- if (saved === undefined)
446
+ if (persistBinding && (fresh || saved === undefined))
289
447
  writeBinding(sessionId, contentId, id);
290
- const view = new CodingNsTerminalView(sessionId, id, this.remote, saved === undefined, shellPath, (resolvedWorkspaceId, currentId) => this.rememberWorkspace(sessionId, contentId, currentId, resolvedWorkspaceId), this.t);
448
+ const view = new CodingNsTerminalView(sessionId, id, this.remote, fresh || saved === undefined, shellPath, persistBinding
449
+ ? (resolvedWorkspaceId, currentId) => this.rememberWorkspace(sessionId, contentId, currentId, resolvedWorkspaceId, !fresh && terminalId === undefined)
450
+ : (resolvedWorkspaceId) => { this.workspaceIds.set(sessionId, resolvedWorkspaceId); return undefined; }, this.t, keepAlive, initialState);
291
451
  this.views.set(mapKey, { contentId, view });
292
452
  return view;
293
453
  }
454
+ /** 聚合页按 Host terminalId 获取内部视图,不再依赖 Sidebar 标签身份。 */
455
+ viewForTerminal(sessionId, terminalId, shellPath) {
456
+ const key = `aggregate:${terminalId}`;
457
+ const info = this.inventoryForSession(sessionId).find((item) => item.id === terminalId);
458
+ const environment = this.environmentForSession(sessionId);
459
+ const initialState = info === undefined && environment === undefined
460
+ ? undefined
461
+ : {
462
+ ...(info === undefined ? {} : { info }),
463
+ ...(environment === undefined ? {} : { environment }),
464
+ };
465
+ return this.view(sessionId, key, key, terminalId, shellPath, false, false, true, initialState);
466
+ }
294
467
  /** 返回某个 Sidebar 内容已经保存的 Host 终端身份,用于恢复时去重。 */
295
468
  boundTerminalId(sessionId, contentId) {
296
469
  const workspaceId = this.workspaceIds.get(sessionId);
297
- return (workspaceId === undefined ? undefined : readWorkspaceBindingWithMigration(workspaceId, contentId))
298
- ?? readBinding(sessionId, contentId);
470
+ return readBinding(sessionId, contentId)
471
+ ?? (workspaceId === undefined ? undefined : readWorkspaceBindingWithMigration(workspaceId, contentId));
472
+ }
473
+ /**
474
+ * 判断恢复阶段是否应保留某个标签。
475
+ *
476
+ * Host 列表为空有两种含义:新建终端的 create 还没有完成,或者 Host 已经
477
+ * 关闭终端而 Sidebar 只剩残留标签。只有当前 Client 视图明确处于创建中或
478
+ * 仍收到 Host 的 running 状态时,空列表才属于前一种情况。
479
+ */
480
+ isTerminalRecoveryProtected(sessionId, contentId, terminalId) {
481
+ for (const record of this.views.values()) {
482
+ if (record.view.sessionId !== sessionId || record.contentId !== contentId)
483
+ continue;
484
+ if (record.view.id !== terminalId)
485
+ continue;
486
+ return record.view.isRecoveryProtected();
487
+ }
488
+ return false;
299
489
  }
300
490
  async launchShells(sessionId, signal) {
301
491
  const shells = unwrap(await resolveRemote(this.remote).shells(sessionId, signal));
@@ -306,6 +496,32 @@ export class CodingNsWebTerminals extends Service {
306
496
  };
307
497
  }
308
498
  selectShell(path) { writeString(SHELL_KEY, path); }
499
+ /** 在聚合页内创建一个新的 Host 终端,并把它加入工作区库存。 */
500
+ async createTerminal(sessionId, shellPath) {
501
+ const terminalId = crypto.randomUUID();
502
+ const view = this.view(sessionId, `aggregate:${terminalId}`, `aggregate:${terminalId}`, terminalId, shellPath, true, false, true);
503
+ await view.refresh();
504
+ const info = view.state.getSnapshot().info;
505
+ if (info === undefined)
506
+ throw new Error(view.state.getSnapshot().error ?? '终端创建失败');
507
+ await this.refreshInventory(sessionId);
508
+ return info;
509
+ }
510
+ /** 只关闭指定 terminalId;其他会话中对应的 attach 视图只做 detach。 */
511
+ async closeTerminal(sessionId, terminalId) {
512
+ const records = [...this.views.entries()].filter(([, record]) => record.view.id === terminalId);
513
+ const primary = records.find(([, record]) => record.view.sessionId === sessionId)?.[1];
514
+ if (primary !== undefined)
515
+ await primary.view.close();
516
+ else
517
+ unwrap(await resolveRemote(this.remote).close(sessionId, terminalId));
518
+ for (const [mapKey, record] of records) {
519
+ this.views.delete(mapKey);
520
+ // close() 只结束 Host 终端,dispose() 负责释放聚合视图保留的 follow。
521
+ await record.view.dispose();
522
+ }
523
+ await this.refreshInventory(sessionId);
524
+ }
309
525
  close(sessionId, key, contentId, terminalId) {
310
526
  const mapKey = JSON.stringify([sessionId, key]);
311
527
  const record = this.views.get(mapKey);
@@ -319,7 +535,7 @@ export class CodingNsWebTerminals extends Service {
319
535
  deleteBinding(sessionId, contentId);
320
536
  const workspaceId = this.workspaceIds.get(sessionId);
321
537
  if (workspaceId !== undefined) {
322
- deleteWorkspaceBinding(workspaceId);
538
+ deleteWorkspaceBindingIfMatches(workspaceId, id);
323
539
  deleteLegacyWorkspaceBinding(workspaceId, contentId);
324
540
  }
325
541
  this.views.delete(mapKey);
@@ -329,15 +545,44 @@ export class CodingNsWebTerminals extends Service {
329
545
  const pending = this.recoveries.get(sessionId);
330
546
  if (pending !== undefined)
331
547
  return pending;
548
+ const requestId = (this.inventoryRequestIds.get(sessionId) ?? 0) + 1;
549
+ this.inventoryRequestIds.set(sessionId, requestId);
550
+ const inventoryEpoch = this.inventoryEpoch;
332
551
  // 官方 list wire 只有 sessionId;先用 agent-scoped environment 让 Host 解析工作区。
333
- const recovery = (async () => {
334
- const environment = unwrap(await resolveRemote(this.remote).environment(sessionId));
552
+ // 使用可选变量避免 TS 将闭包中的自引用判定为“赋值前使用”;
553
+ // Promise 创建后才会执行异步主体,因此运行时仍能安全比较身份。
554
+ let recovery;
555
+ recovery = (async () => {
556
+ const environment = await this.resolveEnvironment(sessionId);
335
557
  if (environment.workspaceId !== undefined)
336
558
  this.workspaceIds.set(sessionId, environment.workspaceId);
337
- const result = unwrap(await resolveRemote(this.remote).list(sessionId));
559
+ const result = dedupeInventory(unwrap(await resolveRemote(this.remote).list(sessionId)));
560
+ const currentRequestId = this.inventoryRequestIds.get(sessionId);
561
+ if (currentRequestId !== requestId || this.inventoryEpoch !== inventoryEpoch) {
562
+ // 工作区尚未解析时,刷新方无法提前把这个会话加入失效集合。
563
+ // 旧 list 结果不能交给 Sidebar,否则关闭后的终端会被重新投影出来;
564
+ // 让当前会话重新发起一轮查询,直到拿到刷新后的库存。
565
+ debugInfo('codingns4dsh: client terminal recover stale retry', {
566
+ sessionId,
567
+ terminalIds: result.map((entry) => entry.id),
568
+ requestId,
569
+ currentRequestId,
570
+ inventoryEpoch,
571
+ currentInventoryEpoch: this.inventoryEpoch,
572
+ });
573
+ // 当前请求尚未进入 finally;先移除自身,才能让 retry 创建新一代
574
+ // Promise,而不是再次命中这个仍在执行的旧请求。
575
+ if (recovery !== undefined && this.recoveries.get(sessionId) === recovery)
576
+ this.recoveries.delete(sessionId);
577
+ return this.recover(sessionId);
578
+ }
338
579
  debugInfo('codingns4dsh: client terminal recover', { sessionId, workspaceId: environment.workspaceId, terminalIds: result.map((entry) => entry.id) });
580
+ this.rememberInventory(sessionId, result);
339
581
  return result;
340
- })().finally(() => this.recoveries.delete(sessionId));
582
+ })().finally(() => {
583
+ if (recovery !== undefined && this.recoveries.get(sessionId) === recovery)
584
+ this.recoveries.delete(sessionId);
585
+ });
341
586
  this.recoveries.set(sessionId, recovery);
342
587
  return recovery;
343
588
  }
@@ -357,18 +602,80 @@ export class CodingNsWebTerminals extends Service {
357
602
  this.views.clear();
358
603
  this.workspaceIds.clear();
359
604
  this.recoveries.clear();
605
+ this.inventories.clear();
606
+ this.environments.clear();
607
+ this.inventoryRequestIds.clear();
608
+ this.inventoryEpoch = 0;
609
+ }
610
+ /** 强制刷新当前会话对应工作区的库存。 */
611
+ async refreshInventory(sessionId) {
612
+ this.inventoryEpoch += 1;
613
+ // 聚合页创建/关闭后必须绕过同一工作区所有会话的旧 list 请求;否则
614
+ // 另一个会话的迟到响应仍可能把关闭后的终端写回共享库存。
615
+ const workspaceId = this.workspaceIds.get(sessionId);
616
+ const invalidated = new Set();
617
+ for (const [knownSessionId, knownWorkspaceId] of this.workspaceIds) {
618
+ if (knownSessionId === sessionId || (workspaceId !== undefined && knownWorkspaceId === workspaceId)) {
619
+ invalidated.add(knownSessionId);
620
+ }
621
+ }
622
+ invalidated.add(sessionId);
623
+ for (const knownSessionId of invalidated)
624
+ this.invalidateRecovery(knownSessionId);
625
+ return this.recover(sessionId);
626
+ }
627
+ /** 令指定会话尚未完成的库存请求失效,并清掉可复用的旧 Promise。 */
628
+ invalidateRecovery(sessionId) {
629
+ this.inventoryRequestIds.set(sessionId, (this.inventoryRequestIds.get(sessionId) ?? 0) + 1);
630
+ this.recoveries.delete(sessionId);
631
+ }
632
+ async resolveEnvironment(sessionId) {
633
+ const cached = this.environments.get(`session:${sessionId}`);
634
+ if (cached !== undefined)
635
+ return cached;
636
+ const environment = unwrap(await resolveRemote(this.remote).environment(sessionId));
637
+ this.environments.set(`session:${sessionId}`, environment);
638
+ if (environment.workspaceId !== undefined)
639
+ this.environments.set(`workspace:${environment.workspaceId}`, environment);
640
+ return environment;
641
+ }
642
+ environmentForSession(sessionId) {
643
+ const session = this.environments.get(`session:${sessionId}`);
644
+ if (session !== undefined)
645
+ return session;
646
+ const workspaceId = this.workspaceIds.get(sessionId);
647
+ return workspaceId === undefined ? undefined : this.environments.get(`workspace:${workspaceId}`);
360
648
  }
361
- rememberWorkspace(sessionId, contentId, id, workspaceId) {
649
+ /** 返回当前会话最近一次拿到的工作区库存;首次加载时为空。 */
650
+ inventoryForSession(sessionId) {
651
+ const workspaceId = this.workspaceIds.get(sessionId) ?? `session:${sessionId}`;
652
+ return this.inventories.get(workspaceId)?.terminals ?? [];
653
+ }
654
+ rememberWorkspace(sessionId, contentId, id, workspaceId, useWorkspaceBinding) {
362
655
  this.workspaceIds.set(sessionId, workspaceId);
363
- const existing = readWorkspaceBindingWithMigration(workspaceId, contentId);
656
+ // 显式恢复和“新建”都必须保留自己的 terminalId;只有普通无参数入口
657
+ // 才使用旧的单终端工作区绑定兼容行为。
658
+ const existing = useWorkspaceBinding ? readWorkspaceBindingWithMigration(workspaceId, contentId) : undefined;
364
659
  const resolvedId = existing ?? id;
365
660
  // 修正首次渲染时已经写入的会话键,避免第二个会话继续携带临时 ID。
366
661
  writeBinding(sessionId, contentId, resolvedId);
367
- // 旧版本的工作区键带 contentId;统一重写成只含 Workspace ID 的新键。
368
- writeWorkspaceBinding(workspaceId, contentId, resolvedId);
369
- deleteLegacyWorkspaceBinding(workspaceId, contentId);
662
+ if (useWorkspaceBinding) {
663
+ // 旧版本的工作区键带 contentId;统一重写成只含 Workspace ID 的新键。
664
+ writeWorkspaceBinding(workspaceId, contentId, resolvedId);
665
+ deleteLegacyWorkspaceBinding(workspaceId, contentId);
666
+ }
370
667
  return { id: resolvedId, existing: existing !== undefined };
371
668
  }
669
+ rememberInventory(sessionId, terminals) {
670
+ const workspaceId = this.workspaceIds.get(sessionId) ?? `session:${sessionId}`;
671
+ const normalized = dedupeInventory(terminals);
672
+ const previous = this.inventories.get(workspaceId);
673
+ if (previous !== undefined && sameInventory(previous.terminals, normalized))
674
+ return;
675
+ const revision = this.inventoryRevisionStore.getSnapshot() + 1;
676
+ this.inventories.set(workspaceId, { workspaceId, terminals: normalized, revision });
677
+ this.inventoryRevisionStore.set(revision);
678
+ }
372
679
  async cleanup(request, view) {
373
680
  try {
374
681
  debugInfo('codingns4dsh: client terminal cleanup begin', { sessionId: request.sessionId, terminalId: request.id, hasView: view !== undefined });
@@ -380,6 +687,15 @@ export class CodingNsWebTerminals extends Service {
380
687
  this.closeRequests.delete(String(request.id));
381
688
  persistCloseRequests(this.closeRequests.values());
382
689
  this.closeFailureStore.set(this.closeFailureStore.getSnapshot().filter((item) => item.id !== request.id));
690
+ // 兼容旧 Sidebar 的显式关闭路径:关闭成功后也必须刷新工作区库存,
691
+ // 否则其他会话会继续看到已经关闭的终端记录。
692
+ void this.refreshInventory(request.sessionId).catch((error) => {
693
+ debugWarn('codingns4dsh: client terminal inventory refresh after close failed', {
694
+ sessionId: request.sessionId,
695
+ terminalId: request.id,
696
+ error: errorMessage(error),
697
+ });
698
+ });
383
699
  }
384
700
  catch (error) {
385
701
  if (isTerminalRemoteUnavailable(error)) {
@@ -448,6 +764,24 @@ function errorMessage(error) {
448
764
  function isTerminalRemoteUnavailable(error) {
449
765
  return error instanceof TerminalRemoteUnavailableError;
450
766
  }
767
+ function sameInventory(left, right) {
768
+ if (left.length !== right.length)
769
+ return false;
770
+ for (let index = 0; index < left.length; index += 1) {
771
+ const a = left[index];
772
+ const b = right[index];
773
+ if (a.id !== b.id || a.title !== b.title || a.cwd !== b.cwd || a.cols !== b.cols || a.rows !== b.rows || a.state !== b.state || a.exitCode !== b.exitCode || a.shell.path !== b.shell.path || a.shell.name !== b.shell.name)
774
+ return false;
775
+ }
776
+ return true;
777
+ }
778
+ /** Host 列表按 terminalId 去重,避免重复记录共享同一个关闭动作。 */
779
+ function dedupeInventory(terminals) {
780
+ const unique = new Map();
781
+ for (const terminal of terminals)
782
+ unique.set(terminal.id, terminal);
783
+ return [...unique.values()];
784
+ }
451
785
  function bindingKey(sessionId, contentId) {
452
786
  return `${BINDING_PREFIX}${JSON.stringify([sessionId, contentId])}`;
453
787
  }
@@ -487,6 +821,11 @@ function deleteWorkspaceBinding(workspaceId, _contentId) {
487
821
  }
488
822
  catch { /* 浏览器禁用存储时仅失去跨刷新绑定。 */ }
489
823
  }
824
+ function deleteWorkspaceBindingIfMatches(workspaceId, id) {
825
+ if (readWorkspaceBinding(workspaceId) !== id)
826
+ return;
827
+ deleteWorkspaceBinding(workspaceId);
828
+ }
490
829
  function deleteLegacyWorkspaceBinding(workspaceId, contentId) {
491
830
  try {
492
831
  localStorage.removeItem(legacyWorkspaceBindingKey(workspaceId, contentId));