@jingyi0605/codingns 0.5.5 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (257) hide show
  1. package/bin/codingns.mjs +15 -2
  2. package/dist/public/assets/{AdaptiveButlerPage-CUyNL98E.js → AdaptiveButlerPage-Dw72U3hG.js} +3 -3
  3. package/dist/public/assets/App-CcDXqFl1.css +1 -0
  4. package/dist/public/assets/{App-BFP7LCSC.js → App-Dsf3ooXU.js} +3 -3
  5. package/dist/public/assets/{BootstrapPage-G74dX2Us.js → BootstrapPage-CE0m1qSR.js} +1 -1
  6. package/dist/public/assets/ConversationPage-8wOY7SX-.js +4 -0
  7. package/dist/public/assets/{DesktopDetachPreviewPage-IV7oEdOX.js → DesktopDetachPreviewPage-Dxarr_Wf.js} +1 -1
  8. package/dist/public/assets/DesktopWindowPage-VytPwJ4c.js +2 -0
  9. package/dist/public/assets/FileContextPanel-DwFzLsOp.js +1 -0
  10. package/dist/public/assets/GitSidebar-CH6WqTrM.js +6 -0
  11. package/dist/public/assets/MobileCreateSessionSheet-DcxKM00P.js +1 -0
  12. package/dist/public/assets/{MobileTopHeaderFrame-COTc7cRr.js → MobileTopHeaderFrame-C5rIKQT6.js} +1 -1
  13. package/dist/public/assets/MobileWorkspaceSwitcherHeader-CfUnHgv_.js +1 -0
  14. package/dist/public/assets/RelayConnectEntryPage-CgMvVZwa.js +1 -0
  15. package/dist/public/assets/ServerSettingsModal-CFul__z1.js +1 -0
  16. package/dist/public/assets/SessionIndexPage-B-tRhBXC.js +1 -0
  17. package/dist/public/assets/SettingsPage-C9LGxSQZ.js +1 -0
  18. package/dist/public/assets/TerminalManagerPanel-BbORd-ee.js +1 -0
  19. package/dist/public/assets/{TerminalPage-DpsvQQVR.js → TerminalPage-DWHv6mlu.js} +19 -19
  20. package/dist/public/assets/TerminalRuntimeFallbackModal-B29YxbQe.js +1 -0
  21. package/dist/public/assets/ToolFilesPage-Dx9cv9hu.js +1 -0
  22. package/dist/public/assets/ToolGitPage-D7H3vAia.js +1 -0
  23. package/dist/public/assets/ToolProcessesPage-PqQWxsy-.js +1 -0
  24. package/dist/public/assets/ToolsHomePage-CX05Pe_4.js +1 -0
  25. package/dist/public/assets/WorkbenchLandingPage-CchkAC75.js +1 -0
  26. package/dist/public/assets/WorkbenchLayout-pOZvEqp7.js +3 -0
  27. package/dist/public/assets/{WorkbenchModal-CbDxaCOR.js → WorkbenchModal-ColqvV6a.js} +1 -1
  28. package/dist/public/assets/WorkbenchShellRoute-C0_h4lP6.js +1 -0
  29. package/dist/public/assets/WorkbenchShellRoute-RGZpA0_J.css +1 -0
  30. package/dist/public/assets/WorkspaceDebugDetailPage-Deqy2_pO.js +1 -0
  31. package/dist/public/assets/WorkspaceDetailPage-Cvf-ZdlB.js +1 -0
  32. package/dist/public/assets/WorkspaceHomePage-Dsyvqyk1.js +1 -0
  33. package/dist/public/assets/client-runtime-manager-DROQJ9v3.js +1 -0
  34. package/dist/public/assets/{file-tree-icon-BMKuc5pw.js → file-tree-icon-Bp3Ntt7u.js} +7 -7
  35. package/dist/public/assets/index-B84Po2NA.css +1 -0
  36. package/dist/public/assets/index-C-0oeG_5.js +42 -0
  37. package/dist/public/assets/legna-code-6TqgZ4Ls.png +0 -0
  38. package/dist/public/assets/login-direct-candidate-resolver-DotM530R.js +1 -0
  39. package/dist/public/assets/model-switch-api-Bh9nYslz.js +1 -0
  40. package/dist/public/assets/{preferences-service-gv_9vGKz.js → preferences-service-BG6GKG29.js} +1 -1
  41. package/dist/public/assets/relay-entry-pmr-c42O.js +1 -0
  42. package/dist/public/assets/session-runtime-machine-YN84QBlr.js +21 -0
  43. package/dist/public/assets/{styles-BWPBZvze.css → styles-CsEMfdaS.css} +1 -1
  44. package/dist/public/assets/{terminal-runtime-meta-B9xJGY__.js → terminal-runtime-meta-8_uRZf7h.js} +1 -1
  45. package/dist/public/assets/useRegisteredDebugTemplates-DWX7LXQu.js +1 -0
  46. package/dist/public/assets/window-BVUB8gMK.js +1 -0
  47. package/dist/public/index.html +2 -2
  48. package/dist/server/config/env.d.ts +3 -0
  49. package/dist/server/config/env.js +10 -0
  50. package/dist/server/config/env.js.map +1 -1
  51. package/dist/server/modules/client/npm-global-package-service.d.ts +7 -1
  52. package/dist/server/modules/client/npm-global-package-service.js +149 -43
  53. package/dist/server/modules/client/npm-global-package-service.js.map +1 -1
  54. package/dist/server/modules/client/service-update-task-service.js +6 -2
  55. package/dist/server/modules/client/service-update-task-service.js.map +1 -1
  56. package/dist/server/modules/client/service-update-types.d.ts +2 -0
  57. package/dist/server/modules/git/git-controller.d.ts +3 -0
  58. package/dist/server/modules/git/git-controller.js +3 -0
  59. package/dist/server/modules/git/git-controller.js.map +1 -1
  60. package/dist/server/modules/git/git-read-service.js +47 -1
  61. package/dist/server/modules/git/git-read-service.js.map +1 -1
  62. package/dist/server/modules/git/git-write-service.d.ts +4 -0
  63. package/dist/server/modules/git/git-write-service.js +24 -0
  64. package/dist/server/modules/git/git-write-service.js.map +1 -1
  65. package/dist/server/modules/git/types.d.ts +1 -0
  66. package/dist/server/modules/git/workspace-repo-guard.d.ts +2 -0
  67. package/dist/server/modules/git/workspace-repo-guard.js +24 -10
  68. package/dist/server/modules/git/workspace-repo-guard.js.map +1 -1
  69. package/dist/server/modules/model-switch/cc-switch-adapter.d.ts +7 -0
  70. package/dist/server/modules/model-switch/cc-switch-adapter.js +17 -0
  71. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
  72. package/dist/server/modules/parallel-sessions/parallel-session-controller.d.ts +57 -0
  73. package/dist/server/modules/parallel-sessions/parallel-session-controller.js +77 -0
  74. package/dist/server/modules/parallel-sessions/parallel-session-controller.js.map +1 -0
  75. package/dist/server/modules/parallel-sessions/parallel-session-group-service.d.ts +88 -0
  76. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js +625 -0
  77. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js.map +1 -0
  78. package/dist/server/modules/parallel-sessions/session-isolated-workspace-service.d.ts +56 -0
  79. package/dist/server/modules/parallel-sessions/session-isolated-workspace-service.js +483 -0
  80. package/dist/server/modules/parallel-sessions/session-isolated-workspace-service.js.map +1 -0
  81. package/dist/server/modules/provider/opencode-model-options.d.ts +1 -0
  82. package/dist/server/modules/provider/opencode-model-options.js +54 -12
  83. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  84. package/dist/server/modules/provider/provider-controller.d.ts +6 -1
  85. package/dist/server/modules/provider/provider-controller.js +24 -2
  86. package/dist/server/modules/provider/provider-controller.js.map +1 -1
  87. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +2 -0
  88. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  89. package/dist/server/modules/provider/provider-discovery-helper-process.js +1 -1
  90. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  91. package/dist/server/modules/provider/provider-discovery-runtime.js +5 -1
  92. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  93. package/dist/server/modules/relay-tunnel/relay-tunnel-candidate-endpoints.d.ts +2 -0
  94. package/dist/server/modules/relay-tunnel/relay-tunnel-candidate-endpoints.js +129 -0
  95. package/dist/server/modules/relay-tunnel/relay-tunnel-candidate-endpoints.js.map +1 -0
  96. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js +12 -9
  97. package/dist/server/modules/relay-tunnel/relay-tunnel-runtime-adapter.js.map +1 -1
  98. package/dist/server/modules/relay-tunnel/relay-tunnel-service.js +1 -128
  99. package/dist/server/modules/relay-tunnel/relay-tunnel-service.js.map +1 -1
  100. package/dist/server/modules/sessions/claude-runtime-helper-client.d.ts +1 -0
  101. package/dist/server/modules/sessions/claude-runtime-helper-client.js +14 -0
  102. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  103. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +4 -0
  104. package/dist/server/modules/sessions/codex-app-server-helper-client.js +66 -45
  105. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  106. package/dist/server/modules/sessions/codex-app-server-helper-process.js +21 -2
  107. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  108. package/dist/server/modules/sessions/provider-session-delete-cli.js +2 -0
  109. package/dist/server/modules/sessions/provider-session-delete-cli.js.map +1 -1
  110. package/dist/server/modules/sessions/session-controller.d.ts +7 -0
  111. package/dist/server/modules/sessions/session-controller.js +22 -0
  112. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  113. package/dist/server/modules/sessions/session-history-service.d.ts +21 -2
  114. package/dist/server/modules/sessions/session-history-service.js +425 -29
  115. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  116. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +23 -2
  117. package/dist/server/modules/sessions/session-live-runtime-service.js +472 -74
  118. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  119. package/dist/server/modules/sessions/session-message-attachment-service.js +2 -2
  120. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
  121. package/dist/server/modules/sessions/session-provider-config-service.d.ts +66 -0
  122. package/dist/server/modules/sessions/session-provider-config-service.js +821 -0
  123. package/dist/server/modules/sessions/session-provider-config-service.js.map +1 -0
  124. package/dist/server/modules/sessions/session-provider-error-mapper.d.ts +2 -0
  125. package/dist/server/modules/sessions/session-provider-error-mapper.js +42 -0
  126. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  127. package/dist/server/modules/workbench/codex-archive-watcher.d.ts +16 -0
  128. package/dist/server/modules/workbench/codex-archive-watcher.js +50 -0
  129. package/dist/server/modules/workbench/codex-archive-watcher.js.map +1 -0
  130. package/dist/server/modules/workbench/workbench-service.d.ts +11 -2
  131. package/dist/server/modules/workbench/workbench-service.js +37 -8
  132. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  133. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +1 -1
  134. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +26 -3
  135. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  136. package/dist/server/modules/workspace/workspace-service.d.ts +3 -1
  137. package/dist/server/modules/workspace/workspace-service.js +10 -2
  138. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  139. package/dist/server/modules/worktree/worktree-base-ref-resolver.d.ts +20 -0
  140. package/dist/server/modules/worktree/worktree-base-ref-resolver.js +111 -0
  141. package/dist/server/modules/worktree/worktree-base-ref-resolver.js.map +1 -0
  142. package/dist/server/modules/worktree/worktree-cleanup-service.js +9 -3
  143. package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -1
  144. package/dist/server/modules/worktree/worktree-manager.d.ts +0 -1
  145. package/dist/server/modules/worktree/worktree-manager.js +14 -20
  146. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  147. package/dist/server/routes/git.js +1 -0
  148. package/dist/server/routes/git.js.map +1 -1
  149. package/dist/server/routes/parallel-groups.d.ts +3 -0
  150. package/dist/server/routes/parallel-groups.js +9 -0
  151. package/dist/server/routes/parallel-groups.js.map +1 -0
  152. package/dist/server/server/create-server.d.ts +8 -0
  153. package/dist/server/server/create-server.js +48 -11
  154. package/dist/server/server/create-server.js.map +1 -1
  155. package/dist/server/server/workbench-runtime-terminal-sync.d.ts +14 -0
  156. package/dist/server/server/workbench-runtime-terminal-sync.js +17 -0
  157. package/dist/server/server/workbench-runtime-terminal-sync.js.map +1 -0
  158. package/dist/server/storage/repositories/parallel-session-group-repository.d.ts +11 -0
  159. package/dist/server/storage/repositories/parallel-session-group-repository.js +131 -0
  160. package/dist/server/storage/repositories/parallel-session-group-repository.js.map +1 -0
  161. package/dist/server/storage/repositories/parallel-session-member-repository.d.ts +12 -0
  162. package/dist/server/storage/repositories/parallel-session-member-repository.js +150 -0
  163. package/dist/server/storage/repositories/parallel-session-member-repository.js.map +1 -0
  164. package/dist/server/storage/repositories/session-binding-repository.js +44 -5
  165. package/dist/server/storage/repositories/session-binding-repository.js.map +1 -1
  166. package/dist/server/storage/repositories/session-index-repository.js +6 -0
  167. package/dist/server/storage/repositories/session-index-repository.js.map +1 -1
  168. package/dist/server/storage/repositories/session-isolated-workspace-repository.d.ts +15 -0
  169. package/dist/server/storage/repositories/session-isolated-workspace-repository.js +230 -0
  170. package/dist/server/storage/repositories/session-isolated-workspace-repository.js.map +1 -0
  171. package/dist/server/storage/sqlite/client.js +19 -0
  172. package/dist/server/storage/sqlite/client.js.map +1 -1
  173. package/dist/server/storage/sqlite/schema.sql +78 -0
  174. package/dist/server/types/domain.d.ts +78 -0
  175. package/dist/server/ws/workbench-ws-hub.d.ts +3 -1
  176. package/dist/server/ws/workbench-ws-hub.js +23 -4
  177. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  178. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.d.ts +5 -2
  179. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js +40 -8
  180. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js.map +1 -1
  181. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +2 -0
  182. package/node_modules/@codingns/session-sync-core/dist/index.js +2 -0
  183. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
  184. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +10 -1
  185. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +110 -35
  186. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  187. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.d.ts +11 -0
  188. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js +105 -0
  189. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js.map +1 -0
  190. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +1 -0
  191. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +34 -0
  192. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  193. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +131 -39
  194. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  195. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.d.ts +9 -0
  196. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.js +17 -0
  197. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.js.map +1 -0
  198. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.d.ts +8 -1
  199. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js +19 -6
  200. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js.map +1 -1
  201. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +1 -0
  202. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +13 -8
  203. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  204. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +3 -0
  205. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +46 -22
  206. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -1
  207. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.d.ts +5 -1
  208. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +103 -51
  209. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  210. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +2 -1
  211. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +528 -301
  212. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  213. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +32 -8
  214. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -1
  215. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.d.ts +10 -0
  216. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.js +16 -0
  217. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.js.map +1 -0
  218. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js +196 -15
  219. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js.map +1 -1
  220. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +2 -0
  221. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
  222. package/node_modules/@codingns/session-sync-core/dist/types.js +1 -1
  223. package/node_modules/@codingns/session-sync-core/dist/types.js.map +1 -1
  224. package/package.json +1 -1
  225. package/dist/public/assets/App-DUAg5urj.css +0 -1
  226. package/dist/public/assets/ConversationPage-Bz0_tvvM.js +0 -2
  227. package/dist/public/assets/DesktopWindowPage-BBmHyRg5.js +0 -2
  228. package/dist/public/assets/FileContextPanel--FVTxDrq.js +0 -1
  229. package/dist/public/assets/GitSidebar-DAiSi9oc.js +0 -6
  230. package/dist/public/assets/MobileCreateSessionSheet-DqVwz_Hp.js +0 -1
  231. package/dist/public/assets/MobileSheet-D1lMrcvD.js +0 -1
  232. package/dist/public/assets/MobileWorkspaceSwitcherHeader-DJPV9ym2.js +0 -1
  233. package/dist/public/assets/RelayConnectEntryPage-dSwU8VzK.js +0 -1
  234. package/dist/public/assets/ServerSettingsModal-B34ms3ze.js +0 -1
  235. package/dist/public/assets/SessionIndexPage-D3tG1gmM.js +0 -1
  236. package/dist/public/assets/SettingsPage-B3-6-5GL.js +0 -1
  237. package/dist/public/assets/TerminalManagerPanel-DhuTEdzV.js +0 -1
  238. package/dist/public/assets/TerminalRuntimeFallbackModal-CNzOt5v5.js +0 -1
  239. package/dist/public/assets/ToolFilesPage-BX9QDi9Y.js +0 -1
  240. package/dist/public/assets/ToolGitPage-4VtFox3p.js +0 -1
  241. package/dist/public/assets/ToolProcessesPage-DZJC6Qnt.js +0 -1
  242. package/dist/public/assets/ToolsHomePage-D7JbrAWv.js +0 -1
  243. package/dist/public/assets/WorkbenchLandingPage-C0yqnzqh.js +0 -1
  244. package/dist/public/assets/WorkbenchLayout-Brlj8K3i.js +0 -3
  245. package/dist/public/assets/WorkbenchShellRoute-BMcnFadA.css +0 -1
  246. package/dist/public/assets/WorkbenchShellRoute-puGpdDFY.js +0 -1
  247. package/dist/public/assets/WorkspaceDebugDetailPage-fTGweC9N.js +0 -1
  248. package/dist/public/assets/WorkspaceDetailPage-BtaIzSDB.js +0 -1
  249. package/dist/public/assets/WorkspaceHomePage-CUmmYDrM.js +0 -1
  250. package/dist/public/assets/client-runtime-manager-RHFa_iWo.js +0 -1
  251. package/dist/public/assets/default-session-permission-mode-Cu5SreTG.js +0 -1
  252. package/dist/public/assets/index-Cq3ue0za.css +0 -1
  253. package/dist/public/assets/index-DEbFT-Aq.js +0 -42
  254. package/dist/public/assets/session-runtime-machine-Bfnxkk9B.js +0 -17
  255. package/dist/public/assets/useRegisteredDebugTemplates-CDfl54Wt.js +0 -1
  256. package/dist/public/assets/window-BWqRixxq.js +0 -1
  257. /package/dist/public/assets/{styles-CSUx5LGe.js → styles-DRVvx_kv.js} +0 -0
