@jingyi0605/codingns 0.6.0 → 0.6.5

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 (253) hide show
  1. package/bin/codingns.mjs +240 -2
  2. package/dist/public/assets/AdaptiveButlerPage-CfsUVZKl.js +3 -0
  3. package/dist/public/assets/{App-BZvapsi8.js → App-B9HcTkMT.js} +3 -3
  4. package/dist/public/assets/{BootstrapPage-gHSoa4JN.js → BootstrapPage-BgLdZEKQ.js} +1 -1
  5. package/dist/public/assets/ConversationPage-Cj8go7L0.js +4 -0
  6. package/dist/public/assets/{DesktopDetachPreviewPage-4eMRxiBW.js → DesktopDetachPreviewPage-YCBnyC58.js} +1 -1
  7. package/dist/public/assets/DesktopWindowPage-CrvndE23.js +2 -0
  8. package/dist/public/assets/FileContextPanel-CdC7GGw5.js +1 -0
  9. package/dist/public/assets/GitSidebar-z2SBinQh.js +6 -0
  10. package/dist/public/assets/MobileCreateSessionSheet-Cb2HM-y3.js +1 -0
  11. package/dist/public/assets/{MobileTopHeaderFrame-Bwv8Ovm_.js → MobileTopHeaderFrame-etF2HKlm.js} +1 -1
  12. package/dist/public/assets/MobileWorkspaceSwitcherHeader-CuGJ31kb.js +1 -0
  13. package/dist/public/assets/{RelayConnectEntryPage-D_4YL-YH.js → RelayConnectEntryPage-BB6DbGtP.js} +1 -1
  14. package/dist/public/assets/{ServerSettingsModal-CMSm3BZU.js → ServerSettingsModal-Bl1sacZg.js} +1 -1
  15. package/dist/public/assets/SessionIndexPage-NbF9gJnp.js +1 -0
  16. package/dist/public/assets/SettingsPage-DGsmQpLv.js +1 -0
  17. package/dist/public/assets/TerminalManagerPanel-BOm8Hi_v.js +1 -0
  18. package/dist/public/assets/{TerminalPage-DaooFaJ4.js → TerminalPage-B5JNFU6w.js} +19 -19
  19. package/dist/public/assets/TerminalRuntimeFallbackModal-CM3LRKOJ.js +1 -0
  20. package/dist/public/assets/{ToolFilesPage-CGxBvYG0.js → ToolFilesPage-GSqKQsj_.js} +1 -1
  21. package/dist/public/assets/ToolGitPage-BVFWMMQp.js +1 -0
  22. package/dist/public/assets/ToolProcessesPage-DZ456fYz.js +1 -0
  23. package/dist/public/assets/ToolsHomePage-DsJp0y8A.js +1 -0
  24. package/dist/public/assets/WorkbenchLandingPage-DyPei0e-.js +1 -0
  25. package/dist/public/assets/WorkbenchLayout-DlbgBT3n.js +4 -0
  26. package/dist/public/assets/WorkbenchModal-LNfB69qx.js +1 -0
  27. package/dist/public/assets/WorkbenchShellRoute-BsxumYx5.js +1 -0
  28. package/dist/public/assets/WorkbenchShellRoute-DhQo_0vu.css +1 -0
  29. package/dist/public/assets/WorkspaceDebugDetailPage-CaXj5zVI.js +1 -0
  30. package/dist/public/assets/WorkspaceDetailPage-DOexuuaw.js +1 -0
  31. package/dist/public/assets/WorkspaceHomePage-DkCHNjKD.js +1 -0
  32. package/dist/public/assets/{client-runtime-manager-BZpL17fc.js → client-runtime-manager-6OoYHXGd.js} +1 -1
  33. package/dist/public/assets/{file-tree-icon-Db5LXC8h.js → file-tree-icon-9pt1OStn.js} +1 -1
  34. package/dist/public/assets/index-BwlbvwaA.css +1 -0
  35. package/dist/public/assets/index-DSw-TkQL.js +42 -0
  36. package/dist/public/assets/legna-code-6TqgZ4Ls.png +0 -0
  37. package/dist/public/assets/{login-direct-candidate-resolver-1mxe_Oh8.js → login-direct-candidate-resolver-CLlYtBRq.js} +1 -1
  38. package/dist/public/assets/model-switch-api-C-l8-E1S.js +1 -0
  39. package/dist/public/assets/{preferences-service-DWnzl5a0.js → preferences-service-BCcfYP_d.js} +1 -1
  40. package/dist/public/assets/{relay-entry-C5_Iay0I.js → relay-entry-BmLkMKuq.js} +1 -1
  41. package/dist/public/assets/session-runtime-machine-DgtvREca.js +21 -0
  42. package/dist/public/assets/{terminal-runtime-meta-cdtWVfCm.js → terminal-runtime-meta-0h-75uRy.js} +1 -1
  43. package/dist/public/assets/useRegisteredDebugTemplates-rBVmAqh3.js +1 -0
  44. package/dist/public/index.html +2 -2
  45. package/dist/server/config/env.d.ts +2 -0
  46. package/dist/server/config/env.js +7 -0
  47. package/dist/server/config/env.js.map +1 -1
  48. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +4 -1
  49. package/dist/server/modules/assistant-capability/assistant-capability-service.js +12 -1
  50. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  51. package/dist/server/modules/butler/butler-control-session-service.d.ts +4 -1
  52. package/dist/server/modules/butler/butler-control-session-service.js +17 -1
  53. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  54. package/dist/server/modules/butler/butler-profile-service.d.ts +3 -1
  55. package/dist/server/modules/butler/butler-profile-service.js +16 -4
  56. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  57. package/dist/server/modules/model-switch/cc-switch-adapter.d.ts +7 -0
  58. package/dist/server/modules/model-switch/cc-switch-adapter.js +17 -0
  59. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
  60. package/dist/server/modules/opencli/opencli-bridge-skill-service.d.ts +12 -0
  61. package/dist/server/modules/opencli/opencli-bridge-skill-service.js +124 -0
  62. package/dist/server/modules/opencli/opencli-bridge-skill-service.js.map +1 -0
  63. package/dist/server/modules/opencli/opencli-catalog-service.d.ts +50 -0
  64. package/dist/server/modules/opencli/opencli-catalog-service.js +345 -0
  65. package/dist/server/modules/opencli/opencli-catalog-service.js.map +1 -0
  66. package/dist/server/modules/opencli/opencli-controller.d.ts +13 -0
  67. package/dist/server/modules/opencli/opencli-controller.js +30 -0
  68. package/dist/server/modules/opencli/opencli-controller.js.map +1 -0
  69. package/dist/server/modules/opencli/opencli-health-service.d.ts +28 -0
  70. package/dist/server/modules/opencli/opencli-health-service.js +106 -0
  71. package/dist/server/modules/opencli/opencli-health-service.js.map +1 -0
  72. package/dist/server/modules/opencli/opencli-install-discovery.d.ts +23 -0
  73. package/dist/server/modules/opencli/opencli-install-discovery.js +130 -0
  74. package/dist/server/modules/opencli/opencli-install-discovery.js.map +1 -0
  75. package/dist/server/modules/opencli/opencli-management-service.d.ts +59 -0
  76. package/dist/server/modules/opencli/opencli-management-service.js +152 -0
  77. package/dist/server/modules/opencli/opencli-management-service.js.map +1 -0
  78. package/dist/server/modules/opencli/opencli-runtime-builder.d.ts +11 -0
  79. package/dist/server/modules/opencli/opencli-runtime-builder.js +214 -0
  80. package/dist/server/modules/opencli/opencli-runtime-builder.js.map +1 -0
  81. package/dist/server/modules/opencli/opencli-runtime-layout.d.ts +3 -0
  82. package/dist/server/modules/opencli/opencli-runtime-layout.js +11 -0
  83. package/dist/server/modules/opencli/opencli-runtime-layout.js.map +1 -0
  84. package/dist/server/modules/opencli/opencli-runtime-profile-service.d.ts +29 -0
  85. package/dist/server/modules/opencli/opencli-runtime-profile-service.js +104 -0
  86. package/dist/server/modules/opencli/opencli-runtime-profile-service.js.map +1 -0
  87. package/dist/server/modules/opencli/opencli-runtime-resolver.d.ts +29 -0
  88. package/dist/server/modules/opencli/opencli-runtime-resolver.js +110 -0
  89. package/dist/server/modules/opencli/opencli-runtime-resolver.js.map +1 -0
  90. package/dist/server/modules/opencli/opencli-session-prompt-service.d.ts +11 -0
  91. package/dist/server/modules/opencli/opencli-session-prompt-service.js +66 -0
  92. package/dist/server/modules/opencli/opencli-session-prompt-service.js.map +1 -0
  93. package/dist/server/modules/parallel-sessions/parallel-session-controller.d.ts +4 -0
  94. package/dist/server/modules/parallel-sessions/parallel-session-controller.js +7 -0
  95. package/dist/server/modules/parallel-sessions/parallel-session-controller.js.map +1 -1
  96. package/dist/server/modules/parallel-sessions/parallel-session-group-service.d.ts +6 -1
  97. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js +36 -2
  98. package/dist/server/modules/parallel-sessions/parallel-session-group-service.js.map +1 -1
  99. package/dist/server/modules/provider/opencode-model-options.d.ts +1 -0
  100. package/dist/server/modules/provider/opencode-model-options.js +54 -12
  101. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  102. package/dist/server/modules/provider/provider-catalog-service.d.ts +46 -0
  103. package/dist/server/modules/provider/provider-catalog-service.js +249 -0
  104. package/dist/server/modules/provider/provider-catalog-service.js.map +1 -0
  105. package/dist/server/modules/provider/provider-controller.d.ts +20 -2
  106. package/dist/server/modules/provider/provider-controller.js +65 -5
  107. package/dist/server/modules/provider/provider-controller.js.map +1 -1
  108. package/dist/server/modules/provider/provider-disabled.d.ts +8 -0
  109. package/dist/server/modules/provider/provider-disabled.js +45 -0
  110. package/dist/server/modules/provider/provider-disabled.js.map +1 -0
  111. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +3 -0
  112. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  113. package/dist/server/modules/provider/provider-discovery-helper-process.js +4 -4
  114. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  115. package/dist/server/modules/provider/provider-discovery-runtime.d.ts +1 -1
  116. package/dist/server/modules/provider/provider-discovery-runtime.js +17 -9
  117. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -1
  118. package/dist/server/modules/sessions/claude-compatible-provider-registry.d.ts +16 -0
  119. package/dist/server/modules/sessions/claude-compatible-provider-registry.js +91 -0
  120. package/dist/server/modules/sessions/claude-compatible-provider-registry.js.map +1 -0
  121. package/dist/server/modules/sessions/claude-runtime-helper-client.d.ts +1 -0
  122. package/dist/server/modules/sessions/claude-runtime-helper-client.js +14 -0
  123. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  124. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +1 -0
  125. package/dist/server/modules/sessions/codex-app-server-helper-client.js +10 -0
  126. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  127. package/dist/server/modules/sessions/provider-session-delete-cli.js +2 -0
  128. package/dist/server/modules/sessions/provider-session-delete-cli.js.map +1 -1
  129. package/dist/server/modules/sessions/session-controller.d.ts +7 -0
  130. package/dist/server/modules/sessions/session-controller.js +22 -0
  131. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  132. package/dist/server/modules/sessions/session-history-service.d.ts +18 -2
  133. package/dist/server/modules/sessions/session-history-service.js +330 -29
  134. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  135. package/dist/server/modules/sessions/session-live-runtime-router-service.js +9 -4
  136. package/dist/server/modules/sessions/session-live-runtime-router-service.js.map +1 -1
  137. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +22 -5
  138. package/dist/server/modules/sessions/session-live-runtime-service.js +350 -122
  139. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  140. package/dist/server/modules/sessions/session-message-attachment-service.js +2 -2
  141. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -1
  142. package/dist/server/modules/sessions/session-permission-request-service.d.ts +5 -2
  143. package/dist/server/modules/sessions/session-permission-request-service.js +26 -27
  144. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  145. package/dist/server/modules/sessions/session-provider-config-service.d.ts +82 -0
  146. package/dist/server/modules/sessions/session-provider-config-service.js +925 -0
  147. package/dist/server/modules/sessions/session-provider-config-service.js.map +1 -0
  148. package/dist/server/modules/sessions/session-provider-error-mapper.d.ts +2 -0
  149. package/dist/server/modules/sessions/session-provider-error-mapper.js +42 -0
  150. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  151. package/dist/server/modules/skills/skill-manager-service.d.ts +5 -0
  152. package/dist/server/modules/skills/skill-manager-service.js +26 -0
  153. package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
  154. package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +1 -0
  155. package/dist/server/modules/tasks/task-helper-process-handlers.js +1 -1
  156. package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -1
  157. package/dist/server/routes/opencli.d.ts +3 -0
  158. package/dist/server/routes/opencli.js +7 -0
  159. package/dist/server/routes/opencli.js.map +1 -0
  160. package/dist/server/routes/providers.js +4 -2
  161. package/dist/server/routes/providers.js.map +1 -1
  162. package/dist/server/server/create-server.d.ts +8 -0
  163. package/dist/server/server/create-server.js +48 -12
  164. package/dist/server/server/create-server.js.map +1 -1
  165. package/dist/server/storage/repositories/opencli-catalog-entry-repository.d.ts +9 -0
  166. package/dist/server/storage/repositories/opencli-catalog-entry-repository.js +85 -0
  167. package/dist/server/storage/repositories/opencli-catalog-entry-repository.js.map +1 -0
  168. package/dist/server/storage/repositories/opencli-provider-repository.d.ts +8 -0
  169. package/dist/server/storage/repositories/opencli-provider-repository.js +88 -0
  170. package/dist/server/storage/repositories/opencli-provider-repository.js.map +1 -0
  171. package/dist/server/storage/repositories/opencli-runtime-profile-repository.d.ts +11 -0
  172. package/dist/server/storage/repositories/opencli-runtime-profile-repository.js +127 -0
  173. package/dist/server/storage/repositories/opencli-runtime-profile-repository.js.map +1 -0
  174. package/dist/server/storage/repositories/provider-control-repository.d.ts +9 -0
  175. package/dist/server/storage/repositories/provider-control-repository.js +51 -0
  176. package/dist/server/storage/repositories/provider-control-repository.js.map +1 -0
  177. package/dist/server/storage/repositories/session-binding-repository.js +44 -5
  178. package/dist/server/storage/repositories/session-binding-repository.js.map +1 -1
  179. package/dist/server/storage/repositories/session-index-repository.js +6 -0
  180. package/dist/server/storage/repositories/session-index-repository.js.map +1 -1
  181. package/dist/server/storage/sqlite/client.js +72 -0
  182. package/dist/server/storage/sqlite/client.js.map +1 -1
  183. package/dist/server/storage/sqlite/schema.sql +71 -0
  184. package/dist/server/types/domain.d.ts +56 -0
  185. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.d.ts +5 -2
  186. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js +40 -8
  187. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js.map +1 -1
  188. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +2 -0
  189. package/node_modules/@codingns/session-sync-core/dist/index.js +2 -0
  190. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -1
  191. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +10 -1
  192. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +110 -35
  193. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  194. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.d.ts +11 -0
  195. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js +105 -0
  196. package/node_modules/@codingns/session-sync-core/dist/providers/claude-session-store.js.map +1 -0
  197. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +131 -39
  198. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  199. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.d.ts +9 -0
  200. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.js +17 -0
  201. package/node_modules/@codingns/session-sync-core/dist/providers/legna-code.js.map +1 -0
  202. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.d.ts +8 -1
  203. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js +19 -6
  204. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js.map +1 -1
  205. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +1 -0
  206. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +13 -8
  207. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  208. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.d.ts +5 -1
  209. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +103 -51
  210. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  211. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +2 -1
  212. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +41 -21
  213. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  214. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js +32 -8
  215. package/node_modules/@codingns/session-sync-core/dist/runtime/gemini-runtime.js.map +1 -1
  216. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.d.ts +15 -0
  217. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.js +16 -0
  218. package/node_modules/@codingns/session-sync-core/dist/runtime/legna-runtime.js.map +1 -0
  219. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js +167 -10
  220. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js.map +1 -1
  221. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +2 -0
  222. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +1 -1
  223. package/node_modules/@codingns/session-sync-core/dist/types.js +1 -1
  224. package/node_modules/@codingns/session-sync-core/dist/types.js.map +1 -1
  225. package/package.json +1 -1
  226. package/dist/public/assets/AdaptiveButlerPage-uFwDdN-F.js +0 -3
  227. package/dist/public/assets/ConversationPage-z3sXtKZ7.js +0 -4
  228. package/dist/public/assets/DesktopWindowPage-CZcoGApB.js +0 -2
  229. package/dist/public/assets/FileContextPanel-C3qex8bb.js +0 -1
  230. package/dist/public/assets/GitSidebar-BK6H16XU.js +0 -6
  231. package/dist/public/assets/MobileCreateSessionSheet-BYfbvK8o.js +0 -1
  232. package/dist/public/assets/MobileSheet-Ckug8hTb.js +0 -1
  233. package/dist/public/assets/MobileWorkspaceSwitcherHeader-RqWrBdn2.js +0 -1
  234. package/dist/public/assets/SessionIndexPage-DuK10DL5.js +0 -1
  235. package/dist/public/assets/SettingsPage-fyD-xaHL.js +0 -1
  236. package/dist/public/assets/TerminalManagerPanel-CCLr1Ypk.js +0 -1
  237. package/dist/public/assets/TerminalRuntimeFallbackModal-aUzjEBwP.js +0 -1
  238. package/dist/public/assets/ToolGitPage-C264yjS9.js +0 -1
  239. package/dist/public/assets/ToolProcessesPage-BOP4A1cb.js +0 -1
  240. package/dist/public/assets/ToolsHomePage-CQxGiKQA.js +0 -1
  241. package/dist/public/assets/WorkbenchLandingPage-CvAY68ca.js +0 -1
  242. package/dist/public/assets/WorkbenchLayout-DGm8Tc5M.js +0 -3
  243. package/dist/public/assets/WorkbenchModal-0tPIIhca.js +0 -1
  244. package/dist/public/assets/WorkbenchShellRoute-BF0nHWOk.css +0 -1
  245. package/dist/public/assets/WorkbenchShellRoute-DBBOsJo9.js +0 -1
  246. package/dist/public/assets/WorkspaceDebugDetailPage-CDerFYd2.js +0 -1
  247. package/dist/public/assets/WorkspaceDetailPage-BlJc1CHE.js +0 -1
  248. package/dist/public/assets/WorkspaceHomePage-BUsKJ3lv.js +0 -1
  249. package/dist/public/assets/default-session-permission-mode-DT4SGiwp.js +0 -1
  250. package/dist/public/assets/index-BZLcEHW3.js +0 -42
  251. package/dist/public/assets/index-BbspQPC2.css +0 -1
  252. package/dist/public/assets/session-runtime-machine-DdLeDqQr.js +0 -17
  253. package/dist/public/assets/useRegisteredDebugTemplates-oFAQNIqh.js +0 -1
