@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,7 +1,7 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
- import { copyFileSync, existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
- import { homedir, tmpdir } from 'node:os';
4
- import { basename, dirname, join } from 'node:path';
2
+ import { closeSync, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, readSync, realpathSync, statSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { basename, dirname, join, relative, resolve } from 'node:path';
5
5
  import readline from 'node:readline';
6
6
  import { firstToolText, serializeToolValue } from './tool-observation.js';
7
7
  import { usageChunk } from './rpc-driver-utils.js';
@@ -9,6 +9,7 @@ import { commandEnvironment, resolveCommandPath, terminateChildProcess } from '.
9
9
  import { promptWithAttachmentPaths } from './attachment-utils.js';
10
10
  import { commandCodeBridgeArgs, commandCodeBridgeEnvironment } from '../cli-bridge/injections.js';
11
11
  import { getSubagentBridge } from '../cli-bridge/bridge-holder.js';
12
+ import { CommandCodeHistory, } from './command-code-history.js';
12
13
  const WINDOWS = process.platform === 'win32';
13
14
  const COMMAND_CODE_BINARIES = WINDOWS
14
15
  ? ['command-code', 'commandcode', 'cmdc']
@@ -93,6 +94,60 @@ const CATALOG_EFFORTS = new Map([
93
94
  ['xai/grok-4.5', ['low', 'medium', 'high']],
94
95
  ['xai/grok-4.7', ['low', 'medium', 'high', 'xhigh']],
95
96
  ]);
97
+ /**
98
+ * 按模型名的最后一段回退查找思考强度。
99
+ *
100
+ * BYOK 提供者的模型 id 带自定义前缀(如 `mcgrox/deepseek-v4.1-flash`),
101
+ * 而 CATALOG_EFFORTS 的键是 Command Code 内置目录 id(`deepseek/deepseek-v4.1-flash`),
102
+ * 直接查表会落空,导致菜单只剩 Default。表内 77 个键的最后一段互不重复,
103
+ * 因此可以安全地按末段匹配;命中多个时按歧义处理并返回空,避免误判。
104
+ */
105
+ function catalogEffortsFor(id) {
106
+ const key = id.toLowerCase();
107
+ const direct = CATALOG_EFFORTS.get(key);
108
+ if (direct !== undefined)
109
+ return direct;
110
+ const bare = key.includes('/') ? key.slice(key.lastIndexOf('/') + 1) : key;
111
+ if (bare === key)
112
+ return [];
113
+ let matched;
114
+ for (const [candidate, efforts] of CATALOG_EFFORTS) {
115
+ if (!candidate.endsWith(`/${bare}`))
116
+ continue;
117
+ if (matched !== undefined)
118
+ return []; // 末段歧义,宁可不给强度也不猜
119
+ matched = efforts;
120
+ }
121
+ return matched ?? [];
122
+ }
123
+ /**
124
+ * 读取 BYOK 提供者在 `~/.commandcode/providers.json` 里声明的思考强度。
125
+ *
126
+ * 这是 BYOK 模型强度的权威来源:用户在 providers.json 的模型条目上写
127
+ * `reasoningEfforts: [...]`,Command Code 据此校验 `--effort`。内置目录查不到
128
+ * 的自定义模型只能从这里拿到强度。键为 `提供者/模型` 小写形式,空 Map 表示无声明。
129
+ */
130
+ function readDeclaredEfforts(homeDirectory) {
131
+ const declared = new Map();
132
+ const config = readJson(join(homeDirectory, 'providers.json'));
133
+ if (config === null)
134
+ return declared;
135
+ const providers = isRecord(config.provider) ? config.provider : isRecord(config.providers) ? config.providers : null;
136
+ if (providers === null)
137
+ return declared;
138
+ for (const [providerId, provider] of Object.entries(providers)) {
139
+ if (!isRecord(provider) || !isRecord(provider.models))
140
+ continue;
141
+ for (const [modelName, entry] of Object.entries(provider.models)) {
142
+ if (!isRecord(entry) || !Array.isArray(entry.reasoningEfforts))
143
+ continue;
144
+ const efforts = entry.reasoningEfforts.filter((value) => typeof value === 'string' && VALID_EFFORTS.has(value));
145
+ if (efforts.length > 0)
146
+ declared.set(`${providerId}/${modelName}`.toLowerCase(), efforts);
147
+ }
148
+ }
149
+ return declared;
150
+ }
96
151
  /**
97
152
  * CLI 的 `--max-turns` 默认只有 100,复杂任务经常在半途被截断。
98
153
  *
@@ -103,8 +158,22 @@ const DEFAULT_AUTO_CONTINUE_ATTEMPTS = 3;
103
158
  const AUTO_CONTINUE_PROMPT = '继续';
104
159
  /** CLI 在 -p 模式撞到 --max-turns 时的退出码(MAX_TURNS_REACHED)。 */
105
160
  const COMMAND_CODE_MAX_TURNS_EXIT_CODE = 8;
161
+ /** 发送 SIGINT 后等待 CLI 自己收尾的时间;超时再强制清理进程树。 */
162
+ const COMMAND_CODE_INTERRUPT_GRACE_MS = 1_500;
163
+ /** 将 DSH 权限状态映射为 Command Code 参数;未知状态保持 CLI 默认审批。 */
164
+ function commandCodePermissionArgs(permission) {
165
+ if (permission?.sandboxMode === 'danger-full-access' && permission.approvalPolicy === 'never')
166
+ return ['--yolo'];
167
+ if (permission?.sandboxMode === 'workspace-write' && permission.approvalPolicy === 'never')
168
+ return ['--permission-mode', 'accept-edits'];
169
+ if (permission?.sandboxMode === 'read-only')
170
+ return ['--plan'];
171
+ return [];
172
+ }
106
173
  /**
107
- * Command Code 驱动:沿用 `--session + -p + --output-format json` 的 NDJSON 事件流。
174
+ * Command Code 驱动:使用 `--session` 建立首轮输入,随后绑定 Provider 原生会话并用
175
+ * `--resume + -p + --output-format json` 续接。首轮历史只作为 CLI 的输入快照,不能替代
176
+ * Provider 自己维护的 canonical transcript。
108
177
  *
109
178
  * 与 Codex 驱动保持同一套消息优化:正文/推理增量携带 assistant 消息身份,工具完成后
110
179
  * 的下一条 assistant 消息之前结束当前 DSH step,因此一个 Provider 运行会被切成多个
@@ -116,7 +185,7 @@ export class CommandCodeDriver {
116
185
  id: 'command-code',
117
186
  name: 'Command Code',
118
187
  protocol: 'command',
119
- capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage'],
188
+ capabilities: ['models', 'stream', 'resume', 'interrupt', 'tool-events', 'reasoning', 'usage', 'history'],
120
189
  };
121
190
  /** 驱动自己维护 Provider turn 边界,Host 可以把工具边界映射为 DSH step。 */
122
191
  supportsSegmentedTurns = true;
@@ -127,6 +196,7 @@ export class CommandCodeDriver {
127
196
  maxTurns;
128
197
  autoContinueMaxAttempts;
129
198
  autoContinuePrompt;
199
+ history;
130
200
  cachedBinary = null;
131
201
  cachedEnvironment;
132
202
  processes = new Set();
@@ -139,6 +209,54 @@ export class CommandCodeDriver {
139
209
  this.maxTurns = normalizePositiveInteger(options.maxTurns ?? readOptionalIntegerEnv('CODINGNS_COMMAND_CODE_MAX_TURNS'), DEFAULT_MAX_TURNS);
140
210
  this.autoContinueMaxAttempts = normalizeNonNegativeInteger(options.autoContinueMaxAttempts ?? readOptionalIntegerEnv('CODINGNS_COMMAND_CODE_AUTO_CONTINUE_ATTEMPTS'), DEFAULT_AUTO_CONTINUE_ATTEMPTS);
141
211
  this.autoContinuePrompt = options.autoContinuePrompt?.trim() || AUTO_CONTINUE_PROMPT;
212
+ const readStatus = options.readStatus ?? ((workspacePath) => this.readCommandCodeStatus(workspacePath));
213
+ this.history = new CommandCodeHistory(this.homeDirectory, { readStatus });
214
+ }
215
+ /** 读取 Command Code 原生会话目录,供 Provider History 层使用。 */
216
+ detectSessions(workspacePath) {
217
+ return this.history.detectSessions(workspacePath);
218
+ }
219
+ detectSessionsDetailed(workspacePath) {
220
+ return this.history.detectSessionsDetailed(workspacePath);
221
+ }
222
+ startSession(workspacePath, options = {}) {
223
+ return this.history.startSession(workspacePath, options);
224
+ }
225
+ resumeSession(providerSessionId, rawStoreRef) {
226
+ return this.history.resumeSession(providerSessionId, rawStoreRef);
227
+ }
228
+ sendMessage(providerSessionId, rawStoreRef, content) {
229
+ return this.history.sendMessage(providerSessionId, rawStoreRef, content);
230
+ }
231
+ readSessionHistory(providerSessionId, rawStoreRef, cursor, limit, direction = 'forward') {
232
+ return this.history.readSessionHistory(providerSessionId, rawStoreRef, cursor, limit, direction);
233
+ }
234
+ readSessionHistoryDelta(providerSessionId, rawStoreRef, cursor, limit, direction = 'forward') {
235
+ return this.history.readSessionHistoryDelta(providerSessionId, rawStoreRef, cursor, limit, direction);
236
+ }
237
+ subscribeSession(providerSessionId, rawStoreRef, cursor, limit, onEvent) {
238
+ return this.history.subscribeSession(providerSessionId, rawStoreRef, cursor, limit, onEvent);
239
+ }
240
+ readSessionTitle(providerSessionId, rawStoreRef) {
241
+ return this.history.readSessionTitle(providerSessionId, rawStoreRef);
242
+ }
243
+ renameSessionTitle(providerSessionId, rawStoreRef, title) {
244
+ return this.history.renameSessionTitle(providerSessionId, rawStoreRef, title);
245
+ }
246
+ updateSessionArchiveState(providerSessionId, rawStoreRef, isArchived) {
247
+ return this.history.updateSessionArchiveState(providerSessionId, rawStoreRef, isArchived);
248
+ }
249
+ deleteSession(providerSessionId, rawStoreRef) {
250
+ return this.history.deleteSession(providerSessionId, rawStoreRef);
251
+ }
252
+ readContextUsage(providerSessionId, rawStoreRef) {
253
+ return this.history.readContextUsage(providerSessionId, rawStoreRef);
254
+ }
255
+ readSessionStats(providerSessionId, rawStoreRef) {
256
+ return this.history.readSessionStats(providerSessionId, rawStoreRef);
257
+ }
258
+ forkSession(providerSessionId, workspacePath, options) {
259
+ return this.history.forkSession(providerSessionId, workspacePath, options);
142
260
  }
143
261
  async detect() {
144
262
  for (const command of this.binaries) {
@@ -189,6 +307,7 @@ export class CommandCodeDriver {
189
307
  return emptyCatalog();
190
308
  }
191
309
  const groups = [];
310
+ const declared = readDeclaredEfforts(this.homeDirectory);
192
311
  let currentGroup;
193
312
  for (const rawLine of stdout.split(/\r?\n/u)) {
194
313
  const line = rawLine.trim();
@@ -204,20 +323,101 @@ export class CommandCodeDriver {
204
323
  continue;
205
324
  const id = match[1];
206
325
  const description = match[2].trim();
207
- currentGroup.models.push({ id, name: id, ...(description ? { description } : {}), efforts: CATALOG_EFFORTS.get(id.toLowerCase()) ?? [] });
326
+ // BYOK 模型以 providers.json 的声明为准,内置目录作为回退;
327
+ // 目录回退按末段匹配,使 `mcgrox/deepseek-v4.1-flash` 也能拿到内置强度。
328
+ const efforts = declared.get(id.toLowerCase()) ?? catalogEffortsFor(id);
329
+ currentGroup.models.push({ id, name: id, ...(description ? { description } : {}), efforts });
208
330
  }
209
331
  const config = readJson(join(this.homeDirectory, 'config.json'));
210
- const currentModel = typeof config?.model === 'string' ? config.model : null;
332
+ // status --json 反映 CLI 当前真实模型;配置文件可能仍是旧值,优先使用运行时状态。
333
+ let statusModel = null;
334
+ try {
335
+ const status = this.runSpawnSync(detection.command, ['status', '--json'], { encoding: 'utf8', timeout: 5_000, windowsHide: true, shell: WINDOWS, ...(this.cachedEnvironment === undefined ? {} : { env: this.cachedEnvironment }) });
336
+ const parsed = parseJson(status.stdout ?? '');
337
+ statusModel = typeof parsed?.model === 'string' && parsed.model.trim() !== '' ? parsed.model.trim() : null;
338
+ }
339
+ catch { /* 状态读取失败时回退配置文件 */ }
340
+ const currentModel = statusModel ?? (typeof config?.model === 'string' ? config.model : null);
211
341
  const configuredEffort = currentModel !== null && isRecord(config?.reasoningEffort) ? config.reasoningEffort[currentModel] : undefined;
212
342
  const currentEffort = typeof configuredEffort === 'string' && VALID_EFFORTS.has(configuredEffort) ? configuredEffort : null;
213
343
  const result = { groups, currentModel, currentEffort };
214
344
  return result;
215
345
  }
216
- async probeSession(_input) {
217
- return {
218
- state: 'ephemeral',
219
- reason: 'Command Code 当前使用单轮临时 transcript,不存在可恢复的 Provider 原始会话',
220
- };
346
+ /** 读取 Provider 当前 status;历史层只在模型一致时使用其中的上下文窗口。 */
347
+ async readCommandCodeStatus(workspacePath) {
348
+ const command = this.cachedBinary ?? this.binaries[0];
349
+ if (command === undefined)
350
+ return null;
351
+ try {
352
+ const result = this.runSpawnSync(command, ['status', '--json'], {
353
+ cwd: workspacePath,
354
+ encoding: 'utf8',
355
+ timeout: 5_000,
356
+ windowsHide: true,
357
+ shell: WINDOWS,
358
+ env: this.cachedEnvironment ?? commandEnvironment(command),
359
+ });
360
+ return parseJson(result.stdout ?? '');
361
+ }
362
+ catch {
363
+ return null;
364
+ }
365
+ }
366
+ async probeSession(input) {
367
+ throwIfCommandCodeProbeAborted(input.signal);
368
+ const providerSessionId = input.providerSessionId?.trim();
369
+ if (!providerSessionId)
370
+ return { state: 'unknown', reason: '缺少 Provider 会话标识' };
371
+ const candidates = new Set();
372
+ const rawStoreRef = input.rawStoreRef?.trim();
373
+ const safeRawStoreRef = rawStoreRef !== undefined && isSafeCommandCodeTranscriptPath(rawStoreRef, this.homeDirectory)
374
+ ? rawStoreRef
375
+ : undefined;
376
+ if (safeRawStoreRef !== undefined)
377
+ candidates.add(safeRawStoreRef);
378
+ const discovered = resolveCanonicalTranscriptPath(join(this.homeDirectory, 'projects', `${providerSessionId}.jsonl`), this.homeDirectory, input.cwd);
379
+ if (discovered !== null)
380
+ candidates.add(discovered);
381
+ const checkpoint = resolveCommandCodeCheckpointPath(this.homeDirectory, input.cwd ?? process.cwd(), providerSessionId);
382
+ if (checkpoint !== null)
383
+ candidates.add(checkpoint);
384
+ if (candidates.size === 0)
385
+ return { state: 'missing', reason: 'Command Code 原生会话文件不存在' };
386
+ let lastFailure;
387
+ for (const candidate of candidates) {
388
+ throwIfCommandCodeProbeAborted(input.signal);
389
+ if (!existsSync(candidate))
390
+ continue;
391
+ try {
392
+ if (!statSync(candidate).isFile()) {
393
+ lastFailure = { state: 'missing', reason: 'Command Code 原生会话路径不是文件', rawStoreRef: candidate };
394
+ continue;
395
+ }
396
+ // 探测只需验证首条记录;CLI 正在追加时末尾可能存在半行,不能因此拒绝
397
+ // 一个仍可由 --resume 找到的会话。完整历史解析由独立的读取入口负责。
398
+ const firstRecord = readFirstJsonRecord(candidate);
399
+ if (firstRecord === null) {
400
+ lastFailure = { state: 'corrupt', reason: 'Command Code 原生会话首条记录不是有效 JSONL', rawStoreRef: candidate };
401
+ continue;
402
+ }
403
+ if (candidate.endsWith('.checkpoints.jsonl')) {
404
+ // checkpoint 只保存回退点,Command Code 的 --resume 索引仍要求同名
405
+ // `.jsonl` transcript。仅有 checkpoint 的旧会话不能宣称可恢复。
406
+ lastFailure = { state: 'missing', reason: 'Command Code 只有 checkpoint,没有可恢复 transcript', rawStoreRef: candidate };
407
+ continue;
408
+ }
409
+ const recordedId = textValue(firstRecord.id ?? firstRecord.sessionId).trim();
410
+ if (recordedId !== '' && recordedId !== providerSessionId) {
411
+ lastFailure = { state: 'corrupt', reason: 'Command Code 原生会话标识与绑定不一致', rawStoreRef: candidate };
412
+ continue;
413
+ }
414
+ return { state: 'available', reason: 'Command Code 原生会话可用', rawStoreRef: candidate };
415
+ }
416
+ catch (error) {
417
+ lastFailure = { state: 'unreachable', reason: `无法读取 Command Code 原生会话:${error instanceof Error ? error.message : String(error)}`, rawStoreRef: candidate };
418
+ }
419
+ }
420
+ return lastFailure ?? { state: 'missing', reason: 'Command Code 原生会话文件不存在', ...(safeRawStoreRef ? { rawStoreRef: safeRawStoreRef } : {}) };
221
421
  }
222
422
  async *executeTurn(input) {
223
423
  const binary = this.cachedBinary ?? (await this.detect()).command;
@@ -226,7 +426,7 @@ export class CommandCodeDriver {
226
426
  const segmented = input.splitToolSteps === true;
227
427
  const turn = segmented ? this.acquireTurn(input, binary) : this.startTurn(input, binary);
228
428
  let suspended = false;
229
- const onAbort = () => { turn.aborted = true; this.disposeTurn(turn); };
429
+ const onAbort = () => { this.requestGracefulStop(turn); };
230
430
  input.signal?.addEventListener('abort', onAbort, { once: true });
231
431
  if (input.signal?.aborted)
232
432
  onAbort();
@@ -269,12 +469,35 @@ export class CommandCodeDriver {
269
469
  return turn;
270
470
  }
271
471
  startTurn(input, binary) {
272
- const transcriptPath = join(tmpdir(), `codingns4dsh-cc-${safeId(input.sessionId)}.jsonl`);
273
- writeTranscript(transcriptPath, input);
472
+ const cwd = input.cwd ?? process.cwd();
473
+ const requestedProviderSessionId = input.providerSessionId?.trim() || undefined;
474
+ // 旧 synthetic ID 可能只对应 checkpoint;回退时必须以当前 DSH 会话 ID
475
+ // 作为文件名,保证后续 session-binding、索引和 --resume 使用同一个标识。
476
+ const canonicalKey = requestedProviderSessionId !== undefined
477
+ && !isLegacySyntheticSessionId(requestedProviderSessionId)
478
+ ? requestedProviderSessionId
479
+ : input.sessionId;
480
+ const canonicalPath = resolveCommandCodeTranscriptPath(this.homeDirectory, cwd, canonicalKey);
481
+ const hasCanonicalTranscript = existsSync(canonicalPath) && isSafeCommandCodeTranscriptPath(canonicalPath, this.homeDirectory);
482
+ const hasRequestedRawTranscript = input.rawStoreRef?.trim() !== undefined
483
+ && existsSync(input.rawStoreRef.trim())
484
+ && isSafeCommandCodeTranscriptPath(input.rawStoreRef.trim(), this.homeDirectory);
485
+ // 旧版本会把只有 checkpoint 的外部路径登记成 synthetic session ID;该 ID
486
+ // 无法被 Command Code 的 --resume 索引解析。没有可读 JSONL 时重新从 DSH
487
+ // 历史建立 canonical transcript,避免把坏绑定继续传给 Provider。
488
+ const canResume = requestedProviderSessionId !== undefined
489
+ && (hasCanonicalTranscript || hasRequestedRawTranscript || !isLegacySyntheticSessionId(requestedProviderSessionId));
490
+ const providerSessionId = canResume ? requestedProviderSessionId : input.sessionId;
491
+ const transcriptPath = canResume ? undefined : canonicalPath;
492
+ if (transcriptPath !== undefined)
493
+ writeTranscript(transcriptPath, input);
274
494
  const turn = {
275
495
  sessionId: input.sessionId,
276
496
  child: undefined,
277
497
  transcriptPath,
498
+ providerSessionId,
499
+ rawStoreRef: canResume ? input.rawStoreRef?.trim() || undefined : transcriptPath,
500
+ cwd,
278
501
  queue: createEventQueue(),
279
502
  currentMessageId: undefined,
280
503
  sawCompletedTool: false,
@@ -287,7 +510,15 @@ export class CommandCodeDriver {
287
510
  attempt: 0,
288
511
  maxTurnsReached: false,
289
512
  messageSequence: 0,
513
+ stderrTail: '',
290
514
  };
515
+ // 首轮固定使用 canonical transcript;CLI 事件里的 checkpoint ID 不可用于
516
+ // --resume,因此先登记稳定的 DSH 会话 ID,后续回合沿用同一路径恢复。
517
+ if (!canResume && transcriptPath !== undefined) {
518
+ turn.queue.push({ type: 'session-binding', providerSessionId: providerSessionId, rawStoreRef: transcriptPath });
519
+ }
520
+ // 普通回合也登记活动 turn;Registry 的 interrupt 不应只对分段回合生效。
521
+ this.turns.set(input.sessionId, turn);
291
522
  void this.runTurnAttempts(turn, input, binary);
292
523
  return turn;
293
524
  }
@@ -304,22 +535,28 @@ export class CommandCodeDriver {
304
535
  try {
305
536
  while (true) {
306
537
  turn.attempt += 1;
538
+ turn.stderrTail = '';
307
539
  // 自动续跑是同一个 DSH 运行里的下一段;消息序号必须跨尝试连续,
308
540
  // 否则新进程会从 command-code-message-1 重新编号,公共投影层会把它
309
541
  // 当成同一条消息继续追加,而不是开启新段。
310
- const state = createStreamState(() => turn.aborted, turn.messageSequence, input.sessionId);
542
+ const state = createStreamState(() => turn.aborted, turn.messageSequence, turn.providerSessionId ?? null, turn.sessionId);
311
543
  const child = this.spawnAttempt(turn, input, binary);
312
544
  const code = await this.readAttempt(turn, child, state);
313
545
  turn.messageSequence = state.messageSequence;
546
+ // 首轮使用临时 `--session` 文件。Provider 会话 ID 在流中确认后,必须在
547
+ // 本次尝试结束时把临时 transcript 提升到 canonical 路径;否则下一轮拿着
548
+ // 已登记的 ID 执行 `--resume` 时,Command Code 找不到对应原生会话。
549
+ if (turn.transcriptPath !== undefined && turn.providerSessionId !== undefined) {
550
+ syncCommandCodeTranscript(turn.transcriptPath, this.homeDirectory, input.cwd, turn.providerSessionId);
551
+ }
314
552
  if (turn.disposed || turn.finished)
315
553
  return;
316
554
  const capped = state.maxTurnsReached || code === COMMAND_CODE_MAX_TURNS_EXIT_CODE;
317
555
  const autoContinueUsed = turn.attempt - 1;
318
- if (capped && !turn.aborted && !turn.terminal && autoContinueUsed < this.autoContinueMaxAttempts) {
556
+ if (capped && !turn.aborted && !turn.terminal && turn.providerSessionId !== undefined && autoContinueUsed < this.autoContinueMaxAttempts) {
319
557
  // 续跑必须落在同一个会话上。CLI 可能把 transcript 写回会话文件、也可能
320
558
  // 写进按 cwd 归档的 canonical 目录;两种落点都要先归位到同一个文件,
321
559
  // 否则“继续”会开出一个没有上文的新会话,等于白跑一轮预算。
322
- syncCommandCodeTranscript(turn.transcriptPath, this.homeDirectory, input.cwd);
323
560
  continue;
324
561
  }
325
562
  if (capped && !turn.aborted && !turn.terminal) {
@@ -343,7 +580,7 @@ export class CommandCodeDriver {
343
580
  const bridgeEnvironment = commandCodeBridgeEnvironment(input.sessionId, this.descriptor.id);
344
581
  const child = this.runSpawn(binary, args, {
345
582
  cwd: input.cwd ?? process.cwd(),
346
- env: { ...(this.cachedEnvironment ?? commandEnvironment(binary)), ...bridgeEnvironment },
583
+ env: { ...(this.cachedEnvironment ?? commandEnvironment(binary)), ...(input.runtimeEnv ?? {}), ...bridgeEnvironment },
347
584
  stdio: ['ignore', 'pipe', 'pipe'],
348
585
  windowsHide: true,
349
586
  shell: WINDOWS,
@@ -352,26 +589,69 @@ export class CommandCodeDriver {
352
589
  if (turn.child !== undefined)
353
590
  this.processes.delete(turn.child);
354
591
  this.processes.add(child);
355
- // 必须消费 stderr,错误内容不能回传给 DSH,避免泄露命令参数或文件片段。
356
- child.stderr?.on('data', () => undefined);
592
+ // 必须消费 stderr,错误内容只保留尾部用于诊断,避免泄露整段命令输出。
593
+ child.stderr?.on('data', (chunk) => {
594
+ turn.stderrTail = `${turn.stderrTail}${chunk.toString()}`.slice(-512);
595
+ });
357
596
  turn.child = child;
358
597
  return child;
359
598
  }
360
599
  buildTurnArgs(input, turn) {
361
- // 续跑沿用同一个 transcript 文件,只把输入换成续跑提示;不能再次写入历史,
600
+ // 续跑沿用同一个 Provider 会话,只把输入换成续跑提示;不能再次写入历史,
362
601
  // 否则会把 CLI 已经落盘的进度覆盖成空会话。
363
602
  const prompt = turn.attempt > 1 ? this.autoContinuePrompt : promptWithAttachmentPaths(input.prompt, input.attachments ?? []);
364
- const args = ['--session', turn.transcriptPath, '-p', prompt, '--output-format', 'json', '--tools-all', '--yolo', '--max-turns', String(this.maxTurns)];
603
+ const args = turn.transcriptPath !== undefined && turn.attempt === 1
604
+ ? ['--session', turn.transcriptPath, '-p', prompt, '--output-format', 'json', '--skip-onboarding', '--max-turns', String(this.maxTurns)]
605
+ : turn.providerSessionId !== undefined
606
+ ? ['-p', prompt, '--output-format', 'json', '--skip-onboarding', '--max-turns', String(this.maxTurns), '--resume', turn.providerSessionId]
607
+ : ['--session', turn.transcriptPath, '-p', prompt, '--output-format', 'json', '--skip-onboarding', '--max-turns', String(this.maxTurns)];
608
+ // 只有 Host 明确确认“完全访问且永不询问”时才允许 --yolo。
609
+ // 权限状态缺省表示尚未读取,必须保持 CLI 的保守默认,不能把未知状态升级为完全访问。
610
+ args.push(...commandCodePermissionArgs(input.permission));
611
+ if (input.plan === true && !args.includes('--plan'))
612
+ args.push('--plan');
613
+ // Command Code 只允许从已有 --resume 会话分叉;首轮临时 transcript 不能带该参数。
614
+ if (input.forkSession === true && turn.providerSessionId !== undefined)
615
+ args.push('--fork-session');
616
+ if (input.enableAskUserQuestion === true || input.runtimeEnv?.CMD_TOOLS_ASK_USER_QUESTION_ENABLE === 'true') {
617
+ args.push('--tools-enable', 'ask_user_question');
618
+ }
365
619
  // 子代理托管开启时加载桥接 mod:内建 agent 调用会被转投成 DSH 原生子会话。
366
620
  args.push(...commandCodeBridgeArgs(input.sessionId));
367
621
  for (const directory of new Set((input.attachments ?? []).map((attachment) => dirname(attachment.path))))
368
622
  args.push('--add-dir', directory);
369
- if (input.modelId)
370
- args.push('-m', input.modelId);
371
- if (input.effortId && input.effortId !== 'default' && input.effortId !== 'Default')
372
- args.push('--effort', input.effortId);
623
+ if (input.modelId && input.modelId !== 'provider-default')
624
+ args.push('--model', input.modelId);
625
+ const effort = input.effortId?.trim().toLowerCase();
626
+ if (effort !== undefined && VALID_EFFORTS.has(effort))
627
+ args.push('--effort', effort);
373
628
  return args;
374
629
  }
630
+ /** 中断当前会话并清理 CLI 进程;不存在活动运行时视为幂等成功。 */
631
+ async interrupt(sessionId) {
632
+ const turn = this.turns.get(sessionId);
633
+ if (turn === undefined)
634
+ return;
635
+ this.requestGracefulStop(turn);
636
+ }
637
+ /** 先让 CLI 保存检查点并自行退出,超时后才清理进程树。 */
638
+ requestGracefulStop(turn) {
639
+ turn.aborted = true;
640
+ const child = turn.child;
641
+ if (child === undefined) {
642
+ this.disposeTurn(turn);
643
+ return;
644
+ }
645
+ try {
646
+ child.kill('SIGINT');
647
+ }
648
+ catch { /* 进程可能已经退出 */ }
649
+ const timer = setTimeout(() => {
650
+ if (!turn.disposed)
651
+ this.disposeTurn(turn);
652
+ }, COMMAND_CODE_INTERRUPT_GRACE_MS);
653
+ timer.unref?.();
654
+ }
375
655
  /** 读取一次尝试的 stdout,直到进程结束;队列在整个运行结束前保持打开。 */
376
656
  async readAttempt(turn, child, state) {
377
657
  // 测试替身可能只提供 stdout/kill,没有 ChildProcess 的事件接口。
@@ -379,6 +659,7 @@ export class CommandCodeDriver {
379
659
  ? child
380
660
  : null;
381
661
  let exitCode = null;
662
+ let exitSignal = null;
382
663
  let resolveClose = null;
383
664
  const closed = new Promise((resolve) => { resolveClose = resolve; });
384
665
  if (emitter !== null) {
@@ -389,8 +670,9 @@ export class CommandCodeDriver {
389
670
  turn.failure ??= error;
390
671
  resolveClose?.();
391
672
  });
392
- emitter.on('close', (code) => {
673
+ emitter.on('close', (code, signal) => {
393
674
  exitCode = code;
675
+ exitSignal = signal ?? null;
394
676
  if (state.maxTurnsReached)
395
677
  turn.maxTurnsReached = true;
396
678
  resolveClose?.();
@@ -398,15 +680,32 @@ export class CommandCodeDriver {
398
680
  }
399
681
  try {
400
682
  const lines = readline.createInterface({ input: child.stdout });
683
+ let lineNumber = 0;
401
684
  try {
402
685
  for await (const line of lines) {
403
686
  if (!line.trim())
404
687
  continue;
688
+ lineNumber += 1;
405
689
  const item = parseJson(line);
406
- if (item === null)
690
+ if (item === null) {
691
+ // CLI 升级后可能输出非对象或损坏的 NDJSON;保留行号诊断,不能静默吞掉。
692
+ console.warn(`[codingns4dsh] Command Code 忽略非法 NDJSON(第 ${lineNumber} 行)`);
407
693
  continue;
694
+ }
408
695
  const event = item.type === 'event' && isRecord(item.event) ? item.event : item;
409
- for (const chunk of commandCodeEventChunks(event, state)) {
696
+ for (const rawChunk of commandCodeEventChunks(event, state)) {
697
+ let chunk = rawChunk;
698
+ if (rawChunk.type === 'session-binding') {
699
+ // `--session <path>` 模式下 CLI 发出的 sessionId 是 checkpoint
700
+ // 内部 ID,不是可供 --resume 查找的 transcript ID;保持 Host 绑定。
701
+ if (turn.transcriptPath !== undefined)
702
+ continue;
703
+ turn.providerSessionId = rawChunk.providerSessionId;
704
+ turn.rawStoreRef = resolveCommandCodeTranscriptPath(this.homeDirectory, turn.cwd, rawChunk.providerSessionId);
705
+ // Provider ID 一旦确认就立即暴露 canonical 路径;文件可能稍后才落盘,
706
+ // 但 Registry 必须先保存稳定绑定,冷恢复时才能直接定位原生会话。
707
+ chunk = { ...rawChunk, rawStoreRef: turn.rawStoreRef };
708
+ }
410
709
  if (chunk.type === 'finish')
411
710
  turn.terminal = true;
412
711
  turn.queue.push(chunk);
@@ -428,6 +727,23 @@ export class CommandCodeDriver {
428
727
  else {
429
728
  await closed;
430
729
  }
730
+ if (!turn.terminal && !state.maxTurnsReached) {
731
+ if (turn.aborted || exitSignal === 'SIGINT' || exitSignal === 'SIGTERM') {
732
+ turn.terminal = true;
733
+ turn.queue.push({ type: 'finish', reason: 'cancel' });
734
+ }
735
+ else if (turn.failure !== null || (exitCode !== null && exitCode !== 0) || exitSignal !== null) {
736
+ const message = turn.failure?.message
737
+ || (turn.stderrTail.trim() !== '' ? `Command Code 执行失败:${turn.stderrTail.trim()}` : `Command Code 进程退出(代码 ${exitCode ?? exitSignal ?? 'unknown'})`);
738
+ turn.failure = null;
739
+ turn.terminal = true;
740
+ turn.queue.push({
741
+ type: 'finish',
742
+ reason: 'error',
743
+ failure: { message, code: exitCode === null ? 'COMMAND_CODE_SPAWN_ERROR' : `COMMAND_CODE_EXIT_${exitCode}` },
744
+ });
745
+ }
746
+ }
431
747
  return exitCode;
432
748
  }
433
749
  async *consumeTurn(turn, input, segmented, suspend) {
@@ -490,13 +806,10 @@ export class CommandCodeDriver {
490
806
  this.processes.delete(child);
491
807
  terminateChildProcess(child);
492
808
  }
493
- try {
494
- rmSync(turn.transcriptPath, { force: true });
495
- }
496
- catch { /* 临时文件清理尽力而为 */ }
809
+ // 首轮 transcript 已经是 canonical 文件,必须保留给下一轮 --resume 及冷恢复。
497
810
  }
498
811
  }
499
- function createStreamState(aborted, messageSequence = 0, sessionId = null) {
812
+ function createStreamState(aborted, messageSequence = 0, sessionId = null, bridgeSessionId = '') {
500
813
  return {
501
814
  messageSequence,
502
815
  messageId: null,
@@ -506,8 +819,10 @@ function createStreamState(aborted, messageSequence = 0, sessionId = null) {
506
819
  perRequestUsageSeen: false,
507
820
  turnUsageSeen: false,
508
821
  sessionId,
822
+ bridgeSessionId,
509
823
  aborted,
510
824
  maxTurnsReached: false,
825
+ terminalEmitted: false,
511
826
  };
512
827
  }
513
828
  function createEventQueue() {
@@ -561,6 +876,16 @@ function commandCodeEventChunks(event, state) {
561
876
  chunks.push({ type: 'session-binding', providerSessionId: sessionId });
562
877
  }
563
878
  switch (type) {
879
+ case 'run_start':
880
+ case 'run-start':
881
+ case 'session_created':
882
+ case 'session-created':
883
+ case 'model_request_start':
884
+ case 'model-request-start':
885
+ case 'model_trace':
886
+ case 'model-trace':
887
+ // 这些事件只标记 Provider 生命周期,不应制造正文或终态。
888
+ break;
564
889
  case 'turn_start':
565
890
  case 'turn-start':
566
891
  beginMessage(state);
@@ -625,19 +950,36 @@ function commandCodeEventChunks(event, state) {
625
950
  }
626
951
  case 'run_end':
627
952
  case 'run-end':
628
- // run_end 只是“CLI 侧整个 run 收尾了”,最终结算仍以紧随其后的 result 行为准。
629
- // 这里只识别轮次上限:上限不是终态,等进程退出后决定续跑还是报错。
630
- if (isMaxTurnsOutcome(event))
953
+ if (!state.sawText) {
954
+ const finalText = readFinalText(event);
955
+ if (finalText) {
956
+ ensureMessage(state);
957
+ state.sawText = true;
958
+ state.emittedText = finalText;
959
+ chunks.push(textDelta(finalText, state));
960
+ }
961
+ }
962
+ if (isMaxTurnsOutcome(event)) {
963
+ // 撞到 CLI 的轮次上限不是终态,但 run_end 可能同时携带本次已生成的
964
+ // 最后一段正文。正文必须先投影出去,续跑只负责继续执行,不能吞掉这段结果。
631
965
  state.maxTurnsReached = true;
966
+ break;
967
+ }
968
+ if (!state.terminalEmitted) {
969
+ state.terminalEmitted = true;
970
+ chunks.push({ type: 'finish', reason: state.aborted() ? 'cancel' : resultReason(event) });
971
+ }
632
972
  break;
633
973
  case 'result': {
974
+ if (state.terminalEmitted)
975
+ break;
634
976
  if (!state.perRequestUsageSeen) {
635
977
  const usage = usageChunk(recordValue(event.usage));
636
978
  if (usage !== null)
637
979
  chunks.push(usage);
638
980
  }
639
981
  if (!state.sawText) {
640
- const finalText = textValue(event.finalText ?? recordValue(event.result)?.finalText ?? (typeof event.result === 'string' ? event.result : undefined));
982
+ const finalText = readFinalText(event);
641
983
  if (finalText) {
642
984
  ensureMessage(state);
643
985
  state.sawText = true;
@@ -652,6 +994,7 @@ function commandCodeEventChunks(event, state) {
652
994
  break;
653
995
  }
654
996
  const reason = state.aborted() ? 'cancel' : resultReason(event);
997
+ state.terminalEmitted = true;
655
998
  chunks.push({
656
999
  type: 'finish',
657
1000
  reason,
@@ -659,7 +1002,19 @@ function commandCodeEventChunks(event, state) {
659
1002
  });
660
1003
  break;
661
1004
  }
1005
+ case 'error':
1006
+ case 'fatal_error':
1007
+ case 'run_error':
1008
+ if (!state.terminalEmitted) {
1009
+ state.terminalEmitted = true;
1010
+ chunks.push({ type: 'finish', reason: state.aborted() ? 'cancel' : 'error', ...(state.aborted() ? {} : { failure: commandCodeFailure(event) }) });
1011
+ }
1012
+ break;
662
1013
  default:
1014
+ if (type !== '' && !isToolStart(type) && !isToolResult(type)) {
1015
+ // 未知事件不应改变正常流,但必须留下可定位的升级诊断。
1016
+ console.warn(`[codingns4dsh] Command Code 忽略未知事件:${type}`);
1017
+ }
663
1018
  break;
664
1019
  }
665
1020
  if (isToolStart(type)) {
@@ -683,15 +1038,25 @@ function commandCodeEventChunks(event, state) {
683
1038
  }
684
1039
  return chunks;
685
1040
  }
1041
+ function readFinalText(event) {
1042
+ const result = recordValue(event.result);
1043
+ return textValue(event.finalText
1044
+ ?? result?.finalText
1045
+ ?? result?.output
1046
+ ?? result?.text
1047
+ ?? (typeof event.result === 'string' ? event.result : undefined)
1048
+ ?? event.output
1049
+ ?? event.text);
1050
+ }
686
1051
  /** 该 blocked 事件是否来自桥接转投;命中后消费一次记录,避免重复投影。 */
687
1052
  function consumeBridgeRedirect(state, event) {
688
- if (state.sessionId === null || state.sessionId === '')
1053
+ if (state.bridgeSessionId === '')
689
1054
  return false;
690
1055
  const callId = firstToolText(event.callId, event.call_id, event.toolCallId, event.tool_call_id, event.toolUseId, event.tool_use_id, event.id);
691
1056
  if (callId === undefined || callId === '')
692
1057
  return false;
693
1058
  try {
694
- return getSubagentBridge()?.consumeRedirect(state.sessionId, callId) !== undefined;
1059
+ return getSubagentBridge()?.consumeRedirect(state.bridgeSessionId, callId) !== undefined;
695
1060
  }
696
1061
  catch {
697
1062
  return false;
@@ -724,8 +1089,17 @@ function reasoningDelta(text, state) {
724
1089
  function appendContentFallback(chunks, content, state) {
725
1090
  if (!Array.isArray(content))
726
1091
  return;
727
- let text = '';
728
- let reasoning = '';
1092
+ let snapshotText = '';
1093
+ let snapshotReasoning = '';
1094
+ let pendingKind = null;
1095
+ let pendingDelta = '';
1096
+ const flushPending = () => {
1097
+ if (pendingKind === null || pendingDelta === '')
1098
+ return;
1099
+ chunks.push(pendingKind === 'text' ? textDelta(pendingDelta, state) : reasoningDelta(pendingDelta, state));
1100
+ pendingKind = null;
1101
+ pendingDelta = '';
1102
+ };
729
1103
  for (const block of content) {
730
1104
  const value = recordValue(block);
731
1105
  if (value === null)
@@ -733,24 +1107,36 @@ function appendContentFallback(chunks, content, state) {
733
1107
  const blockType = textValue(value.type).toLowerCase();
734
1108
  const isReasoning = blockType.includes('thinking') || blockType.includes('reasoning')
735
1109
  || typeof value.thinking === 'string' || typeof value.reasoning === 'string';
736
- if (isReasoning)
737
- reasoning += textValue(value.thinking ?? value.reasoning ?? value.text ?? value.content);
738
- else
739
- text += textValue(value.text ?? value.content);
740
- }
741
- if (text.length > state.emittedText.length) {
742
- const delta = text.slice(state.emittedText.length);
743
- ensureMessage(state);
744
- state.emittedText = text;
745
- state.sawText = true;
746
- chunks.push(textDelta(delta, state));
747
- }
748
- if (reasoning.length > state.emittedReasoning.length) {
749
- const delta = reasoning.slice(state.emittedReasoning.length);
750
- ensureMessage(state);
751
- state.emittedReasoning = reasoning;
752
- chunks.push(reasoningDelta(delta, state));
1110
+ const blockText = textValue(isReasoning
1111
+ ? value.thinking ?? value.reasoning ?? value.text ?? value.content
1112
+ : value.text ?? value.content);
1113
+ if (isReasoning) {
1114
+ snapshotReasoning += blockText;
1115
+ if (snapshotReasoning.length > state.emittedReasoning.length) {
1116
+ const delta = snapshotReasoning.slice(state.emittedReasoning.length);
1117
+ ensureMessage(state);
1118
+ state.emittedReasoning = snapshotReasoning;
1119
+ if (pendingKind !== 'reasoning')
1120
+ flushPending();
1121
+ pendingKind = 'reasoning';
1122
+ pendingDelta += delta;
1123
+ }
1124
+ }
1125
+ else {
1126
+ snapshotText += blockText;
1127
+ if (snapshotText.length > state.emittedText.length) {
1128
+ const delta = snapshotText.slice(state.emittedText.length);
1129
+ ensureMessage(state);
1130
+ state.emittedText = snapshotText;
1131
+ state.sawText = true;
1132
+ if (pendingKind !== 'text')
1133
+ flushPending();
1134
+ pendingKind = 'text';
1135
+ pendingDelta += delta;
1136
+ }
1137
+ }
753
1138
  }
1139
+ flushPending();
754
1140
  }
755
1141
  function readToolChunk(event, status) {
756
1142
  const callId = firstToolText(event.callId, event.call_id, event.toolCallId, event.tool_call_id, event.toolUseId, event.tool_use_id, event.id);
@@ -825,20 +1211,36 @@ function isMaxTurnsOutcome(event) {
825
1211
  *
826
1212
  * 实测 CLI 的落盘位置取决于会话文件里是否已有历史:空会话(只有 session 头)时
827
1213
  * 会把消息写进 `~/.commandcode/projects/<slug>/<id>.jsonl`,带上历史时则写回
828
- * `--session` 指定的文件。两种落点都要先归位,否则自动续跑的“继续”会落在一个
829
- * 没有上文的会话上。归位只做“canonical 比本地新”的单向复制,不覆盖更新的本地文件。
1214
+ * `--session` 指定的文件。两种落点都要统一,否则下一轮恢复会落在一个没有上文的
1215
+ * 会话上。两边都存在时按修改时间同步最新内容,首轮则创建 canonical 文件。
830
1216
  */
831
- function syncCommandCodeTranscript(transcriptPath, homeDirectory, cwd) {
832
- const canonicalPath = resolveCanonicalTranscriptPath(transcriptPath, homeDirectory, cwd);
1217
+ function syncCommandCodeTranscript(transcriptPath, homeDirectory, cwd, providerSessionId) {
1218
+ const canonicalPath = providerSessionId === undefined
1219
+ ? resolveCanonicalTranscriptPath(transcriptPath, homeDirectory, cwd)
1220
+ : resolveCommandCodeTranscriptPath(homeDirectory, cwd ?? process.cwd(), providerSessionId);
833
1221
  if (canonicalPath === null)
834
1222
  return;
1223
+ if (providerSessionId !== undefined && !isSafeCommandCodeTranscriptTarget(canonicalPath, homeDirectory))
1224
+ return;
835
1225
  try {
836
- if (statSync(canonicalPath).mtimeMs <= statSync(transcriptPath).mtimeMs)
1226
+ if (!existsSync(transcriptPath) || !statSync(transcriptPath).isFile())
1227
+ return;
1228
+ const sourceMtime = statSync(transcriptPath).mtimeMs;
1229
+ if (!existsSync(canonicalPath)) {
1230
+ mkdirSync(dirname(canonicalPath), { recursive: true });
1231
+ copyFileSync(transcriptPath, canonicalPath);
837
1232
  return;
838
- copyFileSync(canonicalPath, transcriptPath);
1233
+ }
1234
+ if (!statSync(canonicalPath).isFile())
1235
+ return;
1236
+ const canonicalMtime = statSync(canonicalPath).mtimeMs;
1237
+ if (sourceMtime > canonicalMtime)
1238
+ copyFileSync(transcriptPath, canonicalPath);
1239
+ else if (canonicalMtime > sourceMtime)
1240
+ copyFileSync(canonicalPath, transcriptPath);
839
1241
  }
840
1242
  catch {
841
- // transcript 归位是续跑优化;文件缺失或不可读时保持现状即可。
1243
+ // transcript 归位是恢复前的必要同步;文件竞态或不可读时保持当前运行结果。
842
1244
  }
843
1245
  }
844
1246
  /** 按 CLI 的 `<cwd slug>/<session id>` 规则定位 canonical transcript。 */
@@ -868,6 +1270,86 @@ function resolveCanonicalTranscriptPath(transcriptPath, homeDirectory, cwd) {
868
1270
  }
869
1271
  return null;
870
1272
  }
1273
+ /** 按 Provider 会话 ID 计算 Command Code 的 canonical transcript 路径。 */
1274
+ function resolveCommandCodeTranscriptPath(homeDirectory, cwd, providerSessionId) {
1275
+ return join(homeDirectory, 'projects', workspaceSlug(cwd), `${safeId(providerSessionId)}.jsonl`);
1276
+ }
1277
+ /** Provider 索引只能指向 Command Code 项目目录中的 JSONL,避免旧配置污染探测边界。 */
1278
+ function isSafeCommandCodeTranscriptPath(candidate, homeDirectory) {
1279
+ const projectsRootPath = resolve(join(homeDirectory, 'projects'));
1280
+ let projectsRoot = projectsRootPath;
1281
+ try {
1282
+ if (existsSync(projectsRootPath))
1283
+ projectsRoot = realpathSync(projectsRootPath);
1284
+ }
1285
+ catch {
1286
+ return false;
1287
+ }
1288
+ const resolvedCandidate = resolve(candidate);
1289
+ let actualCandidate = resolvedCandidate;
1290
+ try {
1291
+ if (existsSync(resolvedCandidate))
1292
+ actualCandidate = realpathSync(resolvedCandidate);
1293
+ }
1294
+ catch {
1295
+ return false;
1296
+ }
1297
+ const relativePath = relative(projectsRoot, actualCandidate);
1298
+ const segments = relativePath.split(/[\\/]+/u).filter(Boolean);
1299
+ return relativePath !== ''
1300
+ && !relativePath.startsWith('..')
1301
+ && segments.length === 2
1302
+ && actualCandidate.endsWith('.jsonl')
1303
+ // checkpoint 不是 --resume 使用的 transcript,即使扩展名同为 .jsonl
1304
+ // 也不能把它当作可恢复会话绑定。
1305
+ && !actualCandidate.endsWith('.checkpoints.jsonl');
1306
+ }
1307
+ /** 允许同步创建的 canonical transcript 目标,路径必须严格位于 projects/<slug>/*.jsonl。 */
1308
+ function isSafeCommandCodeTranscriptTarget(candidate, homeDirectory) {
1309
+ const projectsRoot = resolve(join(homeDirectory, 'projects'));
1310
+ const resolvedCandidate = resolve(candidate);
1311
+ const relativePath = relative(projectsRoot, resolvedCandidate);
1312
+ const segments = relativePath.split(/[\\/]+/u).filter(Boolean);
1313
+ return relativePath !== ''
1314
+ && !relativePath.startsWith('..')
1315
+ && segments.length === 2
1316
+ && resolvedCandidate.endsWith('.jsonl')
1317
+ && !resolvedCandidate.endsWith('.checkpoints.jsonl');
1318
+ }
1319
+ /** 只读 JSONL 首条记录,避免探测大 transcript 时把整份文件载入内存。 */
1320
+ function readFirstJsonRecord(path) {
1321
+ const descriptor = openSync(path, 'r');
1322
+ try {
1323
+ const buffer = Buffer.allocUnsafe(64 * 1024);
1324
+ const bytes = readSync(descriptor, buffer, 0, buffer.length, 0);
1325
+ const firstLine = buffer.subarray(0, bytes).toString('utf8').split(/\r?\n/u).find((line) => line.trim() !== '');
1326
+ return firstLine === undefined ? null : parseJson(firstLine);
1327
+ }
1328
+ finally {
1329
+ closeSync(descriptor);
1330
+ }
1331
+ }
1332
+ /**
1333
+ * Command Code 的 `--session <临时路径>` 在部分版本只落检查点文件,不创建主 transcript。
1334
+ * 检查点足以证明会话仍可被 `--resume` 找到,但不包含完整消息历史,因此只用于探测回退。
1335
+ */
1336
+ function resolveCommandCodeCheckpointPath(homeDirectory, cwd, providerSessionId) {
1337
+ const expected = join(homeDirectory, 'projects', workspaceSlug(cwd), `${providerSessionId}.checkpoints.jsonl`);
1338
+ if (existsSync(expected))
1339
+ return expected;
1340
+ const projectsRoot = join(homeDirectory, 'projects');
1341
+ try {
1342
+ for (const entry of readdirSync(projectsRoot)) {
1343
+ const candidate = join(projectsRoot, entry, `${providerSessionId}.checkpoints.jsonl`);
1344
+ if (existsSync(candidate))
1345
+ return candidate;
1346
+ }
1347
+ }
1348
+ catch {
1349
+ // projects 目录不存在说明 CLI 还没写过检查点。
1350
+ }
1351
+ return null;
1352
+ }
871
1353
  /** CLI 的 canonical 目录名:绝对路径去掉前导分隔符,非字母数字统一换成连字符并转小写。 */
872
1354
  function workspaceSlug(workspacePath) {
873
1355
  return workspacePath.replace(/[\\/]+$/, '')
@@ -906,6 +1388,14 @@ function readSessionId(event) {
906
1388
  const value = textValue(event.sessionId ?? event.session_id ?? recordValue(event.session)?.id ?? recordValue(event.result)?.sessionId).trim();
907
1389
  return value === '' ? null : value;
908
1390
  }
1391
+ /** 会话探测由 Registry 控制超时;文件扫描期间也必须尊重取消信号。 */
1392
+ function throwIfCommandCodeProbeAborted(signal) {
1393
+ if (signal?.aborted !== true)
1394
+ return;
1395
+ const error = new Error('Command Code 会话探测已取消');
1396
+ error.name = 'AbortError';
1397
+ throw error;
1398
+ }
909
1399
  function textValue(value) {
910
1400
  return typeof value === 'string' ? value : value === undefined || value === null ? '' : structuredText(value);
911
1401
  }
@@ -923,22 +1413,97 @@ function recordValue(value) {
923
1413
  return isRecord(value) ? value : null;
924
1414
  }
925
1415
  function writeTranscript(path, input) {
1416
+ mkdirSync(dirname(path), { recursive: true });
926
1417
  const history = input.messages.length > 0 ? input.messages.slice(0, -1) : [];
927
1418
  let parentId = null;
928
1419
  const lines = [JSON.stringify({ type: 'session', version: 3, id: input.sessionId, timestamp: new Date().toISOString(), cwd: input.cwd ?? process.cwd() })];
929
1420
  history.forEach((message, index) => {
930
- if (message.role !== 'user' && message.role !== 'assistant')
1421
+ if (message.role !== 'user' && message.role !== 'assistant' && message.role !== 'tool')
1422
+ return;
1423
+ const content = extractText(message.content, message);
1424
+ if (content.length === 0)
931
1425
  return;
932
1426
  const id = message.id ?? `message-${index}`;
933
- lines.push(JSON.stringify({ type: 'message', id, parentId, timestamp: new Date().toISOString(), message: { role: message.role, content: [{ type: 'text', text: extractText(message.content) }] } }));
1427
+ const role = message.role === 'tool' ? 'user' : message.role;
1428
+ lines.push(JSON.stringify({ type: 'message', id, parentId, timestamp: new Date().toISOString(), message: { role, content } }));
934
1429
  parentId = id;
935
1430
  });
936
1431
  writeFileSync(path, `${lines.join('\n')}\n`, 'utf8');
937
1432
  }
938
- function extractText(content) { if (typeof content === 'string')
939
- return content; if (!Array.isArray(content))
940
- return ''; return content.filter(isRecord).map((part) => typeof part.text === 'string' ? part.text : '').join('\n').trim(); }
1433
+ /** 把 DSH 消息块转换为 Command Code 原生 transcript 块,保留工具和推理语义。 */
1434
+ function extractText(content, message) {
1435
+ if (message.role === 'tool') {
1436
+ const toolCallId = message.toolCallId ?? message.source?.callId;
1437
+ if (toolCallId === undefined || toolCallId.trim() === '')
1438
+ return [];
1439
+ return [{ type: 'tool_result', tool_use_id: toolCallId, content: extractToolResultContent(content) }];
1440
+ }
1441
+ if (typeof content === 'string')
1442
+ return content === '' ? [] : [{ type: 'text', text: content }];
1443
+ if (!Array.isArray(content))
1444
+ return [];
1445
+ const blocks = [];
1446
+ for (const part of content) {
1447
+ if (!isRecord(part))
1448
+ continue;
1449
+ if (part.type === 'tool-call') {
1450
+ const id = typeof part.id === 'string' ? part.id.trim() : '';
1451
+ const name = typeof part.name === 'string' ? part.name.trim() : '';
1452
+ if (id === '' || name === '')
1453
+ continue;
1454
+ blocks.push({ type: 'tool_use', id, name, input: parseToolInput(part.arguments ?? part.input) });
1455
+ continue;
1456
+ }
1457
+ if (part.type === 'tool-result') {
1458
+ const toolUseId = firstText(part.toolCallId, part.tool_use_id, part.toolUseId);
1459
+ if (toolUseId !== '')
1460
+ blocks.push({ type: 'tool_result', tool_use_id: toolUseId, content: extractToolResultContent(part.content ?? part.output ?? part.result) });
1461
+ continue;
1462
+ }
1463
+ if (part.type === 'thinking' || part.type === 'reasoning') {
1464
+ const thinking = firstText(part.thinking, part.reasoning, part.text, part.content);
1465
+ if (thinking !== '')
1466
+ blocks.push({ type: 'thinking', thinking });
1467
+ continue;
1468
+ }
1469
+ const text = typeof part.text === 'string' ? part.text : '';
1470
+ if (text !== '')
1471
+ blocks.push({ type: 'text', text });
1472
+ }
1473
+ return blocks;
1474
+ }
1475
+ function extractToolResultContent(content) {
1476
+ if (typeof content === 'string')
1477
+ return [{ type: 'text', text: content }];
1478
+ if (Array.isArray(content)) {
1479
+ const parts = content.filter(isRecord).map((part) => {
1480
+ if (typeof part.text === 'string')
1481
+ return { type: 'text', text: part.text };
1482
+ return null;
1483
+ }).filter((part) => part !== null);
1484
+ if (parts.length > 0)
1485
+ return parts;
1486
+ }
1487
+ return [{ type: 'text', text: structuredText(content) }];
1488
+ }
1489
+ function parseToolInput(value) {
1490
+ if (typeof value !== 'string')
1491
+ return value ?? {};
1492
+ try {
1493
+ return JSON.parse(value);
1494
+ }
1495
+ catch {
1496
+ return value;
1497
+ }
1498
+ }
1499
+ function firstText(...values) {
1500
+ return values.find((value) => typeof value === 'string' && value !== '') ?? '';
1501
+ }
941
1502
  function safeId(value) { return value.replace(/[^a-zA-Z0-9._-]+/gu, '_').slice(0, 96) || 'default'; }
1503
+ /** 旧驱动生成的 Host synthetic ID;没有 canonical transcript 时必须放弃 --resume。 */
1504
+ function isLegacySyntheticSessionId(value) {
1505
+ return value.startsWith('codingns4dsh-cc-session-') || value.startsWith('dsh-codingns-cc-session-');
1506
+ }
942
1507
  function parseJson(value) { try {
943
1508
  const parsed = JSON.parse(value);
944
1509
  return isRecord(parsed) ? parsed : null;