@@ -1,7 +1,7 @@
1
- import { existsSync, readdirSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { performance } from "node:perf_hooks";
4
- import { ClaudeRuntimeAdapter, CodexRuntimeAdapter, GeminiRuntimeAdapter, KimiRuntimeAdapter, OpenCodeRuntimeAdapter, ProviderRuntimeService } from "@codingns/session-sync-core";
4
+ import { ClaudeRuntimeAdapter, CodexRuntimeAdapter, GeminiRuntimeAdapter, KimiRuntimeAdapter, LegnaRuntimeAdapter, OpenCodeRuntimeAdapter, ProviderRuntimeService } from "@codingns/session-sync-core";
5
5
  import { AppError, isAppError } from "../../shared/errors/app-error.js";
6
6
  import { createId } from "../../shared/utils/id.js";
7
7
  import { isPerfDebugEnabled, logPerformance } from "../../shared/utils/perf-log.js";
@@ -9,11 +9,14 @@ import { logPermissionDebug } from "../../shared/utils/permission-debug-log.js";
9
9
  import { nowIso } from "../../shared/utils/time.js";
10
10
  import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
11
11
  import { SessionPermissionRequestService } from "./session-permission-request-service.js";
12
- import { mapSessionProviderError } from "./session-provider-error-mapper.js";
12
+ import { appendSessionProviderErrorContext, mapSessionProviderError } from "./session-provider-error-mapper.js";
13
13
  import { ClaudeRuntimeHelperAdapter } from "./claude-runtime-helper-client.js";
14
14
  import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
15
+ const OPENCODE_ORDER_DEBUG_ENABLED = /^(1|true|yes)$/i.test(process.env.CODINGNS_OPENCODE_ORDER_DEBUG?.trim() ?? "");
15
16
  const RUNTIME_START_BINDING_WAIT_TIMEOUT_MS = 10_000;
16
17
  const START_BINDING_POLL_INTERVAL_MS = 50;
18
+ const EXTERNAL_RUNTIME_INTERRUPT_SUPPRESSION_MS = 30_000;
19
+ const EXTERNAL_RUNTIME_SNAPSHOT_MAX_AGE_MS = 90_000;
17
20
  export class SessionLiveRuntimeService {
18
21
  sessionHistoryService;
19
22
  sessionMessageAttachmentService;
@@ -25,12 +28,14 @@ export class SessionLiveRuntimeService {
25
28
  sessionIndexRepository;
26
29
  sessionStateRepository;
27
30
  sessionStatusSnapshotRepository;
31
+ sessionProviderConfigService;
28
32
  config;
29
33
  providerRuntimeService;
30
34
  sessionActivityAuthorityService;
31
35
  sessionPermissionRequestService;
32
36
  runtimeAdapterDisposables;
33
37
  externalRuntimeSnapshots = new Map();
38
+ externalRuntimeInterruptSuppressions = new Map();
34
39
  runtimeListeners = new Map();
35
40
  terminalStateListeners = new Set();
36
41
  deadRuntimeReconciliationSessions = new Set();
@@ -39,7 +44,7 @@ export class SessionLiveRuntimeService {
39
44
  queueDispatchSessions = new Set();
40
45
  queueRetryTimers = new Map();
41
46
  pendingSendDebugTracesBySessionId = new Map();
42
- constructor(sessionHistoryService, sessionMessageAttachmentService, workspaceService, sessionChangedFileService, sessionBindingRepository, authUserRepository, sessionSendQueueRepository, sessionIndexRepository, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService()) {
47
+ constructor(sessionHistoryService, sessionMessageAttachmentService, workspaceService, sessionChangedFileService, sessionBindingRepository, authUserRepository, sessionSendQueueRepository, sessionIndexRepository, sessionStateRepository, sessionStatusSnapshotRepository, sessionProviderConfigService, config, sessionActivityAuthorityService = new SessionActivityAuthorityService()) {
43
48
  this.sessionHistoryService = sessionHistoryService;
44
49
  this.sessionMessageAttachmentService = sessionMessageAttachmentService;
45
50
  this.workspaceService = workspaceService;
@@ -50,6 +55,7 @@ export class SessionLiveRuntimeService {
50
55
  this.sessionIndexRepository = sessionIndexRepository;
51
56
  this.sessionStateRepository = sessionStateRepository;
52
57
  this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
58
+ this.sessionProviderConfigService = sessionProviderConfigService;
53
59
  this.config = config;
54
60
  this.sessionActivityAuthorityService = sessionActivityAuthorityService;
55
61
  this.sessionPermissionRequestService = new SessionPermissionRequestService(sessionHistoryService, sessionBindingRepository, authUserRepository, workspaceService, config, async (envelope) => {
@@ -76,7 +82,19 @@ export class SessionLiveRuntimeService {
76
82
  });
77
83
  try {
78
84
  const capabilities = this.sessionHistoryService.getProviderCapabilitiesSnapshot(input.provider);
79
- this.ensurePendingSessionBinding(sessionId, workspace.id, input.provider);
85
+ const providerBinding = this.sessionProviderConfigService.prepareSessionBinding({
86
+ sessionId,
87
+ provider: input.provider,
88
+ providerConfigMode: input.providerConfigMode,
89
+ providerPresetId: input.providerPresetId ?? null
90
+ });
91
+ const providerLaunchContext = this.sessionProviderConfigService.resolveLaunchContext({
92
+ provider: input.provider,
93
+ providerConfigMode: providerBinding.providerConfigMode,
94
+ providerPresetId: providerBinding.providerPresetId,
95
+ runtimeHomeDir: providerBinding.runtimeHomeDir
96
+ });
97
+ this.ensurePendingSessionBinding(sessionId, workspace.id, input.provider, providerBinding);
80
98
  const persistedAttachments = this.persistMessageAttachments(sessionId, input.clientRequestId, input.runtimeOptions?.attachments ?? []);
81
99
  const providerPrompt = this.sessionMessageAttachmentService.buildProviderPrompt(input.provider, input.content, persistedAttachments.runtimeAttachments);
82
100
  const providerInstructionFilePath = resolveRuntimeInstructionFilePath(input.provider, workspace.path, input.runtimeOptions?.providerInstructionFilePath ?? null);
@@ -90,6 +108,8 @@ export class SessionLiveRuntimeService {
90
108
  provider: input.provider,
91
109
  providerSessionId: null,
92
110
  rawStoreRef: null,
111
+ runtimeHomeDir: providerLaunchContext.runtimeHomeDir,
112
+ runtimeEnv: providerLaunchContext.runtimeEnv,
93
113
  sequenceBase: 1,
94
114
  options: {
95
115
  content: input.content,
@@ -101,7 +121,12 @@ export class SessionLiveRuntimeService {
101
121
  providerInstructionFilePath,
102
122
  attachments: persistedAttachments.runtimeAttachments
103
123
  }
104
- }, "start");
124
+ }, "start", {
125
+ provider: input.provider,
126
+ providerConfigMode: providerBinding.providerConfigMode,
127
+ providerPresetId: providerBinding.providerPresetId,
128
+ runtimeHomeDir: providerBinding.runtimeHomeDir
129
+ });
105
130
  this.logSendDebugStep(debugTrace, "launch_runtime", launchRuntimeStartedAtMs, {
106
131
  userId: input.userId
107
132
  });
@@ -200,6 +225,7 @@ export class SessionLiveRuntimeService {
200
225
  }
201
226
  async enqueueLiveMessage(input) {
202
227
  const session = await this.resolveQueueDispatchSession(input.sessionId, input.userId);
228
+ this.resolveEffectiveSessionProviderBinding(session, input);
203
229
  this.persistMessageAttachments(input.sessionId, input.clientRequestId, input.runtimeOptions?.attachments ?? []);
204
230
  const timestamp = nowIso();
205
231
  const queueItem = {
@@ -435,7 +461,7 @@ export class SessionLiveRuntimeService {
435
461
  async getSessionRuntime(sessionId, userId) {
436
462
  const runtimeSessionId = this.resolveRuntimeSessionId(sessionId);
437
463
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(runtimeSessionId);
438
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
464
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
439
465
  const runtimeHasActiveRun = runtimeSnapshot ? isActiveRuntimeState(runtimeSnapshot.runningState) : false;
440
466
  const externalHasActiveRun = externalRuntimeSnapshot
441
467
  ? isActiveRuntimeState(externalRuntimeSnapshot.runningState)
@@ -485,7 +511,7 @@ export class SessionLiveRuntimeService {
485
511
  runningState: resolution.runningState,
486
512
  hasActiveRun: externalHasActiveRun,
487
513
  canAttach: false,
488
- canInterrupt: false,
514
+ canInterrupt: externalHasActiveRun,
489
515
  inRunInputMode: capabilities.inRunInputMode,
490
516
  activityResolutionSource: resolution.activityResolutionSource,
491
517
  activityConfidence: resolution.activityConfidence,
@@ -528,32 +554,59 @@ export class SessionLiveRuntimeService {
528
554
  if (runtimeSnapshot) {
529
555
  return createRuntimeActivityObservation(runtimeSessionId, runtimeSnapshot);
530
556
  }
531
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
557
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
532
558
  if (externalRuntimeSnapshot) {
533
559
  return createExternalRuntimeActivityObservation(runtimeSessionId, externalRuntimeSnapshot);
534
560
  }
535
561
  return null;
536
562
  }
537
563
  async interruptSession(sessionId, userId) {
538
- this.sessionHistoryService.getSession(sessionId, userId);
564
+ const session = this.sessionHistoryService.getSession(sessionId, userId);
539
565
  const runtimeSessionId = this.resolveRuntimeSessionId(sessionId);
540
566
  const runtime = this.getLiveRuntimeSnapshot(runtimeSessionId);
541
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
567
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
542
568
  if (!runtime || (runtime.runningState !== "running" && runtime.runningState !== "starting")) {
569
+ if (runtime && !isActiveRuntimeState(runtime.runningState)) {
570
+ await this.reconcileTerminalRuntimeSnapshot(sessionId, runtime);
571
+ return {
572
+ sessionId,
573
+ interrupted: true,
574
+ detail: runtime.detail ?? "当前会话已结束,已自动同步状态"
575
+ };
576
+ }
543
577
  if (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
544
- throw new AppError({
545
- statusCode: 409,
546
- errorCode: "CAPABILITY_NOT_SUPPORTED",
547
- detail: "当前 Claude 外部运行仍在进行,但现有链路不支持中断",
548
- field: "sessionId"
549
- });
578
+ const refreshedSession = await Promise.resolve(this.sessionHistoryService.refreshRuntimeFallbackSession(sessionId, userId)).catch(() => null);
579
+ if (refreshedSession && !isPendingSessionRunningState(refreshedSession.runningState)) {
580
+ await this.forceInterruptInactiveSession(sessionId);
581
+ return {
582
+ sessionId,
583
+ interrupted: true,
584
+ detail: "当前会话已停止,已自动同步状态"
585
+ };
586
+ }
587
+ await this.forceInterruptExternalSession(sessionId);
588
+ return {
589
+ sessionId,
590
+ interrupted: true,
591
+ detail: "Claude 外部运行当前无法直接中断,已强制清理本地运行状态"
592
+ };
550
593
  }
551
- throw new AppError({
552
- statusCode: 409,
553
- errorCode: "SESSION_NOT_RUNNING",
554
- detail: "当前会话不在运行中,无法中断",
555
- field: "sessionId"
556
- });
594
+ const refreshedSession = isPendingSessionRunningState(session.runningState)
595
+ ? await Promise.resolve(this.sessionHistoryService.refreshRuntimeFallbackSession(sessionId, userId)).catch(() => null)
596
+ : session;
597
+ if (refreshedSession && !isPendingSessionRunningState(refreshedSession.runningState)) {
598
+ return {
599
+ sessionId,
600
+ interrupted: true,
601
+ detail: "当前会话已停止,已自动同步状态"
602
+ };
603
+ }
604
+ await this.forceInterruptInactiveSession(sessionId);
605
+ return {
606
+ sessionId,
607
+ interrupted: true,
608
+ detail: "当前会话已停止,已自动修正残留运行状态"
609
+ };
557
610
  }
558
611
  const interrupted = await this.providerRuntimeService.interrupt(runtimeSessionId).catch((error) => {
559
612
  if (error instanceof Error && error.message === "INTERRUPT_NOT_SUPPORTED") {
@@ -589,7 +642,7 @@ export class SessionLiveRuntimeService {
589
642
  subscribeRuntime(sessionId, onEnvelope) {
590
643
  const runtimeSessionId = this.resolveRuntimeSessionId(sessionId);
591
644
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(runtimeSessionId);
592
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
645
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
593
646
  const initialActivityEnvelope = this.buildSessionActivityEnvelope(sessionId, runtimeSessionId);
594
647
  if (runtimeSnapshot) {
595
648
  void onEnvelope({
@@ -619,6 +672,16 @@ export class SessionLiveRuntimeService {
619
672
  if (!envelope) {
620
673
  return;
621
674
  }
675
+ logOpenCodeOrderEnvelopeDebug("runtime.envelope.forward", {
676
+ sessionId,
677
+ runtimeSessionId,
678
+ provider: event.provider ?? null,
679
+ eventType: event.type ?? null,
680
+ envelopeType: envelope.type,
681
+ message: "message" in envelope && envelope.message
682
+ ? summarizeOpenCodeOrderMessage(envelope.message)
683
+ : null
684
+ });
622
685
  await onEnvelope(envelope);
623
686
  });
624
687
  const externalSubscription = this.subscribeExternalRuntime(runtimeSessionId, async (envelope) => {
@@ -767,13 +830,13 @@ export class SessionLiveRuntimeService {
767
830
  sessionId
768
831
  };
769
832
  }
770
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId) ?? null;
833
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
771
834
  if (externalRuntimeSnapshot) {
772
835
  const resolution = this.sessionActivityAuthorityService.observe(createExternalRuntimeActivityObservation(runtimeSessionId, externalRuntimeSnapshot));
773
836
  return {
774
837
  ...this.mapResolutionToActivityEnvelope(resolution, {
775
838
  hasActiveRun: isActiveRuntimeState(externalRuntimeSnapshot.runningState),
776
- canInterrupt: false
839
+ canInterrupt: isActiveRuntimeState(externalRuntimeSnapshot.runningState)
777
840
  }),
778
841
  sessionId
779
842
  };
@@ -792,7 +855,7 @@ export class SessionLiveRuntimeService {
792
855
  }
793
856
  resolveRuntimeSessionId(sessionId) {
794
857
  if (this.providerRuntimeService.getSnapshot(sessionId)
795
- || this.externalRuntimeSnapshots.has(sessionId)) {
858
+ || this.getFreshExternalRuntimeSnapshot(sessionId)) {
796
859
  return sessionId;
797
860
  }
798
861
  const listSnapshots = "listSnapshots" in this.providerRuntimeService
@@ -898,6 +961,12 @@ export class SessionLiveRuntimeService {
898
961
  };
899
962
  }
900
963
  async applyExternalRuntimeUpdate(input) {
964
+ if (input.runningState === "running" && this.shouldIgnoreInterruptedExternalRuntime(input.sessionId)) {
965
+ return;
966
+ }
967
+ if (input.runningState !== "running") {
968
+ this.clearExternalRuntimeInterruptSuppression(input.sessionId);
969
+ }
901
970
  const userIds = this.authUserRepository.listIds();
902
971
  if (userIds.length === 0) {
903
972
  return;
@@ -991,13 +1060,14 @@ export class SessionLiveRuntimeService {
991
1060
  void this.dispatchNextQueuedMessage(input.sessionId);
992
1061
  }
993
1062
  }
994
- async startRuntimeRun(request, userId, mode) {
1063
+ async startRuntimeRun(request, userId, mode, providerBinding) {
995
1064
  this.runtimeMessageSeenSessions.delete(request.sessionId);
996
1065
  this.runtimeHistoryFallbackSentSessions.delete(request.sessionId);
1066
+ this.clearExternalRuntimeInterruptSuppression(request.sessionId);
997
1067
  if (request.provider === "claude-code") {
998
1068
  this.clearExternalRuntimeSnapshot(request.sessionId);
999
1069
  }
1000
- const handle = await this.launchRuntimeRun(request, mode);
1070
+ const handle = await this.launchRuntimeRun(request, mode, providerBinding);
1001
1071
  const snapshot = handle.getSnapshot();
1002
1072
  const currentState = this.sessionStateRepository.findBySessionAndUser(request.sessionId, userId);
1003
1073
  this.attachRuntimePersistence(handle, request.sessionId, request.workspaceId, userId);
@@ -1029,9 +1099,43 @@ export class SessionLiveRuntimeService {
1029
1099
  const capabilities = await this.sessionHistoryService.getSessionCapabilities(input.sessionId);
1030
1100
  const workspace = this.workspaceService.getWorkspaceOrThrow(session.workspaceId);
1031
1101
  const runtimeMode = shouldStartNativeSessionOnFirstMessage(session);
1102
+ const existingBinding = this.getSessionBindingOrThrow(session.sessionId);
1103
+ const resolvedProviderBinding = this.resolveRequestedSessionProviderBinding(session, input, existingBinding);
1104
+ const runtimeSessionId = this.resolveRuntimeSessionId(input.sessionId);
1105
+ const activeRun = this.getLiveRuntimeSnapshot(runtimeSessionId);
1106
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(runtimeSessionId);
1107
+ const hasActiveRun = Boolean(activeRun && isActiveRuntimeState(activeRun.runningState));
1108
+ if (hasActiveRun && activeRun?.provider === "claude-code") {
1109
+ this.clearExternalRuntimeSnapshot(runtimeSessionId);
1110
+ }
1111
+ if (hasActiveRun
1112
+ || (!activeRun &&
1113
+ session.provider === "claude-code" &&
1114
+ externalRuntimeSnapshot &&
1115
+ isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
1116
+ this.assertProviderBindingStableDuringActiveRun(existingBinding, resolvedProviderBinding);
1117
+ }
1118
+ if (!activeRun &&
1119
+ session.provider === "claude-code" &&
1120
+ externalRuntimeSnapshot &&
1121
+ isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
1122
+ throw new AppError({
1123
+ statusCode: 409,
1124
+ errorCode: "SESSION_EXTERNAL_RUN_ACTIVE",
1125
+ detail: "当前 Claude 外部会话仍在运行,不能直接追加;请加入队列或等待当前轮结束",
1126
+ field: "sessionId"
1127
+ });
1128
+ }
1129
+ const providerBinding = hasActiveRun
1130
+ ? existingBinding
1131
+ : this.persistResolvedSessionProviderBinding(existingBinding, resolvedProviderBinding);
1132
+ const providerLaunchContext = this.sessionProviderConfigService.resolveLaunchContext(providerBinding);
1032
1133
  const syntheticForkRawStoreRef = runtimeMode === "start" && shouldResumeCodexSyntheticForkSession(session)
1033
1134
  ? session.rawStoreRef
1034
1135
  : null;
1136
+ const runtimeRawStoreRef = runtimeMode === "start"
1137
+ ? syntheticForkRawStoreRef
1138
+ : await this.resolveCodexRuntimeRequestRawStoreRef(session, providerBinding);
1035
1139
  const nextUserSequence = runtimeMode === "start"
1036
1140
  ? 1
1037
1141
  : await this.resolveNextUserSequence(input.sessionId, session.messageCount);
@@ -1046,7 +1150,9 @@ export class SessionLiveRuntimeService {
1046
1150
  workspacePath: workspace.path,
1047
1151
  provider: session.provider,
1048
1152
  providerSessionId: runtimeMode === "start" ? null : session.providerSessionId,
1049
- rawStoreRef: runtimeMode === "start" ? syntheticForkRawStoreRef : session.rawStoreRef,
1153
+ rawStoreRef: runtimeRawStoreRef,
1154
+ runtimeHomeDir: providerLaunchContext.runtimeHomeDir,
1155
+ runtimeEnv: providerLaunchContext.runtimeEnv,
1050
1156
  sequenceBase: nextUserSequence,
1051
1157
  options: {
1052
1158
  content: input.content,
@@ -1059,26 +1165,7 @@ export class SessionLiveRuntimeService {
1059
1165
  attachments: resolvedAttachments.runtimeAttachments
1060
1166
  }
1061
1167
  };
1062
- const runtimeSessionId = this.resolveRuntimeSessionId(input.sessionId);
1063
- const activeRun = this.getLiveRuntimeSnapshot(runtimeSessionId);
1064
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(runtimeSessionId);
1065
- if (activeRun &&
1066
- activeRun.provider === "claude-code" &&
1067
- isActiveRuntimeState(activeRun.runningState)) {
1068
- this.clearExternalRuntimeSnapshot(runtimeSessionId);
1069
- }
1070
- if (!activeRun &&
1071
- session.provider === "claude-code" &&
1072
- externalRuntimeSnapshot &&
1073
- isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
1074
- throw new AppError({
1075
- statusCode: 409,
1076
- errorCode: "SESSION_EXTERNAL_RUN_ACTIVE",
1077
- detail: "当前 Claude 外部会话仍在运行,不能直接追加;请加入队列或等待当前轮结束",
1078
- field: "sessionId"
1079
- });
1080
- }
1081
- if (activeRun && isActiveRuntimeState(activeRun.runningState)) {
1168
+ if (hasActiveRun) {
1082
1169
  const submitStartedAtMs = performance.now();
1083
1170
  try {
1084
1171
  await this.providerRuntimeService.submitToActiveRun(runtimeSessionId, runtimeRequest.options);
@@ -1088,13 +1175,23 @@ export class SessionLiveRuntimeService {
1088
1175
  });
1089
1176
  }
1090
1177
  catch (error) {
1091
- const mapped = mapSessionProviderError(error);
1178
+ const mapped = appendSessionProviderErrorContext(mapSessionProviderError(error), this.sessionProviderConfigService.describeBinding({
1179
+ provider: session.provider,
1180
+ providerConfigMode: providerBinding.providerConfigMode,
1181
+ providerPresetId: providerBinding.providerPresetId,
1182
+ runtimeHomeDir: providerBinding.runtimeHomeDir
1183
+ }));
1092
1184
  // 运行时句柄还没来得及收尾时,steer 可能会撞上 provider 已终态。
1093
1185
  // 这里直接失败只会把一条正常消息变成偶发 409,属于纯粹的坏味道。
1094
1186
  if (mapped.errorCode === "SESSION_NOT_RUNNING") {
1095
1187
  await this.providerRuntimeService.abandonRun(runtimeSessionId);
1096
1188
  const restartRuntimeStartedAtMs = performance.now();
1097
- await this.startRuntimeRun(runtimeRequest, input.userId, runtimeMode);
1189
+ await this.startRuntimeRun(runtimeRequest, input.userId, runtimeMode, {
1190
+ provider: session.provider,
1191
+ providerConfigMode: providerBinding.providerConfigMode,
1192
+ providerPresetId: providerBinding.providerPresetId,
1193
+ runtimeHomeDir: providerBinding.runtimeHomeDir
1194
+ });
1098
1195
  this.logSendDebugStep(debugTrace, "restart_runtime_after_stale_active_run", restartRuntimeStartedAtMs, {
1099
1196
  runtimeMode
1100
1197
  });
@@ -1106,7 +1203,12 @@ export class SessionLiveRuntimeService {
1106
1203
  }
1107
1204
  else {
1108
1205
  const startRuntimeStartedAtMs = performance.now();
1109
- await this.startRuntimeRun(runtimeRequest, input.userId, runtimeMode);
1206
+ await this.startRuntimeRun(runtimeRequest, input.userId, runtimeMode, {
1207
+ provider: session.provider,
1208
+ providerConfigMode: providerBinding.providerConfigMode,
1209
+ providerPresetId: providerBinding.providerPresetId,
1210
+ runtimeHomeDir: providerBinding.runtimeHomeDir
1211
+ });
1110
1212
  this.logSendDebugStep(debugTrace, "start_runtime_run", startRuntimeStartedAtMs, {
1111
1213
  runtimeMode
1112
1214
  });
@@ -1156,7 +1258,7 @@ export class SessionLiveRuntimeService {
1156
1258
  this.queueDispatchSessions.add(sessionId);
1157
1259
  try {
1158
1260
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(sessionId);
1159
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId);
1261
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(sessionId);
1160
1262
  if ((runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState))
1161
1263
  || (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
1162
1264
  return;
@@ -1222,7 +1324,7 @@ export class SessionLiveRuntimeService {
1222
1324
  if (runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState)) {
1223
1325
  return true;
1224
1326
  }
1225
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(session.sessionId);
1327
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(session.sessionId);
1226
1328
  if (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
1227
1329
  return true;
1228
1330
  }
@@ -1259,7 +1361,7 @@ export class SessionLiveRuntimeService {
1259
1361
  return session;
1260
1362
  }
1261
1363
  const runtimeSnapshot = this.getLiveRuntimeSnapshot(sessionId);
1262
- const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId);
1364
+ const externalRuntimeSnapshot = this.getFreshExternalRuntimeSnapshot(sessionId);
1263
1365
  if ((runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState))
1264
1366
  || (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
1265
1367
  return session;
@@ -1268,14 +1370,18 @@ export class SessionLiveRuntimeService {
1268
1370
  .then((refreshedSession) => refreshedSession ?? session)
1269
1371
  .catch(() => session);
1270
1372
  }
1271
- async launchRuntimeRun(request, mode) {
1373
+ async launchRuntimeRun(request, mode, providerBinding) {
1272
1374
  try {
1273
1375
  return await (mode === "start"
1274
1376
  ? this.providerRuntimeService.startSession(request)
1275
1377
  : this.providerRuntimeService.continueSession(request));
1276
1378
  }
1277
1379
  catch (error) {
1278
- throw mapSessionProviderError(error);
1380
+ const mapped = mapSessionProviderError(error);
1381
+ if (!providerBinding) {
1382
+ throw mapped;
1383
+ }
1384
+ throw appendSessionProviderErrorContext(mapped, this.sessionProviderConfigService.describeBinding(providerBinding));
1279
1385
  }
1280
1386
  }
1281
1387
  attachRuntimePersistence(handle, sessionId, workspaceId, userId) {
@@ -1353,8 +1459,109 @@ export class SessionLiveRuntimeService {
1353
1459
  });
1354
1460
  this.sessionActivityAuthorityService.observe(createRuntimeActivityObservation(input.sessionId, input.snapshot));
1355
1461
  }
1356
- ensurePendingSessionBinding(sessionId, workspaceId, provider) {
1357
- this.sessionHistoryService.persistSessionBinding(sessionId, workspaceId, this.buildBindingSnapshot(sessionId, provider, null, null));
1462
+ ensurePendingSessionBinding(sessionId, workspaceId, provider, providerBinding) {
1463
+ const snapshot = this.buildBindingSnapshot(sessionId, provider, null, null);
1464
+ const timestamp = nowIso();
1465
+ const existingBinding = this.sessionBindingRepository.findBySessionId(sessionId);
1466
+ this.sessionBindingRepository.upsert({
1467
+ sessionId,
1468
+ workspaceId,
1469
+ provider: snapshot.provider,
1470
+ providerSessionId: snapshot.providerSessionId,
1471
+ rawStoreRef: snapshot.rawStoreRef,
1472
+ providerConfigMode: providerBinding?.providerConfigMode ?? existingBinding?.providerConfigMode ?? "global-default",
1473
+ providerPresetId: providerBinding?.providerPresetId ?? existingBinding?.providerPresetId ?? null,
1474
+ runtimeHomeDir: providerBinding?.runtimeHomeDir ?? existingBinding?.runtimeHomeDir ?? null,
1475
+ createdAt: existingBinding?.createdAt ?? timestamp,
1476
+ updatedAt: timestamp
1477
+ });
1478
+ }
1479
+ resolveEffectiveSessionProviderBinding(session, input) {
1480
+ const existingBinding = this.getSessionBindingOrThrow(session.sessionId);
1481
+ const providerBinding = this.resolveRequestedSessionProviderBinding(session, input, existingBinding);
1482
+ return this.persistResolvedSessionProviderBinding(existingBinding, providerBinding);
1483
+ }
1484
+ getSessionBindingOrThrow(sessionId) {
1485
+ const existingBinding = this.sessionBindingRepository.findBySessionId(sessionId);
1486
+ if (!existingBinding) {
1487
+ throw new AppError({
1488
+ statusCode: 404,
1489
+ errorCode: "SESSION_NOT_FOUND",
1490
+ detail: "session 不存在"
1491
+ });
1492
+ }
1493
+ return existingBinding;
1494
+ }
1495
+ resolveRequestedSessionProviderBinding(session, input, existingBinding) {
1496
+ return this.sessionProviderConfigService.resolveSessionBinding({
1497
+ sessionId: session.sessionId,
1498
+ provider: session.provider,
1499
+ existingBinding,
1500
+ providerConfigMode: input.providerConfigMode,
1501
+ providerPresetId: input.providerPresetId ?? null
1502
+ });
1503
+ }
1504
+ persistResolvedSessionProviderBinding(existingBinding, providerBinding) {
1505
+ if (!this.hasSessionProviderBindingChanged(existingBinding, providerBinding)) {
1506
+ return existingBinding;
1507
+ }
1508
+ const nextBinding = {
1509
+ ...existingBinding,
1510
+ providerConfigMode: providerBinding.providerConfigMode,
1511
+ providerPresetId: providerBinding.providerPresetId,
1512
+ runtimeHomeDir: providerBinding.runtimeHomeDir,
1513
+ updatedAt: nowIso()
1514
+ };
1515
+ this.sessionBindingRepository.upsert(nextBinding);
1516
+ return nextBinding;
1517
+ }
1518
+ assertProviderBindingStableDuringActiveRun(existingBinding, requestedBinding) {
1519
+ if (!this.hasSessionProviderBindingChanged(existingBinding, requestedBinding)) {
1520
+ return;
1521
+ }
1522
+ throw new AppError({
1523
+ statusCode: 409,
1524
+ errorCode: "SESSION_PROVIDER_CONFIG_CHANGE_REQUIRES_NEW_RUN",
1525
+ detail: "当前会话仍在执行,不能中途切换模型配置文件;请等本轮结束后再发起新一轮",
1526
+ field: "sessionId"
1527
+ });
1528
+ }
1529
+ hasSessionProviderBindingChanged(currentBinding, nextBinding) {
1530
+ return (currentBinding.providerConfigMode !== nextBinding.providerConfigMode
1531
+ || normalizeOptionalBindingValue(currentBinding.providerPresetId)
1532
+ !== normalizeOptionalBindingValue(nextBinding.providerPresetId)
1533
+ || normalizeOptionalBindingValue(currentBinding.runtimeHomeDir)
1534
+ !== normalizeOptionalBindingValue(nextBinding.runtimeHomeDir));
1535
+ }
1536
+ async resolveCodexRuntimeRequestRawStoreRef(session, providerBinding) {
1537
+ if (session.provider !== "codex") {
1538
+ return session.rawStoreRef;
1539
+ }
1540
+ const currentRawStoreRef = session.rawStoreRef?.trim() || null;
1541
+ if (currentRawStoreRef && existsSync(currentRawStoreRef)) {
1542
+ return currentRawStoreRef;
1543
+ }
1544
+ const messages = await Promise.resolve(this.sessionHistoryService.readAllTextHistoryMessages(session.sessionId)).catch(() => []);
1545
+ if (!Array.isArray(messages) || messages.length === 0) {
1546
+ return currentRawStoreRef;
1547
+ }
1548
+ const baseDir = providerBinding.runtimeHomeDir?.trim()
1549
+ || path.resolve(path.dirname(this.config.databasePath), "runtime", "codex-resume-history");
1550
+ const syntheticDir = path.join(baseDir, ".codingns-synthetic-resume");
1551
+ const syntheticFilePath = path.join(syntheticDir, `${session.sessionId}.jsonl`);
1552
+ const serialized = messages
1553
+ .map((message) => JSON.stringify({
1554
+ timestamp: message.timestamp,
1555
+ type: "event_msg",
1556
+ payload: {
1557
+ type: message.role === "assistant" ? "agent_message" : "user_message",
1558
+ message: message.content
1559
+ }
1560
+ }))
1561
+ .join("\n");
1562
+ mkdirSync(syntheticDir, { recursive: true });
1563
+ writeFileSync(syntheticFilePath, `${serialized}\n`, "utf8");
1564
+ return syntheticFilePath;
1358
1565
  }
1359
1566
  buildBindingSnapshot(sessionId, provider, providerSessionId, rawStoreRef) {
1360
1567
  const pendingValue = `pending://${provider}/${sessionId}`;
@@ -1482,6 +1689,112 @@ export class SessionLiveRuntimeService {
1482
1689
  await listener(event);
1483
1690
  }
1484
1691
  }
1692
+ async reconcileTerminalRuntimeSnapshot(sessionId, runtime) {
1693
+ const timestamp = runtime.lastEventAt ?? runtime.completedAt ?? runtime.startedAt;
1694
+ const runningState = toStoredRunningState(runtime.runningState);
1695
+ const currentSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1696
+ for (const userId of this.authUserRepository.listIds()) {
1697
+ const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1698
+ if (current?.lastEventAt
1699
+ && current.lastEventAt.localeCompare(timestamp) > 0
1700
+ && isTerminalSessionRunningState(current.runningState)) {
1701
+ continue;
1702
+ }
1703
+ this.sessionStateRepository.upsert({
1704
+ sessionId,
1705
+ userId,
1706
+ runningState,
1707
+ activitySource: "runtime",
1708
+ favorite: current?.favorite ?? false,
1709
+ lastEventAt: timestamp,
1710
+ completedAt: isTerminalSessionRunningState(runningState)
1711
+ ? (runtime.completedAt ?? timestamp)
1712
+ : current?.completedAt ?? null,
1713
+ lastSeenAt: current?.lastSeenAt ?? null,
1714
+ updatedAt: nowIso()
1715
+ });
1716
+ }
1717
+ this.upsertSnapshot(sessionId, {
1718
+ syncStatus: runningState === "failed" ? "error" : "idle",
1719
+ syncCursor: currentSnapshot?.syncCursor ?? null,
1720
+ lastSyncAt: timestamp,
1721
+ lastErrorCode: runningState === "failed" ? runtime.errorCode ?? null : null,
1722
+ lastErrorDetail: runningState === "failed" ? runtime.detail ?? null : null,
1723
+ resumedAt: currentSnapshot?.resumedAt ?? null
1724
+ });
1725
+ this.sessionActivityAuthorityService.observe(createRuntimeActivityObservation(sessionId, runtime));
1726
+ await this.emitExternalRuntimeEnvelope({
1727
+ type: "session.runtime_status",
1728
+ sessionId,
1729
+ status: runtime.runningState,
1730
+ detail: runtime.detail,
1731
+ interruptSource: runtime.interruptSource,
1732
+ timestamp
1733
+ });
1734
+ if (isTerminalSessionRunningState(runningState)) {
1735
+ await this.emitTerminalStateEvent({
1736
+ sessionId,
1737
+ status: runningState,
1738
+ timestamp,
1739
+ detail: runtime.detail,
1740
+ source: "runtime"
1741
+ });
1742
+ void this.dispatchNextQueuedMessage(sessionId);
1743
+ }
1744
+ }
1745
+ async forceInterruptInactiveSession(sessionId) {
1746
+ const timestamp = nowIso();
1747
+ const currentSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1748
+ for (const userId of this.authUserRepository.listIds()) {
1749
+ const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1750
+ if (!current || !isPendingSessionRunningState(current.runningState)) {
1751
+ continue;
1752
+ }
1753
+ this.sessionStateRepository.upsert({
1754
+ ...current,
1755
+ runningState: "interrupted",
1756
+ activitySource: "runtime",
1757
+ completedAt: timestamp,
1758
+ updatedAt: timestamp
1759
+ });
1760
+ }
1761
+ this.clearExternalRuntimeSnapshot(sessionId);
1762
+ this.upsertSnapshot(sessionId, {
1763
+ syncStatus: "idle",
1764
+ syncCursor: currentSnapshot?.syncCursor ?? null,
1765
+ lastSyncAt: timestamp,
1766
+ lastErrorCode: null,
1767
+ lastErrorDetail: null,
1768
+ resumedAt: currentSnapshot?.resumedAt ?? null
1769
+ });
1770
+ this.sessionActivityAuthorityService.observe({
1771
+ sessionId,
1772
+ runId: null,
1773
+ runningState: "interrupted",
1774
+ source: "authoritative_runtime",
1775
+ confidence: "strong",
1776
+ detail: "检测到会话实际已停止,已自动修正残留运行状态",
1777
+ interruptSource: "runtime",
1778
+ errorCode: null,
1779
+ observedAt: timestamp
1780
+ });
1781
+ await this.emitExternalRuntimeEnvelope({
1782
+ type: "session.runtime_status",
1783
+ sessionId,
1784
+ status: "interrupted",
1785
+ detail: "检测到会话实际已停止,已自动修正残留运行状态",
1786
+ interruptSource: "runtime",
1787
+ timestamp
1788
+ });
1789
+ await this.emitTerminalStateEvent({
1790
+ sessionId,
1791
+ status: "interrupted",
1792
+ timestamp,
1793
+ detail: "检测到会话实际已停止,已自动修正残留运行状态",
1794
+ source: "runtime"
1795
+ });
1796
+ void this.dispatchNextQueuedMessage(sessionId);
1797
+ }
1485
1798
  beginPendingSendDebugTrace(input) {
1486
1799
  if (!isPerfDebugEnabled()) {
1487
1800
  return null;
@@ -1619,7 +1932,7 @@ export class SessionLiveRuntimeService {
1619
1932
  }
1620
1933
  }
1621
1934
  async resolveNextUserSequence(sessionId, messageCount) {
1622
- let maxSequence = Math.max(messageCount, 0);
1935
+ let maxSequence = 0;
1623
1936
  const envelope = await Promise.resolve(this.sessionHistoryService.readRecentHistoryEnvelope(sessionId, 10)).catch(() => {
1624
1937
  return null;
1625
1938
  });
@@ -1628,6 +1941,9 @@ export class SessionLiveRuntimeService {
1628
1941
  maxSequence = message.sequence;
1629
1942
  }
1630
1943
  }
1944
+ if (maxSequence <= 0) {
1945
+ maxSequence = Math.max(messageCount, 0);
1946
+ }
1631
1947
  return Math.max(maxSequence + 1, 1);
1632
1948
  }
1633
1949
  async waitForResolvedStartBinding(sessionId, workspaceId, provider, handle) {
@@ -1764,6 +2080,39 @@ export class SessionLiveRuntimeService {
1764
2080
  clearExternalRuntimeSnapshot(sessionId) {
1765
2081
  this.externalRuntimeSnapshots.delete(sessionId);
1766
2082
  }
2083
+ getFreshExternalRuntimeSnapshot(sessionId) {
2084
+ const snapshot = this.externalRuntimeSnapshots.get(sessionId) ?? null;
2085
+ if (!snapshot) {
2086
+ return null;
2087
+ }
2088
+ if (!isExternalRuntimeSnapshotExpired(snapshot)) {
2089
+ return snapshot;
2090
+ }
2091
+ this.clearExternalRuntimeSnapshot(sessionId);
2092
+ this.sessionActivityAuthorityService.clearSession(sessionId);
2093
+ return null;
2094
+ }
2095
+ suppressInterruptedExternalRuntime(sessionId) {
2096
+ this.externalRuntimeInterruptSuppressions.set(sessionId, Date.now() + EXTERNAL_RUNTIME_INTERRUPT_SUPPRESSION_MS);
2097
+ }
2098
+ clearExternalRuntimeInterruptSuppression(sessionId) {
2099
+ this.externalRuntimeInterruptSuppressions.delete(sessionId);
2100
+ }
2101
+ shouldIgnoreInterruptedExternalRuntime(sessionId) {
2102
+ const expiresAt = this.externalRuntimeInterruptSuppressions.get(sessionId);
2103
+ if (!expiresAt) {
2104
+ return false;
2105
+ }
2106
+ if (Date.now() >= expiresAt) {
2107
+ this.externalRuntimeInterruptSuppressions.delete(sessionId);
2108
+ return false;
2109
+ }
2110
+ return true;
2111
+ }
2112
+ async forceInterruptExternalSession(sessionId) {
2113
+ this.suppressInterruptedExternalRuntime(sessionId);
2114
+ await this.forceInterruptInactiveSession(sessionId);
2115
+ }
1767
2116
  async resolveActiveClaudePermissionSession(input) {
1768
2117
  const activeSnapshots = this.providerRuntimeService
1769
2118
  .listSnapshots()
@@ -1835,6 +2184,13 @@ function createExternalRuntimeActivityObservation(sessionId, snapshot) {
1835
2184
  observedAt: snapshot.updatedAt
1836
2185
  };
1837
2186
  }
2187
+ function isExternalRuntimeSnapshotExpired(snapshot) {
2188
+ const updatedAtMs = Date.parse(snapshot.updatedAt);
2189
+ if (!Number.isFinite(updatedAtMs)) {
2190
+ return true;
2191
+ }
2192
+ return Date.now() - updatedAtMs > EXTERNAL_RUNTIME_SNAPSHOT_MAX_AGE_MS;
2193
+ }
1838
2194
  function createRuntimeEventObservation(sessionId, event, startedAt) {
1839
2195
  return {
1840
2196
  sessionId,
@@ -2111,21 +2467,33 @@ function createProviderRuntimeAdapters(config, options = {}) {
2111
2467
  if ("dispose" in claudeAdapter && typeof claudeAdapter.dispose === "function") {
2112
2468
  disposables.push(claudeAdapter);
2113
2469
  }
2114
- const codexTransportHelper = process.env.VITEST
2115
- ? null
2116
- : new CodexAppServerHelperClient(config.codexCliPath, {
2117
- homeDir: config.codexHomeDir
2118
- });
2119
- if (codexTransportHelper) {
2120
- disposables.push(codexTransportHelper);
2121
- }
2122
2470
  return {
2123
2471
  adapters: [
2124
2472
  claudeAdapter,
2473
+ new LegnaRuntimeAdapter({
2474
+ homeDir: config.legnaCodeHomeDir,
2475
+ commandPath: config.legnaCodeCliPath,
2476
+ legacyClaudeHomeDir: config.claudeCodeHomeDir
2477
+ }),
2125
2478
  new CodexRuntimeAdapter({
2126
2479
  homeDir: config.codexHomeDir,
2127
2480
  commandPath: config.codexCliPath,
2128
- transportFactory: codexTransportHelper?.createTransport.bind(codexTransportHelper),
2481
+ transportFactory: process.env.VITEST
2482
+ ? undefined
2483
+ : (request) => {
2484
+ const client = new CodexAppServerHelperClient(config.codexCliPath, {
2485
+ homeDir: request.runtimeHomeDir?.trim() || config.codexHomeDir,
2486
+ runtimeEnv: request.runtimeEnv ?? null
2487
+ });
2488
+ const transport = client.createTransport();
2489
+ return {
2490
+ ...transport,
2491
+ close() {
2492
+ transport.close();
2493
+ client.dispose();
2494
+ }
2495
+ };
2496
+ },
2129
2497
  handleServerRequest: options.handleCodexServerRequest
2130
2498
  }),
2131
2499
  new GeminiRuntimeAdapter({
@@ -2150,12 +2518,16 @@ function resolveRuntimeInstructionFilePath(provider, workspacePath, explicitFile
2150
2518
  const resolvedExplicit = path.resolve(normalizedExplicit);
2151
2519
  return existsSync(resolvedExplicit) ? resolvedExplicit : null;
2152
2520
  }
2153
- if (provider !== "claude-code") {
2521
+ const defaultInstructionPath = provider === "claude-code"
2522
+ ? path.join(workspacePath, "CLAUDE.md")
2523
+ : provider === "legna-code"
2524
+ ? path.join(workspacePath, "LEGNA.md")
2525
+ : null;
2526
+ if (!defaultInstructionPath) {
2154
2527
  return null;
2155
2528
  }
2156
- const defaultClaudeInstructionPath = path.join(workspacePath, "CLAUDE.md");
2157
- return existsSync(defaultClaudeInstructionPath)
2158
- ? path.resolve(defaultClaudeInstructionPath)
2529
+ return existsSync(defaultInstructionPath)
2530
+ ? path.resolve(defaultInstructionPath)
2159
2531
  : null;
2160
2532
  }
2161
2533
  function buildClaudeHookBridgeConfig(config) {
@@ -2215,4 +2587,30 @@ function workspaceSlug(workspacePath) {
2215
2587
  .replaceAll("\\", "-")
2216
2588
  .replaceAll("/", "-");
2217
2589
  }
2590
+ function normalizeOptionalBindingValue(value) {
2591
+ const normalized = value?.trim();
2592
+ return normalized && normalized.length > 0 ? normalized : null;
2593
+ }
2594
+ function logOpenCodeOrderEnvelopeDebug(scope, detail) {
2595
+ if (!OPENCODE_ORDER_DEBUG_ENABLED) {
2596
+ return;
2597
+ }
2598
+ console.info(`[opencode-order-envelope] ${scope}`, {
2599
+ timestamp: new Date().toISOString(),
2600
+ ...detail
2601
+ });
2602
+ }
2603
+ function summarizeOpenCodeOrderMessage(message) {
2604
+ const content = typeof message.content === "string" ? message.content : "";
2605
+ const normalized = content.replace(/\s+/g, " ").trim();
2606
+ return {
2607
+ messageId: message.messageId ?? null,
2608
+ role: message.role ?? null,
2609
+ kind: message.kind ?? null,
2610
+ sequence: message.sequence ?? null,
2611
+ timestamp: message.timestamp ?? null,
2612
+ rawRef: message.rawRef ?? null,
2613
+ contentPreview: normalized.length > 80 ? `${normalized.slice(0, 80)}...` : normalized
2614
+ };
2615
+ }
2218
2616
  //# sourceMappingURL=session-live-runtime-service.js.map