@@ -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";
@@ -17,6 +17,7 @@ import { CodexModelOptionsService, createFallbackCodexModelOptions, enrichCodexC
17
17
  import { OpenCodeModelOptionsService, createFallbackOpenCodeModelOptions, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
18
18
  import { getSharedProviderDiscoveryHelperClient } from "../provider/provider-discovery-helper-client.js";
19
19
  import { discoverWorkspaceSessionsInRuntime } from "../provider/provider-discovery-runtime.js";
20
+ import { applyProviderDisabledState, createProviderCapabilityBlockedError } from "../provider/provider-disabled.js";
20
21
  import { createTaskManager } from "../tasks/task-manager.js";
21
22
  import { HOST_TASK_TYPES } from "../tasks/task-types.js";
22
23
  import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
@@ -25,15 +26,18 @@ const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "op
25
26
  const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
26
27
  const MAX_FORK_DEPTH = 4;
27
28
  const SYNTHETIC_CODEX_SESSION_CLEANUP_GRACE_MS = 120_000;
29
+ const GEMINI_RUNTIME_CHAT_DISCOVERY_GRACE_MS = 30_000;
28
30
  const SESSION_START_DEFERRED_PROVIDERS = new Set([
29
31
  "codex",
30
32
  "claude-code",
33
+ "legna-code",
31
34
  "opencode",
32
35
  "gemini",
33
36
  "kimi"
34
37
  ]);
35
38
  const MUTABLE_HISTORY_TAIL_PROVIDERS = new Set([
36
39
  "claude-code",
40
+ "legna-code",
37
41
  "codex",
38
42
  "gemini",
39
43
  "kimi",
@@ -75,6 +79,8 @@ export class SessionHistoryService {
75
79
  sessionIsolatedWorkspaceRepository;
76
80
  providerDiscoveryHelperClient = getSharedProviderDiscoveryHelperClient();
77
81
  providerSessionDiscoveryConfig;
82
+ sessionProviderConfigService;
83
+ providerControlRepository;
78
84
  taskManager;
79
85
  workspaceDiscoveryStatuses = new Map();
80
86
  workspaceStateRefreshStatuses = new Map();
@@ -84,7 +90,7 @@ export class SessionHistoryService {
84
90
  sessionDeletedObservers = new Set();
85
91
  workspaceSessionRelations = new Map();
86
92
  workspaceStateRefreshTaskSequence = 0;
87
- 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) {
93
+ 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, providerControlRepository = null) {
88
94
  this.db = db;
89
95
  this.workspaceRepository = workspaceRepository;
90
96
  this.sessionBindingRepository = sessionBindingRepository;
@@ -102,9 +108,18 @@ export class SessionHistoryService {
102
108
  this.parallelSessionGroupRepository = parallelSessionGroupRepository;
103
109
  this.parallelSessionMemberRepository = parallelSessionMemberRepository;
104
110
  this.sessionIsolatedWorkspaceRepository = sessionIsolatedWorkspaceRepository;
111
+ this.sessionProviderConfigService = sessionProviderConfigService;
112
+ this.providerControlRepository = providerControlRepository ?? {
113
+ get: (providerId) => ({
114
+ providerId: providerId.trim(),
115
+ enabled: true,
116
+ updatedAt: ""
117
+ })
118
+ };
105
119
  this.claudeCodeHomeDir = config.claudeCodeHomeDir;
106
120
  this.providerCliCommandPaths = {
107
121
  "claude-code": process.platform === "win32" ? "claude.cmd" : "claude",
122
+ "legna-code": config.legnaCodeCliPath,
108
123
  codex: config.codexCliPath,
109
124
  gemini: config.geminiCliPath,
110
125
  kimi: config.kimiCliPath
@@ -113,8 +128,10 @@ export class SessionHistoryService {
113
128
  this.providerCliAvailability = buildProviderCliAvailabilitySnapshot(this.providerCliCommandPaths);
114
129
  this.providerSessionDiscoveryConfig = {
115
130
  claudeCodeHomeDir: config.claudeCodeHomeDir,
131
+ legnaCodeHomeDir: config.legnaCodeHomeDir,
116
132
  codexCliPath: config.codexCliPath,
117
133
  codexHomeDir: config.codexHomeDir,
134
+ legnaCodeCliPath: config.legnaCodeCliPath,
118
135
  geminiCliPath: config.geminiCliPath,
119
136
  geminiHomeDir: config.geminiHomeDir,
120
137
  kimiDefaultModel: config.kimiDefaultModel,
@@ -125,6 +142,10 @@ export class SessionHistoryService {
125
142
  };
126
143
  this.providerRegistry = new ProviderRegistry([
127
144
  new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
145
+ new LegnaCodeAdapter({
146
+ homeDir: config.legnaCodeHomeDir,
147
+ legacyClaudeHomeDir: config.claudeCodeHomeDir
148
+ }),
128
149
  new CodexAdapter({
129
150
  homeDir: config.codexHomeDir,
130
151
  forkTransportFactory: adapterOverrides.codexForkTransportFactory
@@ -196,7 +217,7 @@ export class SessionHistoryService {
196
217
  executionLane: "helper_process",
197
218
  concurrency: WORKSPACE_DISCOVERY_SCAN_CONCURRENCY,
198
219
  helperProcessHandler: "session.workspace_discovery",
199
- run: async ({ config, workspacePath, knownSessions }, context) => await discoverWorkspaceSessionsInRuntime(config, workspacePath, knownSessions, context.signal)
220
+ run: async ({ config, workspacePath, knownSessions, enabledProviders }, context) => await discoverWorkspaceSessionsInRuntime(config, workspacePath, knownSessions, enabledProviders, context.signal)
200
221
  });
201
222
  }
202
223
  if (!this.taskManager.has(HOST_TASK_TYPES.providerCapabilityRefresh)) {
@@ -323,8 +344,8 @@ export class SessionHistoryService {
323
344
  ? current?.syncCursor ?? page.cursor
324
345
  : page.cursor,
325
346
  lastSyncAt: nowIso(),
326
- lastErrorCode: current?.lastErrorCode ?? null,
327
- lastErrorDetail: current?.lastErrorDetail ?? null,
347
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(current?.lastErrorCode ?? null),
348
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(current?.lastErrorCode ?? null, current?.lastErrorDetail ?? null),
328
349
  resumedAt: current?.resumedAt ?? null
329
350
  });
330
351
  snapshotIdleMs = Date.now() - snapshotIdleStartedAt;
@@ -441,11 +462,11 @@ export class SessionHistoryService {
441
462
  .listByWorkspace(workspaceId, userId)
442
463
  .filter((item) => !this.isPendingSessionAlias(item));
443
464
  const projectedItems = this.listProjectedIsolatedWorkspaceSessions(workspaceId, userId);
444
- return this.enrichSessionItems(workspaceId, sortSessionListItemsByRecentActivity(mergeSessionListItemsBySessionId([...directItems, ...projectedItems])));
465
+ return this.filterDisabledProviderSessions(this.enrichSessionItems(workspaceId, sortSessionListItemsByRecentActivity(mergeSessionListItemsBySessionId([...directItems, ...projectedItems]))));
445
466
  }
446
467
  getProviderCapabilitiesSnapshot(provider) {
447
468
  try {
448
- return this.resolveProviderCapabilitiesImmediate(this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider)), null);
469
+ return this.applyProviderEnabledState(this.resolveProviderCapabilitiesImmediate(this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider)), null));
449
470
  }
450
471
  catch (error) {
451
472
  throw mapSessionProviderError(error);
@@ -455,8 +476,17 @@ export class SessionHistoryService {
455
476
  try {
456
477
  const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
457
478
  const baseCapabilities = this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider));
479
+ if (baseCapabilities.provider === "opencode" && workspacePath) {
480
+ const refreshed = await this.enrichProviderCapabilities(baseCapabilities, workspacePath);
481
+ const cacheKey = buildProviderCapabilityCacheKey(baseCapabilities.provider, workspacePath);
482
+ this.providerCapabilityCache.set(cacheKey, {
483
+ refreshedAt: Date.now(),
484
+ value: refreshed
485
+ });
486
+ return this.applyProviderEnabledState(refreshed);
487
+ }
458
488
  this.scheduleProviderCapabilityRefresh(baseCapabilities, workspacePath);
459
- return this.resolveProviderCapabilitiesImmediate(baseCapabilities, workspacePath);
489
+ return this.applyProviderEnabledState(this.resolveProviderCapabilitiesImmediate(baseCapabilities, workspacePath));
460
490
  }
461
491
  catch (error) {
462
492
  throw mapSessionProviderError(error);
@@ -470,8 +500,19 @@ export class SessionHistoryService {
470
500
  .getSessionCapabilities(binding.provider, binding.providerSessionId)
471
501
  .then((capabilities) => {
472
502
  const normalizedCapabilities = this.applyProviderCliAvailability(capabilities);
503
+ if (normalizedCapabilities.provider === "opencode") {
504
+ return this.enrichProviderCapabilities(normalizedCapabilities, workspacePath)
505
+ .then((refreshed) => {
506
+ const cacheKey = buildProviderCapabilityCacheKey(normalizedCapabilities.provider, workspacePath);
507
+ this.providerCapabilityCache.set(cacheKey, {
508
+ refreshedAt: Date.now(),
509
+ value: refreshed
510
+ });
511
+ return this.applyProviderEnabledState(refreshed);
512
+ });
513
+ }
473
514
  this.scheduleProviderCapabilityRefresh(normalizedCapabilities, workspacePath);
474
- return this.resolveProviderCapabilitiesImmediate(normalizedCapabilities, workspacePath);
515
+ return this.applyProviderEnabledState(this.resolveProviderCapabilitiesImmediate(normalizedCapabilities, workspacePath));
475
516
  })
476
517
  .catch((error) => {
477
518
  throw mapSessionProviderError(error);
@@ -499,6 +540,9 @@ export class SessionHistoryService {
499
540
  return applyImmediateModelOptionFallbacks(claudeEnriched, this.codexModelOptionsService.peekSnapshot(), this.openCodeModelOptionsService.peekSnapshot(workspacePath));
500
541
  }
501
542
  scheduleProviderCapabilityRefresh(capabilities, workspacePath) {
543
+ if (!this.isProviderEnabled(capabilities.provider)) {
544
+ return;
545
+ }
502
546
  const cacheKey = buildProviderCapabilityCacheKey(capabilities.provider, workspacePath);
503
547
  const cached = this.providerCapabilityCache.get(cacheKey);
504
548
  if (cached &&
@@ -550,17 +594,31 @@ export class SessionHistoryService {
550
594
  limitations
551
595
  };
552
596
  }
597
+ applyProviderEnabledState(capabilities) {
598
+ if (this.isProviderEnabled(capabilities.provider)) {
599
+ return capabilities;
600
+ }
601
+ return applyProviderDisabledState(capabilities);
602
+ }
603
+ isProviderEnabled(provider) {
604
+ return this.providerControlRepository.get(provider.trim()).enabled;
605
+ }
606
+ filterDisabledProviderSessions(items) {
607
+ return items.filter((item) => this.isProviderEnabled(item.provider));
608
+ }
553
609
  assertProviderCapabilityEnabled(provider, capability, fallbackDetail) {
554
610
  const capabilities = this.getProviderCapabilitiesSnapshot(provider);
555
611
  if (capabilities[capability]) {
556
612
  return;
557
613
  }
558
- throw new AppError({
559
- statusCode: 409,
560
- errorCode: "PROVIDER_UNAVAILABLE",
561
- detail: capabilities.limitations[0] ?? fallbackDetail,
562
- field: "provider"
563
- });
614
+ throw createProviderCapabilityBlockedError(capabilities, "provider", fallbackDetail);
615
+ }
616
+ assertProviderSendEnabled(provider, field, fallbackDetail) {
617
+ const capabilities = this.getProviderCapabilitiesSnapshot(provider);
618
+ if (capabilities.canSendMessage) {
619
+ return;
620
+ }
621
+ throw createProviderCapabilityBlockedError(capabilities, field, fallbackDetail);
564
622
  }
565
623
  async getSessionContextUsage(sessionId) {
566
624
  const binding = this.getBindingOrThrow(sessionId);
@@ -597,6 +655,7 @@ export class SessionHistoryService {
597
655
  }
598
656
  }
599
657
  async startSession(input) {
658
+ this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
600
659
  if (SESSION_START_DEFERRED_PROVIDERS.has(input.provider)) {
601
660
  throw new AppError({
602
661
  statusCode: 409,
@@ -610,11 +669,17 @@ export class SessionHistoryService {
610
669
  async startSessionDirect(input) {
611
670
  const workspace = this.getWorkspaceOrThrow(input.workspaceId);
612
671
  this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
672
+ const sessionId = createId();
673
+ const providerBinding = this.prepareDirectSessionBinding({
674
+ sessionId,
675
+ provider: input.provider,
676
+ providerConfigMode: input.providerConfigMode ?? null,
677
+ providerPresetId: input.providerPresetId ?? null
678
+ });
613
679
  try {
614
- const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
680
+ const result = await this.startProviderSessionWithBinding(input.provider, workspace.path, providerBinding.runtimeHomeDir, {
615
681
  initialPrompt: input.initialPrompt
616
682
  });
617
- const sessionId = createId();
618
683
  const timestamp = nowIso();
619
684
  const persist = this.db.transaction(() => {
620
685
  this.sessionBindingRepository.upsert({
@@ -623,6 +688,9 @@ export class SessionHistoryService {
623
688
  provider: result.session.provider,
624
689
  providerSessionId: result.session.providerSessionId,
625
690
  rawStoreRef: result.session.rawStoreRef,
691
+ providerConfigMode: providerBinding.providerConfigMode,
692
+ providerPresetId: providerBinding.providerPresetId,
693
+ runtimeHomeDir: providerBinding.runtimeHomeDir,
626
694
  createdAt: timestamp,
627
695
  updatedAt: timestamp
628
696
  });
@@ -690,10 +758,18 @@ export class SessionHistoryService {
690
758
  });
691
759
  }
692
760
  this.assertForkDepthWithinLimit(input.sessionId);
693
- if (targetProvider !== binding.provider) {
761
+ const requestedTargetSelection = resolveRequestedProviderSelection({
762
+ existingBinding: targetProvider === binding.provider ? binding : null,
763
+ providerConfigMode: input.providerConfigMode ?? undefined,
764
+ providerPresetId: input.providerPresetId ?? undefined
765
+ });
766
+ if (targetProvider !== binding.provider
767
+ || !areEquivalentProviderBindingSelection(binding, requestedTargetSelection)) {
694
768
  return this.forkSessionAcrossProviders({
695
769
  ...input,
696
- targetProvider
770
+ targetProvider,
771
+ providerConfigMode: requestedTargetSelection.providerConfigMode,
772
+ providerPresetId: requestedTargetSelection.providerPresetId
697
773
  }, binding, sourceMessageId);
698
774
  }
699
775
  try {
@@ -713,6 +789,9 @@ export class SessionHistoryService {
713
789
  provider: result.session.provider,
714
790
  providerSessionId: result.session.providerSessionId,
715
791
  rawStoreRef: result.session.rawStoreRef,
792
+ providerConfigMode: binding.providerConfigMode,
793
+ providerPresetId: binding.providerPresetId,
794
+ runtimeHomeDir: binding.runtimeHomeDir,
716
795
  createdAt: timestamp,
717
796
  updatedAt: timestamp
718
797
  });
@@ -807,6 +886,8 @@ export class SessionHistoryService {
807
886
  userId: input.userId,
808
887
  provider: input.targetProvider,
809
888
  initialPrompt: inheritedPrompt,
889
+ providerConfigMode: input.providerConfigMode ?? null,
890
+ providerPresetId: input.providerPresetId ?? null,
810
891
  parentSessionId: input.sessionId,
811
892
  sessionKind: input.sessionKind ?? "default",
812
893
  annotationSourceMessageId: input.annotationSourceMessageId ?? null,
@@ -854,6 +935,40 @@ export class SessionHistoryService {
854
935
  this.workspaceSessionRelations.set(input.targetWorkspaceId?.trim() || sourceBinding.workspaceId, relationMap);
855
936
  return this.getSessionListItemOrThrow(startedSession.sessionId, input.userId);
856
937
  }
938
+ prepareDirectSessionBinding(input) {
939
+ if (!this.sessionProviderConfigService) {
940
+ return {
941
+ providerConfigMode: "global-default",
942
+ providerPresetId: null,
943
+ runtimeHomeDir: null
944
+ };
945
+ }
946
+ return this.sessionProviderConfigService.prepareSessionBinding({
947
+ sessionId: input.sessionId,
948
+ provider: input.provider,
949
+ providerConfigMode: input.providerConfigMode ?? undefined,
950
+ providerPresetId: input.providerPresetId ?? null
951
+ });
952
+ }
953
+ startProviderSessionWithBinding(provider, workspacePath, runtimeHomeDir, options) {
954
+ const scopedRuntimeHomeDir = runtimeHomeDir?.trim() || null;
955
+ if (!scopedRuntimeHomeDir) {
956
+ return this.sessionSyncService.startSession(provider, workspacePath, options);
957
+ }
958
+ switch (provider) {
959
+ case "claude-code":
960
+ return new ClaudeCodeAdapter({ homeDir: scopedRuntimeHomeDir }).startSession(workspacePath, options);
961
+ case "codex":
962
+ return new CodexAdapter({ homeDir: scopedRuntimeHomeDir }).startSession(workspacePath, options);
963
+ case "gemini":
964
+ return new GeminiAdapter({
965
+ homeDir: scopedRuntimeHomeDir,
966
+ commandPath: this.providerSessionDiscoveryConfig.geminiCliPath
967
+ }).startSession(workspacePath, options);
968
+ default:
969
+ return this.sessionSyncService.startSession(provider, workspacePath, options);
970
+ }
971
+ }
857
972
  async readForkSourceMessages(sessionId, binding, sourceType, sourceMessageId, sourceMessageSnapshot = null) {
858
973
  const messages = [];
859
974
  let cursor = null;
@@ -920,6 +1035,7 @@ export class SessionHistoryService {
920
1035
  }
921
1036
  async sendMessage(sessionId, content, clientRequestId, permissionMode = null) {
922
1037
  const binding = this.getBindingOrThrow(sessionId);
1038
+ this.assertProviderSendEnabled(binding.provider, "sessionId", "当前 provider 不支持继续发送消息");
923
1039
  const result = await this.sessionSyncService
924
1040
  .sendMessage(binding.provider, binding.providerSessionId, binding.rawStoreRef, content, clientRequestId, permissionMode)
925
1041
  .catch((error) => {
@@ -1024,13 +1140,14 @@ export class SessionHistoryService {
1024
1140
  return null;
1025
1141
  }
1026
1142
  await this.syncSessionTitleFromProvider(sessionId, binding);
1143
+ const snapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1027
1144
  this.upsertSnapshot(sessionId, {
1028
1145
  syncStatus: "idle",
1029
1146
  syncCursor: page.cursor,
1030
1147
  lastSyncAt: nowIso(),
1031
- lastErrorCode: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.lastErrorCode ?? null,
1032
- lastErrorDetail: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.lastErrorDetail ?? null,
1033
- resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
1148
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(snapshot?.lastErrorCode ?? null),
1149
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(snapshot?.lastErrorCode ?? null, snapshot?.lastErrorDetail ?? null),
1150
+ resumedAt: snapshot?.resumedAt ?? null
1034
1151
  });
1035
1152
  return {
1036
1153
  type: "session.delta",
@@ -1244,6 +1361,15 @@ export class SessionHistoryService {
1244
1361
  provider: resolvedSnapshot.provider,
1245
1362
  providerSessionId: resolvedSnapshot.providerSessionId,
1246
1363
  rawStoreRef: resolvedSnapshot.rawStoreRef,
1364
+ providerConfigMode: currentBinding?.providerConfigMode
1365
+ ?? duplicateBinding?.providerConfigMode
1366
+ ?? "global-default",
1367
+ providerPresetId: currentBinding?.providerPresetId
1368
+ ?? duplicateBinding?.providerPresetId
1369
+ ?? null,
1370
+ runtimeHomeDir: currentBinding?.runtimeHomeDir
1371
+ ?? duplicateBinding?.runtimeHomeDir
1372
+ ?? null,
1247
1373
  createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
1248
1374
  ?? timestamp,
1249
1375
  updatedAt: timestamp
@@ -1285,14 +1411,19 @@ export class SessionHistoryService {
1285
1411
  try {
1286
1412
  const discoverStartedAt = Date.now();
1287
1413
  const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
1288
- const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions, workspace.path);
1414
+ const enabledProviders = this.providerRegistry
1415
+ .list()
1416
+ .map((adapter) => adapter.providerId)
1417
+ .filter((providerId) => this.isProviderEnabled(providerId));
1418
+ const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions.filter((session) => enabledProviders.includes(session.provider)), workspace.path);
1289
1419
  const discoveryHandle = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscoveryScan, {
1290
1420
  key: workspaceId,
1291
1421
  source: "session_history.workspace_discovery.scan",
1292
1422
  input: {
1293
1423
  config: this.providerSessionDiscoveryConfig,
1294
1424
  workspacePath: workspace.path,
1295
- knownSessions
1425
+ knownSessions,
1426
+ enabledProviders
1296
1427
  }
1297
1428
  });
1298
1429
  const discovery = await awaitTaskHandleWithSignal(discoveryHandle, signal).catch((error) => {
@@ -1335,6 +1466,9 @@ export class SessionHistoryService {
1335
1466
  provider: session.provider,
1336
1467
  providerSessionId: session.providerSessionId,
1337
1468
  rawStoreRef: session.rawStoreRef,
1469
+ providerConfigMode: existing?.providerConfigMode ?? "global-default",
1470
+ providerPresetId: existing?.providerPresetId ?? null,
1471
+ runtimeHomeDir: existing?.runtimeHomeDir ?? null,
1338
1472
  createdAt,
1339
1473
  updatedAt: timestamp
1340
1474
  };
@@ -1614,9 +1748,24 @@ export class SessionHistoryService {
1614
1748
  total: 0
1615
1749
  };
1616
1750
  }
1751
+ if (this.shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, provider, error)) {
1752
+ return {
1753
+ messages: [],
1754
+ cursor,
1755
+ nextCursor: null,
1756
+ total: 0
1757
+ };
1758
+ }
1617
1759
  throw mapSessionProviderError(error);
1618
1760
  });
1619
1761
  }
1762
+ shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, provider, error) {
1763
+ if (provider !== "gemini" || !isGeminiChatNotFoundError(error)) {
1764
+ return false;
1765
+ }
1766
+ const sessionIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
1767
+ return this.listSessionStatesBySessionId(sessionId).some((state) => shouldTreatMissingGeminiRuntimeHistoryStateAsTransient(state, sessionIndex));
1768
+ }
1620
1769
  enrichMessagesWithOrigin(sessionId, messages) {
1621
1770
  return this.resolveMessageOrigins(sessionId, messages);
1622
1771
  }
@@ -1909,8 +2058,8 @@ export class SessionHistoryService {
1909
2058
  syncStatus: "idle",
1910
2059
  syncCursor: page.cursor,
1911
2060
  lastSyncAt: nowIso(),
1912
- lastErrorCode: snapshot?.lastErrorCode ?? null,
1913
- lastErrorDetail: snapshot?.lastErrorDetail ?? null,
2061
+ lastErrorCode: clearSuccessfulProviderReadErrorCode(snapshot?.lastErrorCode ?? null),
2062
+ lastErrorDetail: clearSuccessfulProviderReadErrorDetail(snapshot?.lastErrorCode ?? null, snapshot?.lastErrorDetail ?? null),
1914
2063
  resumedAt: snapshot?.resumedAt ?? null
1915
2064
  });
1916
2065
  await onEnvelope({
@@ -1936,7 +2085,12 @@ export class SessionHistoryService {
1936
2085
  provider: binding.provider,
1937
2086
  providerSessionId: binding.providerSessionId,
1938
2087
  rawStoreRef: binding.rawStoreRef
1939
- }, signal)).trim();
2088
+ }, signal).catch((error) => {
2089
+ if (this.shouldTreatMissingGeminiRuntimeHistoryAsEmpty(sessionId, binding.provider, error)) {
2090
+ return "";
2091
+ }
2092
+ throw error;
2093
+ })).trim();
1940
2094
  const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
1941
2095
  if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
1942
2096
  return;
@@ -2371,6 +2525,9 @@ export class SessionHistoryService {
2371
2525
  provider: input.provider,
2372
2526
  providerSessionId: buildPendingBindingValue(input.provider, input.targetSessionId),
2373
2527
  rawStoreRef: buildPendingBindingValue(input.provider, input.targetSessionId),
2528
+ providerConfigMode: sourceBinding.providerConfigMode,
2529
+ providerPresetId: sourceBinding.providerPresetId,
2530
+ runtimeHomeDir: sourceBinding.runtimeHomeDir,
2374
2531
  createdAt: sourceBinding.createdAt,
2375
2532
  updatedAt: input.timestamp
2376
2533
  });
@@ -2452,6 +2609,9 @@ export class SessionHistoryService {
2452
2609
  provider: sourceBinding.provider,
2453
2610
  providerSessionId: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2454
2611
  rawStoreRef: buildAliasBindingValue(input.provider, input.targetSessionId, input.sourceSessionId),
2612
+ providerConfigMode: sourceBinding.providerConfigMode,
2613
+ providerPresetId: sourceBinding.providerPresetId,
2614
+ runtimeHomeDir: sourceBinding.runtimeHomeDir,
2455
2615
  createdAt: sourceBinding.createdAt,
2456
2616
  updatedAt: input.timestamp
2457
2617
  });
@@ -2690,7 +2850,9 @@ export class SessionHistoryService {
2690
2850
  const liveObservation = this.resolveLiveActivityObservation(sessionId);
2691
2851
  const inspection = liveObservation
2692
2852
  ? null
2693
- : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2853
+ : binding.provider === "gemini"
2854
+ ? await this.inspectGeminiHistoryActivity(sessionId, binding)
2855
+ : inspectSessionActivity(binding.provider, binding.rawStoreRef);
2694
2856
  if (inspection) {
2695
2857
  const nowMs = Date.parse(timestamp);
2696
2858
  if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
@@ -2753,6 +2915,15 @@ export class SessionHistoryService {
2753
2915
  });
2754
2916
  return nextRecord;
2755
2917
  }
2918
+ async inspectGeminiHistoryActivity(sessionId, binding) {
2919
+ try {
2920
+ 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);
2921
+ return inferGeminiInspectionFromHistory(page.messages);
2922
+ }
2923
+ catch {
2924
+ return inspectSessionActivity(binding.provider, binding.rawStoreRef);
2925
+ }
2926
+ }
2756
2927
  async repairCodexDirtyBindingBeforeHistoryRead(sessionId, userId, binding) {
2757
2928
  if (!shouldRepairCodexDirtyBinding(binding)) {
2758
2929
  this.codexDirtyBindingRepairStates.delete(sessionId);
@@ -2829,7 +3000,11 @@ export class SessionHistoryService {
2829
3000
  }
2830
3001
  }
2831
3002
  function isProviderCliBacked(provider) {
2832
- return provider === "claude-code" || provider === "codex" || provider === "gemini" || provider === "kimi";
3003
+ return provider === "claude-code"
3004
+ || provider === "legna-code"
3005
+ || provider === "codex"
3006
+ || provider === "gemini"
3007
+ || provider === "kimi";
2833
3008
  }
2834
3009
  function buildProviderCliAvailabilitySnapshot(commandPaths) {
2835
3010
  return Object.freeze(Object.fromEntries(Object.entries(commandPaths).map(([provider, commandPath]) => [
@@ -2841,6 +3016,8 @@ function buildProviderCliUnavailableMessage(provider) {
2841
3016
  switch (provider) {
2842
3017
  case "claude-code":
2843
3018
  return "未检测到 Claude CLI";
3019
+ case "legna-code":
3020
+ return "未检测到 Legna CLI";
2844
3021
  case "codex":
2845
3022
  return "未检测到 Codex CLI";
2846
3023
  case "gemini":
@@ -2900,6 +3077,46 @@ function hasInspectionEvidence(inspection) {
2900
3077
  || !!inspection.lastEventAt
2901
3078
  || !!inspection.completedAtCandidate;
2902
3079
  }
3080
+ function inferGeminiInspectionFromHistory(messages) {
3081
+ let lastEventAt = null;
3082
+ let lastUserAt = null;
3083
+ let latestNonUserMessage = null;
3084
+ const pendingToolCallIds = new Set();
3085
+ for (const message of messages) {
3086
+ lastEventAt = pickLaterIso(lastEventAt, message.timestamp);
3087
+ if (message.role === "user") {
3088
+ lastUserAt = pickLaterIso(lastUserAt, message.timestamp);
3089
+ continue;
3090
+ }
3091
+ latestNonUserMessage = message;
3092
+ if (message.kind === "tool_call" && message.toolCall?.callId) {
3093
+ pendingToolCallIds.add(message.toolCall.callId);
3094
+ continue;
3095
+ }
3096
+ if (message.kind === "tool_result" && message.toolCall?.callId) {
3097
+ pendingToolCallIds.delete(message.toolCall.callId);
3098
+ }
3099
+ }
3100
+ const latestNonUserAt = latestNonUserMessage?.timestamp ?? null;
3101
+ const hasReplyAfterLatestUser = !!latestNonUserAt && (!lastUserAt || latestNonUserAt.localeCompare(lastUserAt) >= 0);
3102
+ const isTerminalReplyKind = latestNonUserMessage?.kind === "text" || latestNonUserMessage?.kind === "tool_result";
3103
+ const completedAtCandidate = hasReplyAfterLatestUser && isTerminalReplyKind && pendingToolCallIds.size === 0
3104
+ ? latestNonUserAt
3105
+ : null;
3106
+ const hasOpenTurn = !completedAtCandidate
3107
+ && hasReplyAfterLatestUser
3108
+ && (pendingToolCallIds.size > 0
3109
+ || latestNonUserMessage?.kind === "thinking"
3110
+ || latestNonUserMessage?.kind === "tool_call");
3111
+ return {
3112
+ runningState: hasOpenTurn ? "running" : "idle",
3113
+ hasPendingTools: pendingToolCallIds.size > 0,
3114
+ lastEventAt,
3115
+ completedAtCandidate,
3116
+ errorCode: null,
3117
+ errorDetail: null
3118
+ };
3119
+ }
2903
3120
  function applySessionActivityResolution(item, resolution) {
2904
3121
  const rawResolvedRunningState = resolution.runningState === "unknown" && item.runningState === null
2905
3122
  ? null
@@ -3453,6 +3670,40 @@ function shouldTreatMissingSyntheticHistoryAsEmpty(provider, rawStoreRef, error)
3453
3670
  const detail = error instanceof Error ? error.message : String(error);
3454
3671
  return detail.includes("ENOENT");
3455
3672
  }
3673
+ function shouldTreatMissingGeminiRuntimeHistoryStateAsTransient(state, sessionIndex) {
3674
+ if (state.activitySource !== "runtime") {
3675
+ return false;
3676
+ }
3677
+ if (state.runningState === "starting" || state.runningState === "running") {
3678
+ return true;
3679
+ }
3680
+ if ((sessionIndex?.messageCount ?? 0) !== 0) {
3681
+ return false;
3682
+ }
3683
+ return isWithinGeminiRuntimeChatDiscoveryGraceWindow(state.lastEventAt ?? state.updatedAt ?? sessionIndex?.updatedAt ?? sessionIndex?.createdAt ?? null);
3684
+ }
3685
+ function isWithinGeminiRuntimeChatDiscoveryGraceWindow(timestamp) {
3686
+ if (!timestamp) {
3687
+ return false;
3688
+ }
3689
+ const timestampMs = Date.parse(timestamp);
3690
+ if (!Number.isFinite(timestampMs)) {
3691
+ return false;
3692
+ }
3693
+ return Date.now() - timestampMs <= GEMINI_RUNTIME_CHAT_DISCOVERY_GRACE_MS;
3694
+ }
3695
+ function isGeminiChatNotFoundError(error) {
3696
+ if (error instanceof AppError) {
3697
+ return error.errorCode === "GEMINI_CHAT_NOT_FOUND";
3698
+ }
3699
+ return error instanceof Error && error.message === "GEMINI_CHAT_NOT_FOUND";
3700
+ }
3701
+ function clearSuccessfulProviderReadErrorCode(errorCode) {
3702
+ return errorCode === "PROVIDER_READ_FAILED" ? null : errorCode;
3703
+ }
3704
+ function clearSuccessfulProviderReadErrorDetail(errorCode, errorDetail) {
3705
+ return errorCode === "PROVIDER_READ_FAILED" ? null : errorDetail;
3706
+ }
3456
3707
  function shouldShortCircuitMissingSyntheticCodexHistory(provider, rawStoreRef) {
3457
3708
  return provider === "codex" && isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
3458
3709
  }
@@ -3599,6 +3850,9 @@ function isSyntheticCodexSessionTitle(title) {
3599
3850
  return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
3600
3851
  /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
3601
3852
  }
3853
+ function isSyntheticGeminiSessionTitle(title) {
3854
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title);
3855
+ }
3602
3856
  function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3603
3857
  const normalizedTitle = currentTitle?.trim() ?? "";
3604
3858
  if (normalizedTitle.length === 0) {
@@ -3607,6 +3861,9 @@ function shouldSyncSessionTitleFromProvider(provider, currentTitle) {
3607
3861
  if (provider === "codex" && isSyntheticCodexSessionTitle(normalizedTitle)) {
3608
3862
  return true;
3609
3863
  }
3864
+ if (provider === "gemini" && isSyntheticGeminiSessionTitle(normalizedTitle)) {
3865
+ return true;
3866
+ }
3610
3867
  return false;
3611
3868
  }
3612
3869
  function shouldRemoveHiddenClaudeDebugSession(session) {
@@ -3618,6 +3875,7 @@ function shouldRemoveHiddenClaudeDebugSession(session) {
3618
3875
  /\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
3619
3876
  }
3620
3877
  const STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS = 120_000;
3878
+ const GEMINI_ACTIVITY_INFERENCE_HISTORY_LIMIT = 80;
3621
3879
  function shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs) {
3622
3880
  if (!current || current.activitySource !== "runtime") {
3623
3881
  return false;
@@ -3795,8 +4053,51 @@ function areEquivalentSessionBindings(current, next) {
3795
4053
  current.provider === next.provider &&
3796
4054
  current.providerSessionId === next.providerSessionId &&
3797
4055
  current.rawStoreRef === next.rawStoreRef &&
4056
+ current.providerConfigMode === next.providerConfigMode &&
4057
+ current.providerPresetId === next.providerPresetId &&
4058
+ current.runtimeHomeDir === next.runtimeHomeDir &&
3798
4059
  current.createdAt === next.createdAt);
3799
4060
  }
4061
+ function resolveRequestedProviderSelection(input) {
4062
+ const existingSelection = input.existingBinding
4063
+ ? {
4064
+ providerConfigMode: input.existingBinding.providerConfigMode,
4065
+ providerPresetId: input.existingBinding.providerPresetId
4066
+ }
4067
+ : null;
4068
+ const normalizedPresetId = input.providerPresetId?.trim() || null;
4069
+ if (input.providerConfigMode === undefined && input.providerPresetId === undefined) {
4070
+ return existingSelection ?? {
4071
+ providerConfigMode: "global-default",
4072
+ providerPresetId: null
4073
+ };
4074
+ }
4075
+ const providerConfigMode = input.providerConfigMode
4076
+ ?? (normalizedPresetId ? "cc-switch-preset" : "global-default");
4077
+ if (providerConfigMode === "global-default") {
4078
+ return {
4079
+ providerConfigMode,
4080
+ providerPresetId: null
4081
+ };
4082
+ }
4083
+ const providerPresetId = normalizedPresetId ?? existingSelection?.providerPresetId ?? null;
4084
+ if (!providerPresetId) {
4085
+ throw new AppError({
4086
+ statusCode: 400,
4087
+ errorCode: "INVALID_INPUT",
4088
+ detail: "使用 cc-switch preset 时必须提供 providerPresetId",
4089
+ field: "providerPresetId"
4090
+ });
4091
+ }
4092
+ return {
4093
+ providerConfigMode,
4094
+ providerPresetId
4095
+ };
4096
+ }
4097
+ function areEquivalentProviderBindingSelection(binding, selection) {
4098
+ return (binding.providerConfigMode === selection.providerConfigMode
4099
+ && binding.providerPresetId === selection.providerPresetId);
4100
+ }
3800
4101
  function areEquivalentSessionIndexRecords(current, next) {
3801
4102
  if (!current) {
3802
4103
  return false;