@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,5 +1,5 @@
1
1
  import { existsSync, readFileSync, statSync } from "node:fs";
2
- import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, GeminiAdapter, KimiAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
2
+ import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, GeminiAdapter, KimiAdapter, LegnaCodeAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
3
3
  import { AppError } from "../../shared/errors/app-error.js";
4
4
  import { hashContent } from "../../shared/utils/hash.js";
5
5
  import { createId } from "../../shared/utils/id.js";
@@ -11,6 +11,7 @@ import { inspectSessionActivity } from "./session-activity-inspector.js";
11
11
  import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
12
12
  import { mapSessionProviderError } from "./session-provider-error-mapper.js";
13
13
  import { SessionForkRepository } from "../../storage/repositories/session-fork-repository.js";
14
+ import { buildParallelGroupColorToken, resolveParallelDisplayParentSessionId } from "../parallel-sessions/parallel-session-group-service.js";
14
15
  import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
15
16
  import { CodexModelOptionsService, createFallbackCodexModelOptions, enrichCodexCapabilities } from "../provider/codex-model-options.js";
16
17
  import { OpenCodeModelOptionsService, createFallbackOpenCodeModelOptions, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
@@ -24,13 +25,23 @@ const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "op
24
25
  const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
25
26
  const MAX_FORK_DEPTH = 4;
26
27
  const SYNTHETIC_CODEX_SESSION_CLEANUP_GRACE_MS = 120_000;
28
+ const GEMINI_RUNTIME_CHAT_DISCOVERY_GRACE_MS = 30_000;
27
29
  const SESSION_START_DEFERRED_PROVIDERS = new Set([
28
30
  "codex",
29
31
  "claude-code",
32
+ "legna-code",
30
33
  "opencode",
31
34
  "gemini",
32
35
  "kimi"
33
36
  ]);
37
+ const MUTABLE_HISTORY_TAIL_PROVIDERS = new Set([
38
+ "claude-code",
39
+ "legna-code",
40
+ "codex",
41
+ "gemini",
42
+ "kimi",
43
+ "opencode"
44
+ ]);
34
45
  const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
35
46
  const WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS = 15_000;
36
47
  const WORKSPACE_DISCOVERY_SCAN_CONCURRENCY = 2;
@@ -62,8 +73,12 @@ export class SessionHistoryService {
62
73
  openCodeModelOptionsService;
63
74
  providerCliCommandPaths;
64
75
  providerCliAvailability;
76
+ parallelSessionGroupRepository;
77
+ parallelSessionMemberRepository;
78
+ sessionIsolatedWorkspaceRepository;
65
79
  providerDiscoveryHelperClient = getSharedProviderDiscoveryHelperClient();
66
80
  providerSessionDiscoveryConfig;
81
+ sessionProviderConfigService;
67
82
  taskManager;
68
83
  workspaceDiscoveryStatuses = new Map();
69
84
  workspaceStateRefreshStatuses = new Map();
@@ -73,7 +88,7 @@ export class SessionHistoryService {
73
88
  sessionDeletedObservers = new Set();
74
89
  workspaceSessionRelations = new Map();
75
90
  workspaceStateRefreshTaskSequence = 0;
76
- constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager()) {
91
+ constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager(), parallelSessionGroupRepository = null, parallelSessionMemberRepository = null, sessionIsolatedWorkspaceRepository = null, sessionProviderConfigService = null) {
77
92
  this.db = db;
78
93
  this.workspaceRepository = workspaceRepository;
79
94
  this.sessionBindingRepository = sessionBindingRepository;
@@ -88,9 +103,14 @@ export class SessionHistoryService {
88
103
  this.providerSessionDeleteCli =
89
104
  adapterOverrides.providerSessionDeleteCli ?? new CodingnsProviderSessionDeleteCli(config);
90
105
  this.taskManager = taskManager;
106
+ this.parallelSessionGroupRepository = parallelSessionGroupRepository;
107
+ this.parallelSessionMemberRepository = parallelSessionMemberRepository;
108
+ this.sessionIsolatedWorkspaceRepository = sessionIsolatedWorkspaceRepository;
109
+ this.sessionProviderConfigService = sessionProviderConfigService;
91
110
  this.claudeCodeHomeDir = config.claudeCodeHomeDir;
92
111
  this.providerCliCommandPaths = {
93
112
  "claude-code": process.platform === "win32" ? "claude.cmd" : "claude",
113
+ "legna-code": config.legnaCodeCliPath,
94
114
  codex: config.codexCliPath,
95
115
  gemini: config.geminiCliPath,
96
116
  kimi: config.kimiCliPath
@@ -99,8 +119,10 @@ export class SessionHistoryService {
99
119
  this.providerCliAvailability = buildProviderCliAvailabilitySnapshot(this.providerCliCommandPaths);
100
120
  this.providerSessionDiscoveryConfig = {
101
121
  claudeCodeHomeDir: config.claudeCodeHomeDir,
122
+ legnaCodeHomeDir: config.legnaCodeHomeDir,
102
123
  codexCliPath: config.codexCliPath,
103
124
  codexHomeDir: config.codexHomeDir,
125
+ legnaCodeCliPath: config.legnaCodeCliPath,
104
126
  geminiCliPath: config.geminiCliPath,
105
127
  geminiHomeDir: config.geminiHomeDir,
106
128
  kimiDefaultModel: config.kimiDefaultModel,
@@ -111,6 +133,10 @@ export class SessionHistoryService {
111
133
  };
112
134
  this.providerRegistry = new ProviderRegistry([
113
135
  new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
136
+ new LegnaCodeAdapter({
137
+ homeDir: config.legnaCodeHomeDir,
138
+ legacyClaudeHomeDir: config.claudeCodeHomeDir
139
+ }),
114
140
  new CodexAdapter({
115
141
  homeDir: config.codexHomeDir,
116
142
  forkTransportFactory: adapterOverrides.codexForkTransportFactory
@@ -309,8 +335,8 @@ export class SessionHistoryService {
309
335
  ? current?.syncCursor ?? page.cursor
310
336
  : page.cursor,
311
337
  lastSyncAt: nowIso(),
312
- lastErrorCode: current?.lastErrorCode ?? null,
313
- lastErrorDetail: current?.lastErrorDetail ?? null,
338
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(current?.lastErrorCode ?? null),
339
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(current?.lastErrorCode ?? null, current?.lastErrorDetail ?? null),
314
340
  resumedAt: current?.resumedAt ?? null
315
341
  });
316
342
  snapshotIdleMs = Date.now() - snapshotIdleStartedAt;
@@ -423,9 +449,11 @@ export class SessionHistoryService {
423
449
  return this.sessionChangedFileService.listBySessionId(sessionId);
424
450
  }
425
451
  listWorkspaceSessions(workspaceId, userId) {
426
- return this.enrichSessionItems(workspaceId, this.sessionIndexRepository
452
+ const directItems = this.sessionIndexRepository
427
453
  .listByWorkspace(workspaceId, userId)
428
- .filter((item) => !this.isPendingSessionAlias(item)));
454
+ .filter((item) => !this.isPendingSessionAlias(item));
455
+ const projectedItems = this.listProjectedIsolatedWorkspaceSessions(workspaceId, userId);
456
+ return this.enrichSessionItems(workspaceId, sortSessionListItemsByRecentActivity(mergeSessionListItemsBySessionId([...directItems, ...projectedItems])));
429
457
  }
430
458
  getProviderCapabilitiesSnapshot(provider) {
431
459
  try {
@@ -439,6 +467,15 @@ export class SessionHistoryService {
439
467
  try {
440
468
  const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
441
469
  const baseCapabilities = this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider));
470
+ if (baseCapabilities.provider === "opencode" && workspacePath) {
471
+ const refreshed = await this.enrichProviderCapabilities(baseCapabilities, workspacePath);
472
+ const cacheKey = buildProviderCapabilityCacheKey(baseCapabilities.provider, workspacePath);
473
+ this.providerCapabilityCache.set(cacheKey, {
474
+ refreshedAt: Date.now(),
475
+ value: refreshed
476
+ });
477
+ return refreshed;
478
+ }
442
479
  this.scheduleProviderCapabilityRefresh(baseCapabilities, workspacePath);
443
480
  return this.resolveProviderCapabilitiesImmediate(baseCapabilities, workspacePath);
444
481
  }
@@ -454,6 +491,17 @@ export class SessionHistoryService {
454
491
  .getSessionCapabilities(binding.provider, binding.providerSessionId)
455
492
  .then((capabilities) => {
456
493
  const normalizedCapabilities = this.applyProviderCliAvailability(capabilities);
494
+ if (normalizedCapabilities.provider === "opencode") {
495
+ return this.enrichProviderCapabilities(normalizedCapabilities, workspacePath)
496
+ .then((refreshed) => {
497
+ const cacheKey = buildProviderCapabilityCacheKey(normalizedCapabilities.provider, workspacePath);
498
+ this.providerCapabilityCache.set(cacheKey, {
499
+ refreshedAt: Date.now(),
500
+ value: refreshed
501
+ });
502
+ return refreshed;
503
+ });
504
+ }
457
505
  this.scheduleProviderCapabilityRefresh(normalizedCapabilities, workspacePath);
458
506
  return this.resolveProviderCapabilitiesImmediate(normalizedCapabilities, workspacePath);
459
507
  })
@@ -594,11 +642,17 @@ export class SessionHistoryService {
594
642
  async startSessionDirect(input) {
595
643
  const workspace = this.getWorkspaceOrThrow(input.workspaceId);
596
644
  this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
645
+ const sessionId = createId();
646
+ const providerBinding = this.prepareDirectSessionBinding({
647
+ sessionId,
648
+ provider: input.provider,
649
+ providerConfigMode: input.providerConfigMode ?? null,
650
+ providerPresetId: input.providerPresetId ?? null
651
+ });
597
652
  try {
598
- const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
653
+ const result = await this.startProviderSessionWithBinding(input.provider, workspace.path, providerBinding.runtimeHomeDir, {
599
654
  initialPrompt: input.initialPrompt
600
655
  });
601
- const sessionId = createId();
602
656
  const timestamp = nowIso();
603
657
  const persist = this.db.transaction(() => {
604
658
  this.sessionBindingRepository.upsert({
@@ -607,6 +661,9 @@ export class SessionHistoryService {
607
661
  provider: result.session.provider,
608
662
  providerSessionId: result.session.providerSessionId,
609
663
  rawStoreRef: result.session.rawStoreRef,
664
+ providerConfigMode: providerBinding.providerConfigMode,
665
+ providerPresetId: providerBinding.providerPresetId,
666
+ runtimeHomeDir: providerBinding.runtimeHomeDir,
610
667
  createdAt: timestamp,
611
668
  updatedAt: timestamp
612
669
  });
@@ -658,7 +715,8 @@ export class SessionHistoryService {
658
715
  }
659
716
  async forkSession(input) {
660
717
  const binding = this.getBindingOrThrow(input.sessionId);
661
- const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
718
+ const targetWorkspaceId = input.targetWorkspaceId?.trim() || binding.workspaceId;
719
+ const workspace = this.getWorkspaceOrThrow(targetWorkspaceId);
662
720
  const targetProvider = input.targetProvider?.trim() || binding.provider;
663
721
  this.assertProviderCapabilityEnabled(targetProvider, "canStartSession", "当前 provider 不支持 fork 创建会话");
664
722
  const sourceMessageId = input.sourceType === "message"
@@ -673,10 +731,18 @@ export class SessionHistoryService {
673
731
  });
674
732
  }
675
733
  this.assertForkDepthWithinLimit(input.sessionId);
676
- if (targetProvider !== binding.provider) {
734
+ const requestedTargetSelection = resolveRequestedProviderSelection({
735
+ existingBinding: targetProvider === binding.provider ? binding : null,
736
+ providerConfigMode: input.providerConfigMode ?? undefined,
737
+ providerPresetId: input.providerPresetId ?? undefined
738
+ });
739
+ if (targetProvider !== binding.provider
740
+ || !areEquivalentProviderBindingSelection(binding, requestedTargetSelection)) {
677
741
  return this.forkSessionAcrossProviders({
678
742
  ...input,
679
- targetProvider
743
+ targetProvider,
744
+ providerConfigMode: requestedTargetSelection.providerConfigMode,
745
+ providerPresetId: requestedTargetSelection.providerPresetId
680
746
  }, binding, sourceMessageId);
681
747
  }
682
748
  try {
@@ -696,6 +762,9 @@ export class SessionHistoryService {
696
762
  provider: result.session.provider,
697
763
  providerSessionId: result.session.providerSessionId,
698
764
  rawStoreRef: result.session.rawStoreRef,
765
+ providerConfigMode: binding.providerConfigMode,
766
+ providerPresetId: binding.providerPresetId,
767
+ runtimeHomeDir: binding.runtimeHomeDir,
699
768
  createdAt: timestamp,
700
769
  updatedAt: timestamp
701
770
  });
@@ -786,10 +855,12 @@ export class SessionHistoryService {
786
855
  messages: reconstructedMessages
787
856
  });
788
857
  const startedSession = await this.startSessionDirect({
789
- workspaceId: sourceBinding.workspaceId,
858
+ workspaceId: input.targetWorkspaceId?.trim() || sourceBinding.workspaceId,
790
859
  userId: input.userId,
791
860
  provider: input.targetProvider,
792
861
  initialPrompt: inheritedPrompt,
862
+ providerConfigMode: input.providerConfigMode ?? null,
863
+ providerPresetId: input.providerPresetId ?? null,
793
864
  parentSessionId: input.sessionId,
794
865
  sessionKind: input.sessionKind ?? "default",
795
866
  annotationSourceMessageId: input.annotationSourceMessageId ?? null,
@@ -824,7 +895,7 @@ export class SessionHistoryService {
824
895
  createdAt: timestamp
825
896
  });
826
897
  })();
827
- const relationMap = this.workspaceSessionRelations.get(sourceBinding.workspaceId)
898
+ const relationMap = this.workspaceSessionRelations.get(input.targetWorkspaceId?.trim() || sourceBinding.workspaceId)
828
899
  ?? new Map();
829
900
  relationMap.set(startedSession.sessionId, {
830
901
  parentSessionId: input.sessionId,
@@ -834,9 +905,43 @@ export class SessionHistoryService {
834
905
  isSubagent: startedSession.isSubagent ?? false,
835
906
  subagentLabel: startedSession.subagentLabel ?? null
836
907
  });
837
- this.workspaceSessionRelations.set(sourceBinding.workspaceId, relationMap);
908
+ this.workspaceSessionRelations.set(input.targetWorkspaceId?.trim() || sourceBinding.workspaceId, relationMap);
838
909
  return this.getSessionListItemOrThrow(startedSession.sessionId, input.userId);
839
910
  }
911
+ prepareDirectSessionBinding(input) {
912
+ if (!this.sessionProviderConfigService) {
913
+ return {
914
+ providerConfigMode: "global-default",
915
+ providerPresetId: null,
916
+ runtimeHomeDir: null
917
+ };
918
+ }
919
+ return this.sessionProviderConfigService.prepareSessionBinding({
920
+ sessionId: input.sessionId,
921
+ provider: input.provider,
922
+ providerConfigMode: input.providerConfigMode ?? undefined,
923
+ providerPresetId: input.providerPresetId ?? null
924
+ });
925
+ }
926
+ startProviderSessionWithBinding(provider, workspacePath, runtimeHomeDir, options) {
927
+ const scopedRuntimeHomeDir = runtimeHomeDir?.trim() || null;
928
+ if (!scopedRuntimeHomeDir) {
929
+ return this.sessionSyncService.startSession(provider, workspacePath, options);
930
+ }
931
+ switch (provider) {
932
+ case "claude-code":
933
+ return new ClaudeCodeAdapter({ homeDir: scopedRuntimeHomeDir }).startSession(workspacePath, options);
934
+ case "codex":
935
+ return new CodexAdapter({ homeDir: scopedRuntimeHomeDir }).startSession(workspacePath, options);
936
+ case "gemini":
937
+ return new GeminiAdapter({
938
+ homeDir: scopedRuntimeHomeDir,
939
+ commandPath: this.providerSessionDiscoveryConfig.geminiCliPath
940
+ }).startSession(workspacePath, options);
941
+ default:
942
+ return this.sessionSyncService.startSession(provider, workspacePath, options);
943
+ }
944
+ }
840
945
  async readForkSourceMessages(sessionId, binding, sourceType, sourceMessageId, sourceMessageSnapshot = null) {
841
946
  const messages = [];
842
947
  let cursor = null;
@@ -1007,13 +1112,14 @@ export class SessionHistoryService {
1007
1112
  return null;
1008
1113
  }
1009
1114
  await this.syncSessionTitleFromProvider(sessionId, binding);
1115
+ const snapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1010
1116
  this.upsertSnapshot(sessionId, {
1011
1117
  syncStatus: "idle",
1012
1118
  syncCursor: page.cursor,
1013
1119
  lastSyncAt: nowIso(),
1014
- lastErrorCode: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.lastErrorCode ?? null,
1015
- lastErrorDetail: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.lastErrorDetail ?? null,
1016
- resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
1120
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(snapshot?.lastErrorCode ?? null),
1121
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(snapshot?.lastErrorCode ?? null, snapshot?.lastErrorDetail ?? null),
1122
+ resumedAt: snapshot?.resumedAt ?? null
1017
1123
  });
1018
1124
  return {
1019
1125
  type: "session.delta",
@@ -1125,7 +1231,10 @@ export class SessionHistoryService {
1125
1231
  async deleteSession(sessionId, userId) {
1126
1232
  const binding = this.getBindingOrThrow(sessionId);
1127
1233
  const existing = this.getSessionListItemOrThrow(sessionId, userId);
1128
- if (existing.runningState === "starting" || existing.runningState === "running") {
1234
+ const resolvedExisting = existing.runningState === "starting" || existing.runningState === "running"
1235
+ ? await this.refreshRuntimeFallbackSession(sessionId, userId).catch(() => existing)
1236
+ : existing;
1237
+ if (resolvedExisting.runningState === "starting" || resolvedExisting.runningState === "running") {
1129
1238
  throw new AppError({
1130
1239
  statusCode: 409,
1131
1240
  errorCode: "SESSION_DELETE_RUNNING",
@@ -1224,6 +1333,15 @@ export class SessionHistoryService {
1224
1333
  provider: resolvedSnapshot.provider,
1225
1334
  providerSessionId: resolvedSnapshot.providerSessionId,
1226
1335
  rawStoreRef: resolvedSnapshot.rawStoreRef,
1336
+ providerConfigMode: currentBinding?.providerConfigMode
1337
+ ?? duplicateBinding?.providerConfigMode
1338
+ ?? "global-default",
1339
+ providerPresetId: currentBinding?.providerPresetId
1340
+ ?? duplicateBinding?.providerPresetId
1341
+ ?? null,
1342
+ runtimeHomeDir: currentBinding?.runtimeHomeDir
1343
+ ?? duplicateBinding?.runtimeHomeDir
1344
+ ?? null,
1227
1345
  createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
1228
1346
  ?? timestamp,
1229
1347
  updatedAt: timestamp
@@ -1315,6 +1433,9 @@ export class SessionHistoryService {
1315
1433
  provider: session.provider,
1316
1434
  providerSessionId: session.providerSessionId,
1317
1435
  rawStoreRef: session.rawStoreRef,
1436
+ providerConfigMode: existing?.providerConfigMode ?? "global-default",
1437
+ providerPresetId: existing?.providerPresetId ?? null,
1438
+ runtimeHomeDir: existing?.runtimeHomeDir ?? null,
1318
1439
  createdAt,
1319
1440
  updatedAt: timestamp
1320
1441
  };
@@ -1594,9 +1715,24 @@ export class SessionHistoryService {
1594
1715
  total: 0
1595
1716
  };
1596
1717
  }
1718
+ if (this.shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, provider, error)) {
1719
+ return {
1720
+ messages: [],
1721
+ cursor,
1722
+ nextCursor: null,
1723
+ total: 0
1724
+ };
1725
+ }
1597
1726
  throw mapSessionProviderError(error);
1598
1727
  });
1599
1728
  }
1729
+ shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, provider, error) {
1730
+ if (provider !== "gemini" || !isGeminiChatNotFoundError(error)) {
1731
+ return false;
1732
+ }
1733
+ const sessionIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1734
+ return this.listSessionStatesBySessionId(sessionId).some((state) => shouldTreatMissingGeminiRuntimeHistoryStateAsTransient(state, sessionIndex));
1735
+ }
1600
1736
  enrichMessagesWithOrigin(sessionId, messages) {
1601
1737
  return this.resolveMessageOrigins(sessionId, messages);
1602
1738
  }
@@ -1715,13 +1851,14 @@ export class SessionHistoryService {
1715
1851
  }
1716
1852
  enrichSessionItems(workspaceId, items) {
1717
1853
  const relationMap = this.workspaceSessionRelations.get(workspaceId);
1854
+ const projectionBySessionId = this.buildParallelProjectionBySessionId(items);
1718
1855
  if (!relationMap) {
1719
- return items.map((item) => this.enrichSessionItem(item));
1856
+ return items.map((item) => this.enrichSessionItem(item, projectionBySessionId.get(item.sessionId)));
1720
1857
  }
1721
1858
  return items.map((item) => {
1722
1859
  const relation = relationMap.get(item.sessionId);
1723
1860
  if (!relation) {
1724
- return this.enrichSessionItem(item);
1861
+ return this.enrichSessionItem(item, projectionBySessionId.get(item.sessionId));
1725
1862
  }
1726
1863
  return this.enrichSessionItem({
1727
1864
  ...item,
@@ -1731,10 +1868,12 @@ export class SessionHistoryService {
1731
1868
  annotationSourceText: relation.annotationSourceText,
1732
1869
  isSubagent: relation.isSubagent,
1733
1870
  subagentLabel: relation.subagentLabel
1734
- });
1871
+ }, projectionBySessionId.get(item.sessionId));
1735
1872
  });
1736
1873
  }
1737
- enrichSessionItem(item) {
1874
+ enrichSessionItem(item, projection) {
1875
+ const resolvedProjection = projection
1876
+ ?? this.buildParallelProjectionBySessionId([item]).get(item.sessionId);
1738
1877
  const relation = this.workspaceSessionRelations.get(item.workspaceId)?.get(item.sessionId);
1739
1878
  const nextItem = relation
1740
1879
  ? {
@@ -1756,7 +1895,90 @@ export class SessionHistoryService {
1756
1895
  subagentLabel: item.subagentLabel ?? null
1757
1896
  };
1758
1897
  const resolution = this.sessionActivityAuthorityService.resolvePersistedSession(nextItem);
1759
- return applySessionActivityResolution(nextItem, resolution);
1898
+ return applySessionActivityResolution({
1899
+ ...nextItem,
1900
+ parallelGroup: resolvedProjection?.parallelGroup ?? null,
1901
+ displayParentSessionId: resolvedProjection?.displayParentSessionId ?? null,
1902
+ sessionIsolatedWorkspace: resolvedProjection?.sessionIsolatedWorkspace ?? null
1903
+ }, resolution);
1904
+ }
1905
+ buildParallelProjectionBySessionId(items) {
1906
+ const projectionBySessionId = new Map();
1907
+ if (!this.parallelSessionGroupRepository
1908
+ || !this.parallelSessionMemberRepository
1909
+ || !this.sessionIsolatedWorkspaceRepository
1910
+ || items.length === 0) {
1911
+ return projectionBySessionId;
1912
+ }
1913
+ const sessionIds = items.map((item) => item.sessionId);
1914
+ const members = this.parallelSessionMemberRepository
1915
+ .listBySessionIds(sessionIds)
1916
+ .filter((member) => member.deletedAt === null);
1917
+ if (members.length === 0) {
1918
+ return projectionBySessionId;
1919
+ }
1920
+ const groupIds = [...new Set(members.map((member) => member.groupId))];
1921
+ const groups = this.parallelSessionGroupRepository
1922
+ .listByIds(groupIds)
1923
+ .filter((group) => group.status !== "deleted");
1924
+ const groupById = new Map(groups.map((group) => [group.id, group]));
1925
+ const activeMembersByGroupId = new Map();
1926
+ for (const member of this.parallelSessionMemberRepository.listByGroupIds(groupIds)) {
1927
+ if (member.deletedAt !== null) {
1928
+ continue;
1929
+ }
1930
+ const groupMembers = activeMembersByGroupId.get(member.groupId) ?? [];
1931
+ groupMembers.push(member);
1932
+ activeMembersByGroupId.set(member.groupId, groupMembers);
1933
+ }
1934
+ const isolatedWorkspaceBySessionId = new Map(this.sessionIsolatedWorkspaceRepository
1935
+ .listByOwnerSessionIds(sessionIds)
1936
+ .map((record) => [record.ownerSessionId, record]));
1937
+ for (const member of members) {
1938
+ const group = groupById.get(member.groupId);
1939
+ if (!group) {
1940
+ continue;
1941
+ }
1942
+ const activeMembers = activeMembersByGroupId.get(member.groupId) ?? [member];
1943
+ const isolatedWorkspace = isolatedWorkspaceBySessionId.get(member.sessionId) ?? null;
1944
+ projectionBySessionId.set(member.sessionId, {
1945
+ parallelGroup: {
1946
+ groupId: group.id,
1947
+ role: member.sessionId === group.anchorSessionId ? "anchor" : "member",
1948
+ memberCount: activeMembers.length,
1949
+ sourceType: group.sourceType,
1950
+ sourceSessionId: group.sourceSessionId,
1951
+ anchorSessionId: group.anchorSessionId,
1952
+ colorToken: buildParallelGroupColorToken(group.id)
1953
+ },
1954
+ displayParentSessionId: resolveParallelDisplayParentSessionId(group, member),
1955
+ sessionIsolatedWorkspace: isolatedWorkspace
1956
+ ? {
1957
+ id: isolatedWorkspace.id,
1958
+ workspaceId: isolatedWorkspace.workspaceId,
1959
+ sourceWorkspaceId: isolatedWorkspace.sourceWorkspaceId,
1960
+ branchName: isolatedWorkspace.branchName,
1961
+ lifecycleStatus: isolatedWorkspace.lifecycleStatus,
1962
+ promotedAt: isolatedWorkspace.promotedAt,
1963
+ createdAt: isolatedWorkspace.createdAt,
1964
+ updatedAt: isolatedWorkspace.updatedAt
1965
+ }
1966
+ : null
1967
+ });
1968
+ }
1969
+ return projectionBySessionId;
1970
+ }
1971
+ listProjectedIsolatedWorkspaceSessions(workspaceId, userId) {
1972
+ if (!this.sessionIsolatedWorkspaceRepository) {
1973
+ return [];
1974
+ }
1975
+ return this.sessionIsolatedWorkspaceRepository
1976
+ .listBySourceWorkspaceId(workspaceId)
1977
+ .filter((record) => record.lifecycleStatus === "active"
1978
+ || record.lifecycleStatus === "removing")
1979
+ .map((record) => this.sessionIndexRepository.findBySessionId(record.ownerSessionId, userId))
1980
+ .filter((item) => Boolean(item))
1981
+ .filter((item) => !this.isPendingSessionAlias(item));
1760
1982
  }
1761
1983
  async pullSessionHistory(sessionId, cursor, limit, deliveredMessages, onEnvelope, envelopeType, isClosed = () => false) {
1762
1984
  let currentCursor = cursor;
@@ -1803,8 +2025,8 @@ export class SessionHistoryService {
1803
2025
  syncStatus: "idle",
1804
2026
  syncCursor: page.cursor,
1805
2027
  lastSyncAt: nowIso(),
1806
- lastErrorCode: snapshot?.lastErrorCode ?? null,
1807
- lastErrorDetail: snapshot?.lastErrorDetail ?? null,
2028
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(snapshot?.lastErrorCode ?? null),
2029
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(snapshot?.lastErrorCode ?? null, snapshot?.lastErrorDetail ?? null),
1808
2030
  resumedAt: snapshot?.resumedAt ?? null
1809
2031
  });
1810
2032
  await onEnvelope({
@@ -1830,7 +2052,12 @@ export class SessionHistoryService {
1830
2052
  provider: binding.provider,
1831
2053
  providerSessionId: binding.providerSessionId,
1832
2054
  rawStoreRef: binding.rawStoreRef
1833
- }, signal)).trim();
2055
+ }, signal).catch((error) => {
2056
+ if (this.shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, binding.provider, error)) {
2057
+ return "";
2058
+ }
2059
+ throw error;
2060
+ })).trim();
1834
2061
  const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
1835
2062
  if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
1836
2063
  return;
@@ -2265,6 +2492,9 @@ export class SessionHistoryService {
2265
2492
  provider: input.provider,
2266
2493
  providerSessionId: buildPendingBindingValue(input.provider, input.targetSessionId),
2267
2494
  rawStoreRef: buildPendingBindingValue(input.provider, input.targetSessionId),
2495
+ providerConfigMode: sourceBinding.providerConfigMode,
2496
+ providerPresetId: sourceBinding.providerPresetId,
2497
+ runtimeHomeDir: sourceBinding.runtimeHomeDir,
2268
2498
  createdAt: sourceBinding.createdAt,
2269
2499
  updatedAt: input.timestamp
2270
2500
  });
@@ -2346,6 +2576,9 @@ export class SessionHistoryService {
2346
2576
  provider: sourceBinding.provider,
2347
2577
  providerSessionId: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2348
2578
  rawStoreRef: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2579
+ providerConfigMode: sourceBinding.providerConfigMode,
2580
+ providerPresetId: sourceBinding.providerPresetId,
2581
+ runtimeHomeDir: sourceBinding.runtimeHomeDir,
2349
2582
  createdAt: sourceBinding.createdAt,
2350
2583
  updatedAt: input.timestamp
2351
2584
  });
@@ -2584,7 +2817,9 @@ export class SessionHistoryService {
2584
2817
  const liveObservation = this.resolveLiveActivityObservation(sessionId);
2585
2818
  const inspection = liveObservation
2586
2819
  ? null
2587
- : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2820
+ : binding.provider === "gemini"
2821
+ ? await this.inspectGeminiHistoryActivity(sessionId, binding)
2822
+ : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2588
2823
  if (inspection) {
2589
2824
  const nowMs = Date.parse(timestamp);
2590
2825
  if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
@@ -2647,6 +2882,15 @@ export class SessionHistoryService {
2647
2882
  });
2648
2883
  return nextRecord;
2649
2884
  }
2885
+ async inspectGeminiHistoryActivity(sessionId, binding) {
2886
+ try {
2887
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, GEMINI_ACTIVITY_INFERENCE_HISTORY_LIMIT, "backward", this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null);
2888
+ return inferGeminiInspectionFromHistory(page.messages);
2889
+ }
2890
+ catch {
2891
+ return inspectSessionActivity(binding.provider, binding.rawStoreRef);
2892
+ }
2893
+ }
2650
2894
  async repairCodexDirtyBindingBeforeHistoryRead(sessionId, userId, binding) {
2651
2895
  if (!shouldRepairCodexDirtyBinding(binding)) {
2652
2896
  this.codexDirtyBindingRepairStates.delete(sessionId);
@@ -2723,7 +2967,11 @@ export class SessionHistoryService {
2723
2967
  }
2724
2968
  }
2725
2969
  function isProviderCliBacked(provider) {
2726
- return provider === "claude-code" || provider === "codex" || provider === "gemini" || provider === "kimi";
2970
+ return provider === "claude-code"
2971
+ || provider === "legna-code"
2972
+ || provider === "codex"
2973
+ || provider === "gemini"
2974
+ || provider === "kimi";
2727
2975
  }
2728
2976
  function buildProviderCliAvailabilitySnapshot(commandPaths) {
2729
2977
  return Object.freeze(Object.fromEntries(Object.entries(commandPaths).map(([provider, commandPath]) => [
@@ -2735,6 +2983,8 @@ function buildProviderCliUnavailableMessage(provider) {
2735
2983
  switch (provider) {
2736
2984
  case "claude-code":
2737
2985
  return "未检测到 Claude CLI";
2986
+ case "legna-code":
2987
+ return "未检测到 Legna CLI";
2738
2988
  case "codex":
2739
2989
  return "未检测到 Codex CLI";
2740
2990
  case "gemini":
@@ -2794,6 +3044,46 @@ function hasInspectionEvidence(inspection) {
2794
3044
  || !!inspection.lastEventAt
2795
3045
  || !!inspection.completedAtCandidate;
2796
3046
  }
3047
+ function inferGeminiInspectionFromHistory(messages) {
3048
+ let lastEventAt = null;
3049
+ let lastUserAt = null;
3050
+ let latestNonUserMessage = null;
3051
+ const pendingToolCallIds = new Set();
3052
+ for (const message of messages) {
3053
+ lastEventAt = pickLaterIso(lastEventAt, message.timestamp);
3054
+ if (message.role === "user") {
3055
+ lastUserAt = pickLaterIso(lastUserAt, message.timestamp);
3056
+ continue;
3057
+ }
3058
+ latestNonUserMessage = message;
3059
+ if (message.kind === "tool_call" && message.toolCall?.callId) {
3060
+ pendingToolCallIds.add(message.toolCall.callId);
3061
+ continue;
3062
+ }
3063
+ if (message.kind === "tool_result" && message.toolCall?.callId) {
3064
+ pendingToolCallIds.delete(message.toolCall.callId);
3065
+ }
3066
+ }
3067
+ const latestNonUserAt = latestNonUserMessage?.timestamp ?? null;
3068
+ const hasReplyAfterLatestUser = !!latestNonUserAt && (!lastUserAt || latestNonUserAt.localeCompare(lastUserAt) >= 0);
3069
+ const isTerminalReplyKind = latestNonUserMessage?.kind === "text" || latestNonUserMessage?.kind === "tool_result";
3070
+ const completedAtCandidate = hasReplyAfterLatestUser && isTerminalReplyKind && pendingToolCallIds.size === 0
3071
+ ? latestNonUserAt
3072
+ : null;
3073
+ const hasOpenTurn = !completedAtCandidate
3074
+ && hasReplyAfterLatestUser
3075
+ && (pendingToolCallIds.size > 0
3076
+ || latestNonUserMessage?.kind === "thinking"
3077
+ || latestNonUserMessage?.kind === "tool_call");
3078
+ return {
3079
+ runningState: hasOpenTurn ? "running" : "idle",
3080
+ hasPendingTools: pendingToolCallIds.size > 0,
3081
+ lastEventAt,
3082
+ completedAtCandidate,
3083
+ errorCode: null,
3084
+ errorDetail: null
3085
+ };
3086
+ }
2797
3087
  function applySessionActivityResolution(item, resolution) {
2798
3088
  const rawResolvedRunningState = resolution.runningState === "unknown" && item.runningState === null
2799
3089
  ? null
@@ -3289,7 +3579,7 @@ function createDeliveredHistoryMessageState() {
3289
3579
  };
3290
3580
  }
3291
3581
  function shouldRefreshMutableHistoryTail(provider, page, cursor, deliveredMessages) {
3292
- if (provider !== "kimi" || cursor === null || page.messages.length > 0) {
3582
+ if (!MUTABLE_HISTORY_TAIL_PROVIDERS.has(provider) || cursor === null || page.messages.length > 0) {
3293
3583
  return false;
3294
3584
  }
3295
3585
  return Date.now() - deliveredMessages.lastMutableTailRefreshAtMs >= MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS;
@@ -3347,6 +3637,40 @@ function shouldTreatMissingSyntheticHistoryAsEmpty(provider, rawStoreRef, error)
3347
3637
  const detail = error instanceof Error ? error.message : String(error);
3348
3638
  return detail.includes("ENOENT");
3349
3639
  }
3640
+ function shouldTreatMissingGeminiRuntimeHistoryStateAsTransient(state, sessionIndex) {
3641
+ if (state.activitySource !== "runtime") {
3642
+ return false;
3643
+ }
3644
+ if (state.runningState === "starting" || state.runningState === "running") {
3645
+ return true;
3646
+ }
3647
+ if ((sessionIndex?.messageCount ?? 0) !== 0) {
3648
+ return false;
3649
+ }
3650
+ return isWithinGeminiRuntimeChatDiscoveryGraceWindow(state.lastEventAt ?? state.updatedAt ?? sessionIndex?.updatedAt ?? sessionIndex?.createdAt ?? null);
3651
+ }
3652
+ function isWithinGeminiRuntimeChatDiscoveryGraceWindow(timestamp) {
3653
+ if (!timestamp) {
3654
+ return false;
3655
+ }
3656
+ const timestampMs = Date.parse(timestamp);
3657
+ if (!Number.isFinite(timestampMs)) {
3658
+ return false;
3659
+ }
3660
+ return Date.now() - timestampMs <= GEMINI_RUNTIME_CHAT_DISCOVERY_GRACE_MS;
3661
+ }
3662
+ function isGeminiChatNotFoundError(error) {
3663
+ if (error instanceof AppError) {
3664
+ return error.errorCode === "GEMINI_CHAT_NOT_FOUND";
3665
+ }
3666
+ return error instanceof Error && error.message === "GEMINI_CHAT_NOT_FOUND";
3667
+ }
3668
+ function clearSuccessfulProviderReadErrorCode(errorCode) {
3669
+ return errorCode === "PROVIDER_READ_FAILED" ? null : errorCode;
3670
+ }
3671
+ function clearSuccessfulProviderReadErrorDetail(errorCode, errorDetail) {
3672
+ return errorCode === "PROVIDER_READ_FAILED" ? null : errorDetail;
3673
+ }
3350
3674
  function shouldShortCircuitMissingSyntheticCodexHistory(provider, rawStoreRef) {
3351
3675
  return provider === "codex" && isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
3352
3676
  }
@@ -3493,6 +3817,9 @@ function isSyntheticCodexSessionTitle(title) {
3493
3817
  return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
3494
3818
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
3495
3819
  }
3820
+ function isSyntheticGeminiSessionTitle(title) {
3821
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title);
3822
+ }
3496
3823
  function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3497
3824
  const normalizedTitle = currentTitle?.trim() ?? "";
3498
3825
  if (normalizedTitle.length === 0) {
@@ -3501,6 +3828,9 @@ function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3501
3828
  if (provider === "codex" && isSyntheticCodexSessionTitle(normalizedTitle)) {
3502
3829
  return true;
3503
3830
  }
3831
+ if (provider === "gemini" && isSyntheticGeminiSessionTitle(normalizedTitle)) {
3832
+ return true;
3833
+ }
3504
3834
  return false;
3505
3835
  }
3506
3836
  function shouldRemoveHiddenClaudeDebugSession(session) {
@@ -3512,6 +3842,7 @@ function shouldRemoveHiddenClaudeDebugSession(session) {
3512
3842
  /\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
3513
3843
  }
3514
3844
  const STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS = 120_000;
3845
+ const GEMINI_ACTIVITY_INFERENCE_HISTORY_LIMIT = 80;
3515
3846
  function shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs) {
3516
3847
  if (!current || current.activitySource !== "runtime") {
3517
3848
  return false;
@@ -3545,6 +3876,11 @@ function shouldPreserveRuntimeTerminalState(current, inspection) {
3545
3876
  return inspection.lastEventAt.localeCompare(current.lastEventAt) <= 0;
3546
3877
  }
3547
3878
  if (current.runningState === "starting" || current.runningState === "running") {
3879
+ if (inspection.completedAtCandidate
3880
+ || inspection.errorCode
3881
+ || inspection.runningState === "interrupted") {
3882
+ return false;
3883
+ }
3548
3884
  return inspection.lastEventAt.localeCompare(current.lastEventAt) <= 0;
3549
3885
  }
3550
3886
  return false;
@@ -3684,8 +4020,51 @@ function areEquivalentSessionBindings(current, next) {
3684
4020
  current.provider === next.provider &&
3685
4021
  current.providerSessionId === next.providerSessionId &&
3686
4022
  current.rawStoreRef === next.rawStoreRef &&
4023
+ current.providerConfigMode === next.providerConfigMode &&
4024
+ current.providerPresetId === next.providerPresetId &&
4025
+ current.runtimeHomeDir === next.runtimeHomeDir &&
3687
4026
  current.createdAt === next.createdAt);
3688
4027
  }
4028
+ function resolveRequestedProviderSelection(input) {
4029
+ const existingSelection = input.existingBinding
4030
+ ? {
4031
+ providerConfigMode: input.existingBinding.providerConfigMode,
4032
+ providerPresetId: input.existingBinding.providerPresetId
4033
+ }
4034
+ : null;
4035
+ const normalizedPresetId = input.providerPresetId?.trim() || null;
4036
+ if (input.providerConfigMode === undefined && input.providerPresetId === undefined) {
4037
+ return existingSelection ?? {
4038
+ providerConfigMode: "global-default",
4039
+ providerPresetId: null
4040
+ };
4041
+ }
4042
+ const providerConfigMode = input.providerConfigMode
4043
+ ?? (normalizedPresetId ? "cc-switch-preset" : "global-default");
4044
+ if (providerConfigMode === "global-default") {
4045
+ return {
4046
+ providerConfigMode,
4047
+ providerPresetId: null
4048
+ };
4049
+ }
4050
+ const providerPresetId = normalizedPresetId ?? existingSelection?.providerPresetId ?? null;
4051
+ if (!providerPresetId) {
4052
+ throw new AppError({
4053
+ statusCode: 400,
4054
+ errorCode: "INVALID_INPUT",
4055
+ detail: "使用 cc-switch preset 时必须提供 providerPresetId",
4056
+ field: "providerPresetId"
4057
+ });
4058
+ }
4059
+ return {
4060
+ providerConfigMode,
4061
+ providerPresetId
4062
+ };
4063
+ }
4064
+ function areEquivalentProviderBindingSelection(binding, selection) {
4065
+ return (binding.providerConfigMode === selection.providerConfigMode
4066
+ && binding.providerPresetId === selection.providerPresetId);
4067
+ }
3689
4068
  function areEquivalentSessionIndexRecords(current, next) {
3690
4069
  if (!current) {
3691
4070
  return false;
@@ -3772,6 +4151,23 @@ async function runBatchedTransactions(items, batchSize, transaction, logOptions)
3772
4151
  maxBatchMs
3773
4152
  };
3774
4153
  }
4154
+ function mergeSessionListItemsBySessionId(items) {
4155
+ const itemBySessionId = new Map();
4156
+ for (const item of items) {
4157
+ itemBySessionId.set(item.sessionId, item);
4158
+ }
4159
+ return [...itemBySessionId.values()];
4160
+ }
4161
+ function sortSessionListItemsByRecentActivity(items) {
4162
+ return [...items].sort((left, right) => {
4163
+ const leftPrimary = left.lastMessageAt ?? left.updatedAt;
4164
+ const rightPrimary = right.lastMessageAt ?? right.updatedAt;
4165
+ if (leftPrimary !== rightPrimary) {
4166
+ return rightPrimary.localeCompare(leftPrimary);
4167
+ }
4168
+ return right.updatedAt.localeCompare(left.updatedAt);
4169
+ });
4170
+ }
3775
4171
  function applyImmediateModelOptionFallbacks(capabilities, codexSnapshot, openCodeSnapshot) {
3776
4172
  if (capabilities.provider === "codex") {
3777
4173
  return {