@jingyi0605/codingns 0.1.5 → 0.2.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 (299) hide show
  1. package/README.md +44 -0
  2. package/bin/codingns.mjs +640 -53
  3. package/dist/public/assets/{TerminalPage-4p6EBqrR.js → TerminalPage-BkjqU9NG.js} +19 -19
  4. package/dist/public/assets/index-C6U8-9jg.css +1 -0
  5. package/dist/public/assets/index-CKSumuV2.js +109 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/config/env.d.ts +2 -0
  8. package/dist/server/config/env.js +8 -1
  9. package/dist/server/config/env.js.map +1 -1
  10. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +89 -0
  11. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +138 -0
  12. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -0
  13. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +115 -0
  14. package/dist/server/modules/assistant-capability/assistant-capability-service.js +241 -0
  15. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -0
  16. package/dist/server/modules/butler/butler-control-session-service.js +69 -30
  17. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  18. package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +9 -0
  19. package/dist/server/modules/butler/butler-follow-up-scheduler.js +47 -11
  20. package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -1
  21. package/dist/server/modules/butler/butler-follow-up-service.d.ts +7 -1
  22. package/dist/server/modules/butler/butler-follow-up-service.js +10 -0
  23. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  24. package/dist/server/modules/butler/butler-session-service.d.ts +2 -1
  25. package/dist/server/modules/butler/butler-session-service.js +10 -1
  26. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  27. package/dist/server/modules/butler/butler-session-summary-service.d.ts +8 -1
  28. package/dist/server/modules/butler/butler-session-summary-service.js +34 -7
  29. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  30. package/dist/server/modules/butler/context-aggregator.js +44 -13
  31. package/dist/server/modules/butler/context-aggregator.js.map +1 -1
  32. package/dist/server/modules/butler/patrol-scheduler.d.ts +9 -0
  33. package/dist/server/modules/butler/patrol-scheduler.js +63 -9
  34. package/dist/server/modules/butler/patrol-scheduler.js.map +1 -1
  35. package/dist/server/modules/butler/session-summary-scheduler.d.ts +9 -0
  36. package/dist/server/modules/butler/session-summary-scheduler.js +47 -11
  37. package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -1
  38. package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.d.ts +38 -0
  39. package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.js +99 -0
  40. package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.js.map +1 -0
  41. package/dist/server/modules/debug-target/debug-target-controller.d.ts +70 -0
  42. package/dist/server/modules/debug-target/debug-target-controller.js +113 -0
  43. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -0
  44. package/dist/server/modules/debug-target/debug-target-service.d.ts +102 -0
  45. package/dist/server/modules/debug-target/debug-target-service.js +1484 -0
  46. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -0
  47. package/dist/server/modules/debug-target/framework-compatibility-matrix.d.ts +4 -0
  48. package/dist/server/modules/debug-target/framework-compatibility-matrix.js +45 -0
  49. package/dist/server/modules/debug-target/framework-compatibility-matrix.js.map +1 -0
  50. package/dist/server/modules/debug-target/launch-adapter-registry.d.ts +25 -0
  51. package/dist/server/modules/debug-target/launch-adapter-registry.js +445 -0
  52. package/dist/server/modules/debug-target/launch-adapter-registry.js.map +1 -0
  53. package/dist/server/modules/file/file-content-service.d.ts +2 -1
  54. package/dist/server/modules/file/file-content-service.js +53 -0
  55. package/dist/server/modules/file/file-content-service.js.map +1 -1
  56. package/dist/server/modules/file/file-controller.d.ts +12 -1
  57. package/dist/server/modules/file/file-controller.js +72 -1
  58. package/dist/server/modules/file/file-controller.js.map +1 -1
  59. package/dist/server/modules/file/file-preview-link-service.d.ts +22 -0
  60. package/dist/server/modules/file/file-preview-link-service.js +160 -0
  61. package/dist/server/modules/file/file-preview-link-service.js.map +1 -0
  62. package/dist/server/modules/git/commit-orchestrator.d.ts +4 -1
  63. package/dist/server/modules/git/commit-orchestrator.js +18 -1
  64. package/dist/server/modules/git/commit-orchestrator.js.map +1 -1
  65. package/dist/server/modules/git/git-auth.d.ts +25 -0
  66. package/dist/server/modules/git/git-auth.js +88 -0
  67. package/dist/server/modules/git/git-auth.js.map +1 -0
  68. package/dist/server/modules/git/git-controller.d.ts +6 -0
  69. package/dist/server/modules/git/git-controller.js +5 -1
  70. package/dist/server/modules/git/git-controller.js.map +1 -1
  71. package/dist/server/modules/git/git-read-service.d.ts +2 -1
  72. package/dist/server/modules/git/git-read-service.js +19 -2
  73. package/dist/server/modules/git/git-read-service.js.map +1 -1
  74. package/dist/server/modules/git/git-remote-credential-service.d.ts +9 -0
  75. package/dist/server/modules/git/git-remote-credential-service.js +76 -0
  76. package/dist/server/modules/git/git-remote-credential-service.js.map +1 -0
  77. package/dist/server/modules/git/git-write-service.d.ts +5 -2
  78. package/dist/server/modules/git/git-write-service.js +33 -17
  79. package/dist/server/modules/git/git-write-service.js.map +1 -1
  80. package/dist/server/modules/git/types.d.ts +3 -0
  81. package/dist/server/modules/git/workspace-repo-guard.js +3 -2
  82. package/dist/server/modules/git/workspace-repo-guard.js.map +1 -1
  83. package/dist/server/modules/provider/codex-model-options.d.ts +3 -1
  84. package/dist/server/modules/provider/codex-model-options.js +4 -1
  85. package/dist/server/modules/provider/codex-model-options.js.map +1 -1
  86. package/dist/server/modules/provider/opencode-model-options.d.ts +3 -1
  87. package/dist/server/modules/provider/opencode-model-options.js +5 -1
  88. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  89. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +24 -0
  90. package/dist/server/modules/provider/provider-discovery-helper-client.js +14 -0
  91. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  92. package/dist/server/modules/provider/provider-discovery-helper-process.js +54 -0
  93. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  94. package/dist/server/modules/sessions/codex-app-server-helper-client.d.ts +2 -1
  95. package/dist/server/modules/sessions/codex-app-server-helper-client.js +103 -0
  96. package/dist/server/modules/sessions/codex-app-server-helper-client.js.map +1 -1
  97. package/dist/server/modules/sessions/codex-app-server-helper-process.js +106 -1
  98. package/dist/server/modules/sessions/codex-app-server-helper-process.js.map +1 -1
  99. package/dist/server/modules/sessions/session-controller.d.ts +26 -0
  100. package/dist/server/modules/sessions/session-controller.js +39 -1
  101. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  102. package/dist/server/modules/sessions/session-history-service.d.ts +51 -5
  103. package/dist/server/modules/sessions/session-history-service.js +906 -59
  104. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  105. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +5 -0
  106. package/dist/server/modules/sessions/session-live-runtime-service.js +59 -2
  107. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  108. package/dist/server/modules/sessions/session-provider-error-mapper.js +66 -0
  109. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -1
  110. package/dist/server/modules/tasks/event-loop-monitor.d.ts +21 -0
  111. package/dist/server/modules/tasks/event-loop-monitor.js +64 -0
  112. package/dist/server/modules/tasks/event-loop-monitor.js.map +1 -0
  113. package/dist/server/modules/tasks/observability-controller.d.ts +30 -0
  114. package/dist/server/modules/tasks/observability-controller.js +44 -0
  115. package/dist/server/modules/tasks/observability-controller.js.map +1 -0
  116. package/dist/server/modules/tasks/observability-service.d.ts +32 -0
  117. package/dist/server/modules/tasks/observability-service.js +104 -0
  118. package/dist/server/modules/tasks/observability-service.js.map +1 -0
  119. package/dist/server/modules/tasks/scheduler-metrics.d.ts +41 -0
  120. package/dist/server/modules/tasks/scheduler-metrics.js +92 -0
  121. package/dist/server/modules/tasks/scheduler-metrics.js.map +1 -0
  122. package/dist/server/modules/tasks/task-activity-log.d.ts +39 -0
  123. package/dist/server/modules/tasks/task-activity-log.js +43 -0
  124. package/dist/server/modules/tasks/task-activity-log.js.map +1 -0
  125. package/dist/server/modules/tasks/task-helper-client.d.ts +11 -0
  126. package/dist/server/modules/tasks/task-helper-client.js +132 -0
  127. package/dist/server/modules/tasks/task-helper-client.js.map +1 -0
  128. package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +16 -0
  129. package/dist/server/modules/tasks/task-helper-process-handlers.js +14 -0
  130. package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -0
  131. package/dist/server/modules/tasks/task-helper-process.d.ts +1 -0
  132. package/dist/server/modules/tasks/task-helper-process.js +49 -0
  133. package/dist/server/modules/tasks/task-helper-process.js.map +1 -0
  134. package/dist/server/modules/tasks/task-lane-executors.d.ts +2 -0
  135. package/dist/server/modules/tasks/task-lane-executors.js +15 -0
  136. package/dist/server/modules/tasks/task-lane-executors.js.map +1 -0
  137. package/dist/server/modules/tasks/task-manager.d.ts +15 -0
  138. package/dist/server/modules/tasks/task-manager.js +36 -0
  139. package/dist/server/modules/tasks/task-manager.js.map +1 -0
  140. package/dist/server/modules/tasks/task-metrics.d.ts +9 -0
  141. package/dist/server/modules/tasks/task-metrics.js +81 -0
  142. package/dist/server/modules/tasks/task-metrics.js.map +1 -0
  143. package/dist/server/modules/tasks/task-registry.d.ts +7 -0
  144. package/dist/server/modules/tasks/task-registry.js +21 -0
  145. package/dist/server/modules/tasks/task-registry.js.map +1 -0
  146. package/dist/server/modules/tasks/task-scheduler.d.ts +31 -0
  147. package/dist/server/modules/tasks/task-scheduler.js +473 -0
  148. package/dist/server/modules/tasks/task-scheduler.js.map +1 -0
  149. package/dist/server/modules/tasks/task-types.d.ts +106 -0
  150. package/dist/server/modules/tasks/task-types.js +23 -0
  151. package/dist/server/modules/tasks/task-types.js.map +1 -0
  152. package/dist/server/modules/terminal/command-template-service.d.ts +4 -0
  153. package/dist/server/modules/terminal/command-template-service.js +5 -3
  154. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  155. package/dist/server/modules/terminal/runtime/terminal-log-spooler.d.ts +7 -3
  156. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js +95 -15
  157. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js.map +1 -1
  158. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.d.ts +21 -0
  159. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +144 -0
  160. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -0
  161. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.d.ts +1 -0
  162. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +187 -0
  163. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -0
  164. package/dist/server/modules/terminal/terminal-service.d.ts +12 -0
  165. package/dist/server/modules/terminal/terminal-service.js +34 -17
  166. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  167. package/dist/server/modules/workbench/workbench-service.d.ts +23 -2
  168. package/dist/server/modules/workbench/workbench-service.js +126 -15
  169. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  170. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +5 -1
  171. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +88 -19
  172. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  173. package/dist/server/modules/workspace/workspace-code-composition.d.ts +2 -0
  174. package/dist/server/modules/workspace/workspace-code-composition.js +154 -0
  175. package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -0
  176. package/dist/server/modules/workspace/workspace-controller.d.ts +14 -0
  177. package/dist/server/modules/workspace/workspace-controller.js +19 -0
  178. package/dist/server/modules/workspace/workspace-controller.js.map +1 -1
  179. package/dist/server/modules/workspace/workspace-service.d.ts +21 -14
  180. package/dist/server/modules/workspace/workspace-service.js +183 -234
  181. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  182. package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +35 -0
  183. package/dist/server/modules/worktree/worktree-cleanup-service.js +210 -0
  184. package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -0
  185. package/dist/server/modules/worktree/worktree-controller.d.ts +44 -0
  186. package/dist/server/modules/worktree/worktree-controller.js +40 -0
  187. package/dist/server/modules/worktree/worktree-controller.js.map +1 -0
  188. package/dist/server/modules/worktree/worktree-manager.d.ts +34 -0
  189. package/dist/server/modules/worktree/worktree-manager.js +292 -0
  190. package/dist/server/modules/worktree/worktree-manager.js.map +1 -0
  191. package/dist/server/modules/worktree/worktree-merge-service.d.ts +52 -0
  192. package/dist/server/modules/worktree/worktree-merge-service.js +293 -0
  193. package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -0
  194. package/dist/server/modules/worktree/worktree-sync-service.d.ts +23 -0
  195. package/dist/server/modules/worktree/worktree-sync-service.js +166 -0
  196. package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -0
  197. package/dist/server/routes/assistant.d.ts +3 -0
  198. package/dist/server/routes/assistant.js +15 -0
  199. package/dist/server/routes/assistant.js.map +1 -0
  200. package/dist/server/routes/debug-targets.d.ts +3 -0
  201. package/dist/server/routes/debug-targets.js +15 -0
  202. package/dist/server/routes/debug-targets.js.map +1 -0
  203. package/dist/server/routes/files.js +2 -0
  204. package/dist/server/routes/files.js.map +1 -1
  205. package/dist/server/routes/git.js +1 -0
  206. package/dist/server/routes/git.js.map +1 -1
  207. package/dist/server/routes/observability.d.ts +3 -0
  208. package/dist/server/routes/observability.js +7 -0
  209. package/dist/server/routes/observability.js.map +1 -0
  210. package/dist/server/routes/sessions.js +1 -0
  211. package/dist/server/routes/sessions.js.map +1 -1
  212. package/dist/server/routes/workspaces.js +2 -0
  213. package/dist/server/routes/workspaces.js.map +1 -1
  214. package/dist/server/routes/worktrees.d.ts +3 -0
  215. package/dist/server/routes/worktrees.js +8 -0
  216. package/dist/server/routes/worktrees.js.map +1 -0
  217. package/dist/server/server/create-server.d.ts +38 -0
  218. package/dist/server/server/create-server.js +106 -11
  219. package/dist/server/server/create-server.js.map +1 -1
  220. package/dist/server/shared/utils/command-availability.d.ts +1 -0
  221. package/dist/server/shared/utils/command-availability.js +83 -0
  222. package/dist/server/shared/utils/command-availability.js.map +1 -0
  223. package/dist/server/shared/utils/secret-box.d.ts +2 -0
  224. package/dist/server/shared/utils/secret-box.js +29 -0
  225. package/dist/server/shared/utils/secret-box.js.map +1 -0
  226. package/dist/server/shared/utils/terminal-debug-log.js +5 -3
  227. package/dist/server/shared/utils/terminal-debug-log.js.map +1 -1
  228. package/dist/server/storage/repositories/ai-fallback-edit-repository.d.ts +11 -0
  229. package/dist/server/storage/repositories/ai-fallback-edit-repository.js +118 -0
  230. package/dist/server/storage/repositories/ai-fallback-edit-repository.js.map +1 -0
  231. package/dist/server/storage/repositories/debug-runtime-session-repository.d.ts +11 -0
  232. package/dist/server/storage/repositories/debug-runtime-session-repository.js +100 -0
  233. package/dist/server/storage/repositories/debug-runtime-session-repository.js.map +1 -0
  234. package/dist/server/storage/repositories/debug-service-repository.d.ts +9 -0
  235. package/dist/server/storage/repositories/debug-service-repository.js +99 -0
  236. package/dist/server/storage/repositories/debug-service-repository.js.map +1 -0
  237. package/dist/server/storage/repositories/debug-target-repository.d.ts +11 -0
  238. package/dist/server/storage/repositories/debug-target-repository.js +100 -0
  239. package/dist/server/storage/repositories/debug-target-repository.js.map +1 -0
  240. package/dist/server/storage/repositories/framework-analysis-result-repository.d.ts +9 -0
  241. package/dist/server/storage/repositories/framework-analysis-result-repository.js +98 -0
  242. package/dist/server/storage/repositories/framework-analysis-result-repository.js.map +1 -0
  243. package/dist/server/storage/repositories/git-remote-credential-repository.d.ts +9 -0
  244. package/dist/server/storage/repositories/git-remote-credential-repository.js +51 -0
  245. package/dist/server/storage/repositories/git-remote-credential-repository.js.map +1 -0
  246. package/dist/server/storage/repositories/port-lease-repository.d.ts +12 -0
  247. package/dist/server/storage/repositories/port-lease-repository.js +124 -0
  248. package/dist/server/storage/repositories/port-lease-repository.js.map +1 -0
  249. package/dist/server/storage/repositories/runtime-binding-repository.d.ts +10 -0
  250. package/dist/server/storage/repositories/runtime-binding-repository.js +89 -0
  251. package/dist/server/storage/repositories/runtime-binding-repository.js.map +1 -0
  252. package/dist/server/storage/repositories/session-fork-repository.d.ts +8 -0
  253. package/dist/server/storage/repositories/session-fork-repository.js +69 -0
  254. package/dist/server/storage/repositories/session-fork-repository.js.map +1 -0
  255. package/dist/server/storage/repositories/session-index-repository.js +40 -2
  256. package/dist/server/storage/repositories/session-index-repository.js.map +1 -1
  257. package/dist/server/storage/repositories/terminal-command-template-repository.js +77 -4
  258. package/dist/server/storage/repositories/terminal-command-template-repository.js.map +1 -1
  259. package/dist/server/storage/repositories/terminal-instance-repository.js +89 -7
  260. package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -1
  261. package/dist/server/storage/repositories/workspace-navigation-state-repository.d.ts +9 -0
  262. package/dist/server/storage/repositories/workspace-navigation-state-repository.js +49 -0
  263. package/dist/server/storage/repositories/workspace-navigation-state-repository.js.map +1 -0
  264. package/dist/server/storage/repositories/workspace-repository.d.ts +7 -1
  265. package/dist/server/storage/repositories/workspace-repository.js +32 -8
  266. package/dist/server/storage/repositories/workspace-repository.js.map +1 -1
  267. package/dist/server/storage/repositories/workspace-worktree-repository.d.ts +13 -0
  268. package/dist/server/storage/repositories/workspace-worktree-repository.js +158 -0
  269. package/dist/server/storage/repositories/workspace-worktree-repository.js.map +1 -0
  270. package/dist/server/storage/sqlite/client.js +408 -0
  271. package/dist/server/storage/sqlite/client.js.map +1 -1
  272. package/dist/server/storage/sqlite/schema.sql +268 -0
  273. package/dist/server/types/domain.d.ts +256 -1
  274. package/dist/server/ws/workbench-ws-hub.js +33 -9
  275. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  276. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +6 -1
  277. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +240 -7
  278. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  279. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +26 -1
  280. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +507 -2
  281. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  282. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +94 -5
  283. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  284. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +5 -1
  285. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +293 -3
  286. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  287. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  288. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +117 -17
  289. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  290. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +10 -0
  291. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +128 -8
  292. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  293. package/node_modules/@codingns/session-sync-core/dist/services.d.ts +2 -1
  294. package/node_modules/@codingns/session-sync-core/dist/services.js +55 -8
  295. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  296. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +33 -0
  297. package/package.json +1 -1
  298. package/dist/public/assets/index-CxeghocY.css +0 -1
  299. package/dist/public/assets/index-DXusStl0.js +0 -108
@@ -4,13 +4,23 @@ 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";
6
6
  import { logPerformance } from "../../shared/utils/perf-log.js";
7
+ import { isTerminalDebugEnabled, logTerminalDebug, terminalDebugNowMs } from "../../shared/utils/terminal-debug-log.js";
7
8
  import { nowIso } from "../../shared/utils/time.js";
9
+ import { isCommandAvailable } from "../../shared/utils/command-availability.js";
8
10
  import { inspectSessionActivity } from "./session-activity-inspector.js";
9
11
  import { SessionActivityAuthorityService } from "./session-activity-authority-service.js";
10
12
  import { mapSessionProviderError } from "./session-provider-error-mapper.js";
13
+ import { SessionForkRepository } from "../../storage/repositories/session-fork-repository.js";
11
14
  import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
12
- import { CodexModelOptionsService, enrichCodexCapabilities } from "../provider/codex-model-options.js";
13
- import { OpenCodeModelOptionsService, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
15
+ import { CodexModelOptionsService, createFallbackCodexModelOptions, enrichCodexCapabilities } from "../provider/codex-model-options.js";
16
+ import { OpenCodeModelOptionsService, createFallbackOpenCodeModelOptions, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
17
+ import { ProviderDiscoveryHelperClient } from "../provider/provider-discovery-helper-client.js";
18
+ import { createTaskManager } from "../tasks/task-manager.js";
19
+ import { HOST_TASK_TYPES } from "../tasks/task-types.js";
20
+ import { CodexAppServerHelperClient } from "./codex-app-server-helper-client.js";
21
+ const RECONSTRUCTED_FORK_TARGET_PROVIDERS = new Set(["codex", "claude-code", "opencode"]);
22
+ const FORK_RECONSTRUCTION_PAGE_SIZE = 200;
23
+ const MAX_FORK_DEPTH = 4;
14
24
  const SESSION_START_DEFERRED_PROVIDERS = new Set([
15
25
  "codex",
16
26
  "claude-code",
@@ -19,6 +29,9 @@ const SESSION_START_DEFERRED_PROVIDERS = new Set([
19
29
  "kimi"
20
30
  ]);
21
31
  const MUTABLE_HISTORY_TAIL_REFRESH_INTERVAL_MS = 1_200;
32
+ const WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS = 15_000;
33
+ const PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS = 5_000;
34
+ const WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE = 25;
22
35
  export class SessionHistoryService {
23
36
  db;
24
37
  workspaceRepository;
@@ -33,14 +46,20 @@ export class SessionHistoryService {
33
46
  sessionSyncService;
34
47
  capabilityService;
35
48
  sessionActivityAuthorityService;
49
+ sessionForkRepository;
36
50
  claudeCodeHomeDir;
37
51
  codexModelOptionsService;
38
52
  openCodeModelOptionsService;
53
+ providerCliCommandPaths;
54
+ providerCliAvailability;
55
+ providerDiscoveryHelperClient = new ProviderDiscoveryHelperClient();
56
+ providerSessionDiscoveryConfig;
57
+ taskManager;
39
58
  workspaceDiscoveryStatuses = new Map();
40
- workspaceDiscoveryInflight = new Map();
41
59
  workspaceStateRefreshInflight = new Map();
60
+ providerCapabilityCache = new Map();
42
61
  workspaceSessionRelations = new Map();
43
- constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null) {
62
+ constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config, sessionActivityAuthorityService = new SessionActivityAuthorityService(), sessionMessageOriginRepository = null, sessionForkRepository = null, adapterOverrides = {}, taskManager = createTaskManager()) {
44
63
  this.db = db;
45
64
  this.workspaceRepository = workspaceRepository;
46
65
  this.sessionBindingRepository = sessionBindingRepository;
@@ -51,10 +70,36 @@ export class SessionHistoryService {
51
70
  this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
52
71
  this.sessionMessageOriginRepository = sessionMessageOriginRepository;
53
72
  this.sessionActivityAuthorityService = sessionActivityAuthorityService;
73
+ this.sessionForkRepository = sessionForkRepository ?? new SessionForkRepository(db);
74
+ this.taskManager = taskManager;
54
75
  this.claudeCodeHomeDir = config.claudeCodeHomeDir;
76
+ this.providerCliCommandPaths = {
77
+ "claude-code": process.platform === "win32" ? "claude.cmd" : "claude",
78
+ codex: config.codexCliPath,
79
+ gemini: config.geminiCliPath,
80
+ kimi: config.kimiCliPath
81
+ };
82
+ // CLI 是否可用只在 Host 启动时探测一次;后续统一读缓存,更新 CLI 后重启 Host 生效。
83
+ this.providerCliAvailability = buildProviderCliAvailabilitySnapshot(this.providerCliCommandPaths);
84
+ this.providerSessionDiscoveryConfig = {
85
+ claudeCodeHomeDir: config.claudeCodeHomeDir,
86
+ codexCliPath: config.codexCliPath,
87
+ codexHomeDir: config.codexHomeDir,
88
+ geminiCliPath: config.geminiCliPath,
89
+ geminiHomeDir: config.geminiHomeDir,
90
+ kimiDefaultModel: config.kimiDefaultModel,
91
+ kimiHomeDir: config.kimiHomeDir,
92
+ opencodeBaseUrl: config.opencodeBaseUrl,
93
+ opencodeDataDir: config.opencodeDataDir,
94
+ opencodeDbPath: config.opencodeDbPath
95
+ };
55
96
  this.providerRegistry = new ProviderRegistry([
56
97
  new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
57
- new CodexAdapter({ homeDir: config.codexHomeDir }),
98
+ new CodexAdapter({
99
+ homeDir: config.codexHomeDir,
100
+ forkTransportFactory: adapterOverrides.codexForkTransportFactory
101
+ ?? createCodexForkTransportFactory(config.codexCliPath, config.codexHomeDir)
102
+ }),
58
103
  new GeminiAdapter({
59
104
  homeDir: config.geminiHomeDir,
60
105
  commandPath: config.geminiCliPath
@@ -80,6 +125,32 @@ export class SessionHistoryService {
80
125
  baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver),
81
126
  commandPath: config.opencodeCliPath
82
127
  });
128
+ this.registerBackgroundTasks();
129
+ }
130
+ observeBackgroundTaskMetrics() {
131
+ return this.taskManager.observe();
132
+ }
133
+ registerBackgroundTasks() {
134
+ if (!this.taskManager.has(HOST_TASK_TYPES.workspaceDiscovery)) {
135
+ this.taskManager.register({
136
+ taskType: HOST_TASK_TYPES.workspaceDiscovery,
137
+ executionLane: "helper_process",
138
+ run: async ({ workspaceId, userId, refreshStateMode }) => this.runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode)
139
+ });
140
+ }
141
+ if (!this.taskManager.has(HOST_TASK_TYPES.providerCapabilityRefresh)) {
142
+ this.taskManager.register({
143
+ taskType: HOST_TASK_TYPES.providerCapabilityRefresh,
144
+ executionLane: "external_process",
145
+ run: async ({ capabilities, workspacePath }) => {
146
+ const value = await this.enrichProviderCapabilities(capabilities, workspacePath);
147
+ this.providerCapabilityCache.set(buildProviderCapabilityCacheKey(capabilities.provider, workspacePath), {
148
+ refreshedAt: Date.now(),
149
+ value
150
+ });
151
+ }
152
+ });
153
+ }
83
154
  }
84
155
  async discoverWorkspaceSessions(workspaceId, userId, options) {
85
156
  const maxAgeMs = options?.maxAgeMs ?? 0;
@@ -90,17 +161,47 @@ export class SessionHistoryService {
90
161
  discoveryStatus?.isComplete === true &&
91
162
  maxAgeMs > 0 &&
92
163
  Date.now() - lastRefreshedAt <= maxAgeMs) {
164
+ this.taskManager.recordCacheHit(HOST_TASK_TYPES.workspaceDiscovery, workspaceId);
93
165
  return this.listWorkspaceSessions(workspaceId, userId);
94
166
  }
95
- const inflight = this.workspaceDiscoveryInflight.get(workspaceId);
96
- if (inflight) {
97
- return inflight;
167
+ return this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscovery, {
168
+ key: workspaceId,
169
+ source: "session_history.discover_workspace_sessions",
170
+ input: {
171
+ workspaceId,
172
+ userId,
173
+ refreshStateMode: options?.refreshStateMode ?? "inline"
174
+ }
175
+ }).promise;
176
+ }
177
+ requestWorkspaceDiscovery(workspaceId, userId, options) {
178
+ const maxAgeMs = options?.maxAgeMs ?? WORKSPACE_DISCOVERY_BACKGROUND_MAX_AGE_MS;
179
+ const force = options?.force ?? false;
180
+ if (!force && !this.needsWorkspaceDiscovery(workspaceId, maxAgeMs)) {
181
+ return;
98
182
  }
99
- const task = this.runDiscoverWorkspaceSessions(workspaceId, userId, options?.refreshStateMode ?? "inline").finally(() => {
100
- this.workspaceDiscoveryInflight.delete(workspaceId);
183
+ const task = this.taskManager.enqueue(HOST_TASK_TYPES.workspaceDiscovery, {
184
+ key: workspaceId,
185
+ source: "session_history.request_workspace_discovery",
186
+ input: {
187
+ workspaceId,
188
+ userId,
189
+ refreshStateMode: options?.refreshStateMode ?? "deferred"
190
+ }
191
+ });
192
+ if (task.deduped) {
193
+ return;
194
+ }
195
+ void task.promise.catch((error) => {
196
+ logPerformance("workspace.discover_sessions.background_failed", 0, {
197
+ workspaceId,
198
+ error: error instanceof Error ? error.message : "unknown"
199
+ }, {
200
+ thresholdMs: 0,
201
+ force: true
202
+ });
203
+ return this.listWorkspaceSessions(workspaceId, userId);
101
204
  });
102
- this.workspaceDiscoveryInflight.set(workspaceId, task);
103
- return task;
104
205
  }
105
206
  needsWorkspaceDiscovery(workspaceId, maxAgeMs) {
106
207
  if (maxAgeMs <= 0) {
@@ -224,13 +325,13 @@ export class SessionHistoryService {
224
325
  const binding = this.getBindingOrThrow(sessionId);
225
326
  await this.syncSessionTitleFromProvider(sessionId, binding);
226
327
  }
227
- async syncWorkspaceSessionTitles(workspaceId, userId) {
328
+ async syncWorkspaceSessionTitles(workspaceId, userId, concurrency = 1) {
228
329
  const sessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
229
- for (const session of sessions) {
330
+ await runWithConcurrency(sessions, concurrency, async (session) => {
230
331
  await this.syncSessionTitle(session.sessionId).catch(() => {
231
332
  return;
232
333
  });
233
- }
334
+ });
234
335
  }
235
336
  async listSessionChangedFiles(sessionId, userId) {
236
337
  this.getSession(sessionId, userId);
@@ -244,7 +345,7 @@ export class SessionHistoryService {
244
345
  }
245
346
  getProviderCapabilitiesSnapshot(provider) {
246
347
  try {
247
- return this.capabilityService.getProviderCapabilities(provider);
348
+ return this.resolveProviderCapabilitiesImmediate(this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider)), null);
248
349
  }
249
350
  catch (error) {
250
351
  throw mapSessionProviderError(error);
@@ -253,7 +354,9 @@ export class SessionHistoryService {
253
354
  async getProviderCapabilities(provider, workspaceId) {
254
355
  try {
255
356
  const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
256
- return await this.enrichProviderCapabilities(this.capabilityService.getProviderCapabilities(provider), workspacePath);
357
+ const baseCapabilities = this.applyProviderCliAvailability(this.capabilityService.getProviderCapabilities(provider));
358
+ this.scheduleProviderCapabilityRefresh(baseCapabilities, workspacePath);
359
+ return this.resolveProviderCapabilitiesImmediate(baseCapabilities, workspacePath);
257
360
  }
258
361
  catch (error) {
259
362
  throw mapSessionProviderError(error);
@@ -262,9 +365,14 @@ export class SessionHistoryService {
262
365
  async getSessionCapabilities(sessionId) {
263
366
  const binding = this.getBindingOrThrow(sessionId);
264
367
  const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
368
+ const workspacePath = workspace.path;
265
369
  return this.capabilityService
266
370
  .getSessionCapabilities(binding.provider, binding.providerSessionId)
267
- .then((capabilities) => this.enrichProviderCapabilities(capabilities, workspace.path))
371
+ .then((capabilities) => {
372
+ const normalizedCapabilities = this.applyProviderCliAvailability(capabilities);
373
+ this.scheduleProviderCapabilityRefresh(normalizedCapabilities, workspacePath);
374
+ return this.resolveProviderCapabilitiesImmediate(normalizedCapabilities, workspacePath);
375
+ })
268
376
  .catch((error) => {
269
377
  throw mapSessionProviderError(error);
270
378
  });
@@ -277,6 +385,83 @@ export class SessionHistoryService {
277
385
  const codexEnriched = await enrichCodexCapabilities(claudeEnriched, this.codexModelOptionsService);
278
386
  return enrichOpenCodeCapabilities(codexEnriched, this.openCodeModelOptionsService, workspacePath);
279
387
  }
388
+ resolveProviderCapabilitiesImmediate(capabilities, workspacePath) {
389
+ const cacheKey = buildProviderCapabilityCacheKey(capabilities.provider, workspacePath);
390
+ const cached = this.providerCapabilityCache.get(cacheKey);
391
+ if (cached) {
392
+ this.taskManager.recordCacheHit(HOST_TASK_TYPES.providerCapabilityRefresh, cacheKey);
393
+ return cached.value;
394
+ }
395
+ const claudeEnriched = enrichClaudeCapabilities(capabilities, {
396
+ claudeHomeDir: this.claudeCodeHomeDir,
397
+ workspacePath
398
+ });
399
+ return applyImmediateModelOptionFallbacks(claudeEnriched, this.codexModelOptionsService.peekSnapshot(), this.openCodeModelOptionsService.peekSnapshot(workspacePath));
400
+ }
401
+ scheduleProviderCapabilityRefresh(capabilities, workspacePath) {
402
+ const cacheKey = buildProviderCapabilityCacheKey(capabilities.provider, workspacePath);
403
+ const cached = this.providerCapabilityCache.get(cacheKey);
404
+ if (cached &&
405
+ Date.now() - cached.refreshedAt <= PROVIDER_CAPABILITY_CACHE_MAX_AGE_MS) {
406
+ return;
407
+ }
408
+ const task = this.taskManager.enqueue(HOST_TASK_TYPES.providerCapabilityRefresh, {
409
+ key: cacheKey,
410
+ source: "session_history.provider_capability_refresh",
411
+ input: {
412
+ capabilities,
413
+ workspacePath
414
+ }
415
+ });
416
+ if (task.deduped) {
417
+ return;
418
+ }
419
+ void task.promise.catch((error) => {
420
+ logPerformance("provider.capabilities.background_failed", 0, {
421
+ provider: capabilities.provider,
422
+ workspacePath,
423
+ error: error instanceof Error ? error.message : "unknown"
424
+ }, {
425
+ thresholdMs: 0,
426
+ force: true
427
+ });
428
+ });
429
+ }
430
+ applyProviderCliAvailability(capabilities) {
431
+ if (!isProviderCliBacked(capabilities.provider)) {
432
+ return capabilities;
433
+ }
434
+ if (this.providerCliAvailability[capabilities.provider]) {
435
+ return capabilities;
436
+ }
437
+ const limitation = buildProviderCliUnavailableMessage(capabilities.provider);
438
+ const limitations = capabilities.limitations.includes(limitation)
439
+ ? capabilities.limitations
440
+ : [limitation, ...capabilities.limitations];
441
+ return {
442
+ ...capabilities,
443
+ canStartSession: false,
444
+ canResumeSession: false,
445
+ canSendMessage: false,
446
+ supportsSubagents: false,
447
+ supportsInterrupt: false,
448
+ supportsSessionFork: false,
449
+ supportsNativeAgents: false,
450
+ limitations
451
+ };
452
+ }
453
+ assertProviderCapabilityEnabled(provider, capability, fallbackDetail) {
454
+ const capabilities = this.getProviderCapabilitiesSnapshot(provider);
455
+ if (capabilities[capability]) {
456
+ return;
457
+ }
458
+ throw new AppError({
459
+ statusCode: 409,
460
+ errorCode: "PROVIDER_UNAVAILABLE",
461
+ detail: capabilities.limitations[0] ?? fallbackDetail,
462
+ field: "provider"
463
+ });
464
+ }
280
465
  async getSessionContextUsage(sessionId) {
281
466
  const binding = this.getBindingOrThrow(sessionId);
282
467
  try {
@@ -288,6 +473,7 @@ export class SessionHistoryService {
288
473
  }
289
474
  async resumeSession(sessionId) {
290
475
  const binding = this.getBindingOrThrow(sessionId);
476
+ this.assertProviderCapabilityEnabled(binding.provider, "canResumeSession", "当前 provider 不支持继续会话");
291
477
  try {
292
478
  const result = await this.sessionSyncService.resumeSession(binding.provider, binding.providerSessionId, binding.rawStoreRef);
293
479
  this.upsertSnapshot(sessionId, {
@@ -311,7 +497,6 @@ export class SessionHistoryService {
311
497
  }
312
498
  }
313
499
  async startSession(input) {
314
- const workspace = this.getWorkspaceOrThrow(input.workspaceId);
315
500
  if (SESSION_START_DEFERRED_PROVIDERS.has(input.provider)) {
316
501
  throw new AppError({
317
502
  statusCode: 409,
@@ -320,6 +505,11 @@ export class SessionHistoryService {
320
505
  field: "provider"
321
506
  });
322
507
  }
508
+ return this.startSessionDirect(input);
509
+ }
510
+ async startSessionDirect(input) {
511
+ const workspace = this.getWorkspaceOrThrow(input.workspaceId);
512
+ this.assertProviderCapabilityEnabled(input.provider, "canStartSession", "当前 provider 不支持创建会话");
323
513
  try {
324
514
  const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
325
515
  initialPrompt: input.initialPrompt
@@ -340,7 +530,10 @@ export class SessionHistoryService {
340
530
  sessionId,
341
531
  workspaceId: workspace.id,
342
532
  provider: result.session.provider,
343
- parentSessionId: result.session.parentProviderSessionId ?? null,
533
+ parentSessionId: input.parentSessionId ?? result.session.parentProviderSessionId ?? null,
534
+ sessionKind: input.sessionKind ?? "default",
535
+ annotationSourceMessageId: input.annotationSourceMessageId ?? null,
536
+ annotationSourceText: input.annotationSourceText ?? null,
344
537
  isSubagent: result.session.isSubagent ?? false,
345
538
  subagentLabel: result.session.subagentLabel ?? null,
346
539
  title: result.session.title,
@@ -379,6 +572,251 @@ export class SessionHistoryService {
379
572
  throw mapSessionProviderError(error);
380
573
  }
381
574
  }
575
+ async forkSession(input) {
576
+ const binding = this.getBindingOrThrow(input.sessionId);
577
+ const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
578
+ const targetProvider = input.targetProvider?.trim() || binding.provider;
579
+ this.assertProviderCapabilityEnabled(targetProvider, "canStartSession", "当前 provider 不支持 fork 创建会话");
580
+ const sourceMessageId = input.sourceType === "message"
581
+ ? input.sourceMessageId?.trim() || null
582
+ : null;
583
+ if (input.sourceType === "message" && !sourceMessageId) {
584
+ throw new AppError({
585
+ statusCode: 400,
586
+ errorCode: "INVALID_INPUT",
587
+ detail: "按消息派生会话时必须提供 sourceMessageId",
588
+ field: "sourceMessageId"
589
+ });
590
+ }
591
+ this.assertForkDepthWithinLimit(input.sessionId);
592
+ if (targetProvider !== binding.provider) {
593
+ return this.forkSessionAcrossProviders({
594
+ ...input,
595
+ targetProvider
596
+ }, binding, sourceMessageId);
597
+ }
598
+ try {
599
+ const result = await this.sessionSyncService.forkSession(binding.provider, binding.providerSessionId, workspace.path, {
600
+ rawStoreRef: binding.rawStoreRef,
601
+ sourceType: input.sourceType,
602
+ sourceMessageId,
603
+ sourceMessageSnapshot: input.sourceMessageSnapshot ?? null,
604
+ strategy: input.strategy ?? "auto"
605
+ });
606
+ const sessionId = createId();
607
+ const timestamp = nowIso();
608
+ this.db.transaction(() => {
609
+ this.sessionBindingRepository.upsert({
610
+ sessionId,
611
+ workspaceId: workspace.id,
612
+ provider: result.session.provider,
613
+ providerSessionId: result.session.providerSessionId,
614
+ rawStoreRef: result.session.rawStoreRef,
615
+ createdAt: timestamp,
616
+ updatedAt: timestamp
617
+ });
618
+ this.sessionIndexRepository.upsert({
619
+ sessionId,
620
+ workspaceId: workspace.id,
621
+ provider: result.session.provider,
622
+ parentSessionId: input.sessionId,
623
+ sessionKind: input.sessionKind ?? "default",
624
+ annotationSourceMessageId: input.annotationSourceMessageId ?? null,
625
+ annotationSourceText: input.annotationSourceText ?? null,
626
+ isSubagent: result.session.isSubagent ?? false,
627
+ subagentLabel: result.session.subagentLabel ?? null,
628
+ title: result.session.title,
629
+ messageCount: result.session.messageCount,
630
+ isArchived: result.session.isArchived ?? false,
631
+ lastMessageAt: result.session.lastMessageAt,
632
+ createdAt: timestamp,
633
+ updatedAt: timestamp
634
+ });
635
+ this.sessionForkRepository.upsert({
636
+ sessionId,
637
+ parentSessionId: input.sessionId,
638
+ provider: result.session.provider,
639
+ forkSourceType: result.forkSourceType,
640
+ forkSourceSessionId: input.sessionId,
641
+ forkSourceMessageId: sourceMessageId,
642
+ inheritedPrefixMessageCount: result.inheritedPrefixMessageCount,
643
+ providerParentSessionId: binding.providerSessionId,
644
+ providerSourceMessageId: result.providerSourceMessageId ?? null,
645
+ forkMethod: result.forkMethod,
646
+ createdAt: timestamp
647
+ });
648
+ this.sessionStatusSnapshotRepository.upsert({
649
+ sessionId,
650
+ syncStatus: "idle",
651
+ syncCursor: null,
652
+ lastSyncAt: timestamp,
653
+ lastErrorCode: null,
654
+ lastErrorDetail: null,
655
+ resumedAt: null,
656
+ updatedAt: timestamp
657
+ });
658
+ this.sessionStateRepository.upsert({
659
+ sessionId,
660
+ userId: input.userId,
661
+ runningState: "idle",
662
+ activitySource: "none",
663
+ favorite: false,
664
+ lastEventAt: result.session.lastMessageAt,
665
+ completedAt: null,
666
+ lastSeenAt: null,
667
+ updatedAt: timestamp
668
+ });
669
+ })();
670
+ const forkedSession = this.getSessionListItemOrThrow(sessionId, input.userId);
671
+ const relationMap = this.workspaceSessionRelations.get(workspace.id)
672
+ ?? new Map();
673
+ relationMap.set(sessionId, {
674
+ parentSessionId: input.sessionId,
675
+ sessionKind: forkedSession.sessionKind ?? input.sessionKind ?? "default",
676
+ annotationSourceMessageId: forkedSession.annotationSourceMessageId ?? input.annotationSourceMessageId ?? null,
677
+ annotationSourceText: forkedSession.annotationSourceText ?? input.annotationSourceText ?? null,
678
+ isSubagent: forkedSession.isSubagent ?? false,
679
+ subagentLabel: forkedSession.subagentLabel ?? null
680
+ });
681
+ this.workspaceSessionRelations.set(workspace.id, relationMap);
682
+ return this.getSessionListItemOrThrow(sessionId, input.userId);
683
+ }
684
+ catch (error) {
685
+ throw mapSessionProviderError(error);
686
+ }
687
+ }
688
+ async forkSessionAcrossProviders(input, sourceBinding, sourceMessageId) {
689
+ if (!RECONSTRUCTED_FORK_TARGET_PROVIDERS.has(input.targetProvider)) {
690
+ throw mapSessionProviderError(new Error("FORK_TARGET_PROVIDER_NOT_SUPPORTED"));
691
+ }
692
+ const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sessionId);
693
+ const inheritedMessages = await this.readForkSourceMessages(input.sessionId, sourceBinding, input.sourceType, sourceMessageId, input.sourceMessageSnapshot ?? null);
694
+ const reconstructedMessages = inheritedMessages.filter((message) => (message.role === "user" || message.role === "assistant")
695
+ && message.kind === "text"
696
+ && message.content.trim().length > 0);
697
+ const inheritedPrompt = buildReconstructedForkPrompt({
698
+ sourceProvider: sourceBinding.provider,
699
+ targetProvider: input.targetProvider,
700
+ sourceType: input.sourceType,
701
+ sourceTitle: sourceIndex?.title?.trim() || null,
702
+ messages: reconstructedMessages
703
+ });
704
+ const startedSession = await this.startSessionDirect({
705
+ workspaceId: sourceBinding.workspaceId,
706
+ userId: input.userId,
707
+ provider: input.targetProvider,
708
+ initialPrompt: inheritedPrompt,
709
+ parentSessionId: input.sessionId,
710
+ sessionKind: input.sessionKind ?? "default",
711
+ annotationSourceMessageId: input.annotationSourceMessageId ?? null,
712
+ annotationSourceText: input.annotationSourceText ?? null
713
+ });
714
+ const timestamp = nowIso();
715
+ const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(startedSession.sessionId);
716
+ this.db.transaction(() => {
717
+ if (currentIndex) {
718
+ this.sessionIndexRepository.upsert({
719
+ ...currentIndex,
720
+ parentSessionId: input.sessionId,
721
+ sessionKind: input.sessionKind ?? currentIndex.sessionKind ?? "default",
722
+ annotationSourceMessageId: input.annotationSourceMessageId ?? currentIndex.annotationSourceMessageId ?? null,
723
+ annotationSourceText: input.annotationSourceText ?? currentIndex.annotationSourceText ?? null,
724
+ updatedAt: timestamp
725
+ });
726
+ }
727
+ this.sessionForkRepository.upsert({
728
+ sessionId: startedSession.sessionId,
729
+ parentSessionId: input.sessionId,
730
+ provider: input.targetProvider,
731
+ forkSourceType: input.sourceType,
732
+ forkSourceSessionId: input.sessionId,
733
+ forkSourceMessageId: sourceMessageId,
734
+ inheritedPrefixMessageCount: reconstructedMessages.length,
735
+ providerParentSessionId: sourceBinding.providerSessionId,
736
+ providerSourceMessageId: null,
737
+ forkMethod: input.sourceType === "session"
738
+ ? "reconstructed_session_fork"
739
+ : "reconstructed_message_fork",
740
+ createdAt: timestamp
741
+ });
742
+ })();
743
+ const relationMap = this.workspaceSessionRelations.get(sourceBinding.workspaceId)
744
+ ?? new Map();
745
+ relationMap.set(startedSession.sessionId, {
746
+ parentSessionId: input.sessionId,
747
+ sessionKind: startedSession.sessionKind ?? input.sessionKind ?? "default",
748
+ annotationSourceMessageId: startedSession.annotationSourceMessageId ?? input.annotationSourceMessageId ?? null,
749
+ annotationSourceText: startedSession.annotationSourceText ?? input.annotationSourceText ?? null,
750
+ isSubagent: startedSession.isSubagent ?? false,
751
+ subagentLabel: startedSession.subagentLabel ?? null
752
+ });
753
+ this.workspaceSessionRelations.set(sourceBinding.workspaceId, relationMap);
754
+ return this.getSessionListItemOrThrow(startedSession.sessionId, input.userId);
755
+ }
756
+ async readForkSourceMessages(sessionId, binding, sourceType, sourceMessageId, sourceMessageSnapshot = null) {
757
+ const messages = [];
758
+ let cursor = null;
759
+ while (true) {
760
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, FORK_RECONSTRUCTION_PAGE_SIZE, "forward");
761
+ messages.push(...page.messages);
762
+ if (!page.nextCursor) {
763
+ break;
764
+ }
765
+ cursor = page.nextCursor;
766
+ }
767
+ if (sourceType === "session") {
768
+ return messages;
769
+ }
770
+ const targetIndex = messages.findIndex((message) => message.messageId === sourceMessageId);
771
+ if (targetIndex < 0) {
772
+ throw mapSessionProviderError(new Error("FORK_SOURCE_MESSAGE_NOT_FOUND"));
773
+ }
774
+ const inheritedMessages = messages.slice(0, targetIndex + 1);
775
+ if (!sourceMessageSnapshot) {
776
+ return inheritedMessages;
777
+ }
778
+ const targetMessage = inheritedMessages[targetIndex];
779
+ if (!targetMessage) {
780
+ return inheritedMessages;
781
+ }
782
+ inheritedMessages[targetIndex] = {
783
+ ...targetMessage,
784
+ role: sourceMessageSnapshot.role,
785
+ kind: sourceMessageSnapshot.kind,
786
+ content: sourceMessageSnapshot.content
787
+ };
788
+ return inheritedMessages;
789
+ }
790
+ assertForkDepthWithinLimit(parentSessionId) {
791
+ const nextDepth = this.getSessionForkDepth(parentSessionId) + 1;
792
+ if (nextDepth > MAX_FORK_DEPTH) {
793
+ throw new AppError({
794
+ statusCode: 409,
795
+ errorCode: "FORK_DEPTH_LIMIT_EXCEEDED",
796
+ detail: `fork 会话层级最多支持 ${MAX_FORK_DEPTH} 级`
797
+ });
798
+ }
799
+ }
800
+ getSessionForkDepth(sessionId) {
801
+ let depth = 1;
802
+ let currentSessionId = sessionId;
803
+ const visitedSessionIds = new Set();
804
+ while (currentSessionId) {
805
+ if (visitedSessionIds.has(currentSessionId)) {
806
+ return depth;
807
+ }
808
+ visitedSessionIds.add(currentSessionId);
809
+ const parentSessionId = this.sessionForkRepository.findBySessionId(currentSessionId)?.parentSessionId
810
+ ?? this.sessionIndexRepository.findIndexRecordBySessionId(currentSessionId)?.parentSessionId
811
+ ?? null;
812
+ if (!parentSessionId) {
813
+ return depth;
814
+ }
815
+ depth += 1;
816
+ currentSessionId = parentSessionId;
817
+ }
818
+ return depth;
819
+ }
382
820
  async sendMessage(sessionId, content, clientRequestId, permissionMode = null) {
383
821
  const binding = this.getBindingOrThrow(sessionId);
384
822
  const result = await this.sessionSyncService
@@ -388,14 +826,21 @@ export class SessionHistoryService {
388
826
  throw mapSessionProviderError(error);
389
827
  });
390
828
  const existing = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
829
+ const sessionFork = this.sessionForkRepository.findBySessionId(sessionId);
830
+ const parentTitle = sessionFork?.parentSessionId
831
+ ? this.sessionIndexRepository.findIndexRecordBySessionId(sessionFork.parentSessionId)?.title ?? null
832
+ : null;
391
833
  this.sessionIndexRepository.upsert({
392
834
  sessionId,
393
835
  workspaceId: binding.workspaceId,
394
836
  provider: binding.provider,
395
837
  parentSessionId: existing?.parentSessionId ?? null,
838
+ sessionKind: existing?.sessionKind ?? "default",
839
+ annotationSourceMessageId: existing?.annotationSourceMessageId ?? null,
840
+ annotationSourceText: existing?.annotationSourceText ?? null,
396
841
  isSubagent: existing?.isSubagent ?? false,
397
842
  subagentLabel: existing?.subagentLabel ?? null,
398
- title: existing?.title ?? result.message.content.slice(0, 48),
843
+ title: resolveSessionListTitle(binding.provider, existing?.title ?? null, result.message.content, parentTitle),
399
844
  messageCount: (existing?.messageCount ?? 0) + 1,
400
845
  isArchived: existing?.isArchived ?? false,
401
846
  lastMessageAt: result.message.timestamp,
@@ -493,6 +938,25 @@ export class SessionHistoryService {
493
938
  messages: page.messages
494
939
  };
495
940
  }
941
+ async readAllTextHistoryMessages(sessionId, limit = FORK_RECONSTRUCTION_PAGE_SIZE) {
942
+ const binding = this.getBindingOrThrow(sessionId);
943
+ const messages = [];
944
+ let cursor = null;
945
+ let remaining = Math.max(limit, 0);
946
+ while (remaining > 0) {
947
+ const pageSize = Math.min(remaining, FORK_RECONSTRUCTION_PAGE_SIZE);
948
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, pageSize, "forward");
949
+ messages.push(...page.messages.filter((message) => (message.role === "user" || message.role === "assistant")
950
+ && message.kind === "text"
951
+ && message.content.trim().length > 0));
952
+ if (!page.nextCursor || page.messages.length === 0) {
953
+ break;
954
+ }
955
+ cursor = page.nextCursor;
956
+ remaining -= page.messages.length;
957
+ }
958
+ return messages;
959
+ }
496
960
  async markSessionSeen(sessionId, userId) {
497
961
  const existing = this.sessionStateRepository.findBySessionAndUser(sessionId, userId) ??
498
962
  (await this.refreshSessionState(sessionId, userId));
@@ -556,6 +1020,9 @@ export class SessionHistoryService {
556
1020
  workspaceId: existing.workspaceId,
557
1021
  provider: existing.provider,
558
1022
  parentSessionId: existing.parentSessionId ?? null,
1023
+ sessionKind: existing.sessionKind ?? "default",
1024
+ annotationSourceMessageId: existing.annotationSourceMessageId ?? null,
1025
+ annotationSourceText: existing.annotationSourceText ?? null,
559
1026
  isSubagent: existing.isSubagent ?? false,
560
1027
  subagentLabel: existing.subagentLabel ?? null,
561
1028
  title: existing.title,
@@ -645,16 +1112,29 @@ export class SessionHistoryService {
645
1112
  }
646
1113
  async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
647
1114
  const startedAt = Date.now();
1115
+ const debugStartedAtMs = terminalDebugNowMs();
648
1116
  const workspace = this.getWorkspaceOrThrow(workspaceId);
649
1117
  let discoverDurationMs = 0;
650
1118
  let persistDurationMs = 0;
1119
+ let persistPass1DurationMs = 0;
1120
+ let persistPass1BatchCount = 0;
1121
+ let persistPass1MaxBatchMs = 0;
1122
+ let relationMapDurationMs = 0;
1123
+ let persistPass2DurationMs = 0;
1124
+ let persistPass2BatchCount = 0;
1125
+ let persistPass2MaxBatchMs = 0;
1126
+ let cleanupDurationMs = 0;
1127
+ let listItemsDurationMs = 0;
1128
+ let refreshStateDurationMs = 0;
651
1129
  const refreshStateCount = 10;
652
1130
  try {
653
1131
  const discoverStartedAt = Date.now();
654
1132
  const existingWorkspaceSessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
655
1133
  const knownSessions = this.buildKnownSessionSummaries(existingWorkspaceSessions, workspace.path);
656
- const discovery = await this.sessionSyncService
657
- .discoverWorkspaceSessions(workspace.path, {
1134
+ const discovery = await this.providerDiscoveryHelperClient
1135
+ .discoverWorkspaceSessions({
1136
+ config: this.providerSessionDiscoveryConfig,
1137
+ workspacePath: workspace.path,
658
1138
  knownSessions
659
1139
  })
660
1140
  .catch((error) => {
@@ -666,10 +1146,9 @@ export class SessionHistoryService {
666
1146
  const discoveredSessionIds = new Map();
667
1147
  const persistedSessions = [];
668
1148
  const claimedPendingSessionIds = new Set();
669
- const persist = this.db.transaction(() => {
670
- for (const session of sessions) {
1149
+ const persistPass1Transaction = this.db.transaction((batch) => {
1150
+ for (const session of batch) {
671
1151
  const exactExisting = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ?? this.sessionBindingRepository.findByRawStoreRef(session.provider, session.rawStoreRef);
672
- // discover 只能补全当前工作区,不能把别的工作区已有会话偷过来重绑。
673
1152
  if (exactExisting && exactExisting.workspaceId !== workspaceId) {
674
1153
  continue;
675
1154
  }
@@ -699,11 +1178,24 @@ export class SessionHistoryService {
699
1178
  createdAt,
700
1179
  updatedAt: timestamp
701
1180
  });
1181
+ const preservedParentSessionId = existingIndex?.parentSessionId
1182
+ ?? this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
1183
+ ?? null;
1184
+ const preservedParentTitle = preservedParentSessionId
1185
+ ? this.sessionIndexRepository.findIndexRecordBySessionId(preservedParentSessionId)?.title ?? null
1186
+ : null;
1187
+ const preservedTitle = resolvePersistedSessionTitle(session.provider, session.title, existingIndex?.title ?? null, preservedParentTitle);
702
1188
  this.sessionIndexRepository.upsert({
703
1189
  sessionId,
704
1190
  workspaceId: workspace.id,
705
1191
  provider: session.provider,
706
- title: session.title,
1192
+ parentSessionId: preservedParentSessionId,
1193
+ sessionKind: existingIndex?.sessionKind ?? "default",
1194
+ annotationSourceMessageId: existingIndex?.annotationSourceMessageId ?? null,
1195
+ annotationSourceText: existingIndex?.annotationSourceText ?? null,
1196
+ isSubagent: existingIndex?.isSubagent ?? false,
1197
+ subagentLabel: existingIndex?.subagentLabel ?? null,
1198
+ title: preservedTitle,
707
1199
  messageCount: session.messageCount,
708
1200
  isArchived: resolveDiscoveredArchiveState(existingIndex?.isArchived ?? false, session.isArchived),
709
1201
  lastMessageAt: session.lastMessageAt,
@@ -728,17 +1220,46 @@ export class SessionHistoryService {
728
1220
  existingIndex
729
1221
  });
730
1222
  }
731
- const relationMap = this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds);
732
- for (const persistedSession of persistedSessions) {
1223
+ });
1224
+ const persistPass1StartedAt = Date.now();
1225
+ const persistPass1Stats = await runBatchedTransactions(sessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass1Transaction);
1226
+ persistPass1DurationMs = Date.now() - persistPass1StartedAt;
1227
+ persistPass1BatchCount = persistPass1Stats.batchCount;
1228
+ persistPass1MaxBatchMs = persistPass1Stats.maxBatchMs;
1229
+ const relationMapStartedAt = Date.now();
1230
+ const relationMap = this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds);
1231
+ relationMapDurationMs = Date.now() - relationMapStartedAt;
1232
+ const persistPass2Transaction = this.db.transaction((batch) => {
1233
+ for (const persistedSession of batch) {
733
1234
  const relation = relationMap.get(persistedSession.sessionId);
1235
+ const resolvedParentSessionId = relation?.parentSessionId
1236
+ ?? persistedSession.existingIndex?.parentSessionId
1237
+ ?? this.sessionForkRepository.findBySessionId(persistedSession.sessionId)?.parentSessionId
1238
+ ?? null;
1239
+ const resolvedParentTitle = resolvedParentSessionId
1240
+ ? this.sessionIndexRepository.findIndexRecordBySessionId(resolvedParentSessionId)?.title ?? null
1241
+ : null;
734
1242
  this.sessionIndexRepository.upsert({
735
1243
  sessionId: persistedSession.sessionId,
736
1244
  workspaceId: workspace.id,
737
1245
  provider: persistedSession.session.provider,
738
- parentSessionId: relation?.parentSessionId ?? null,
739
- isSubagent: relation?.isSubagent ?? false,
740
- subagentLabel: relation?.subagentLabel ?? null,
741
- title: persistedSession.session.title,
1246
+ parentSessionId: resolvedParentSessionId,
1247
+ sessionKind: relation?.sessionKind
1248
+ ?? persistedSession.existingIndex?.sessionKind
1249
+ ?? "default",
1250
+ annotationSourceMessageId: relation?.annotationSourceMessageId
1251
+ ?? persistedSession.existingIndex?.annotationSourceMessageId
1252
+ ?? null,
1253
+ annotationSourceText: relation?.annotationSourceText
1254
+ ?? persistedSession.existingIndex?.annotationSourceText
1255
+ ?? null,
1256
+ isSubagent: relation?.isSubagent
1257
+ ?? persistedSession.existingIndex?.isSubagent
1258
+ ?? false,
1259
+ subagentLabel: relation?.subagentLabel
1260
+ ?? persistedSession.existingIndex?.subagentLabel
1261
+ ?? null,
1262
+ title: resolvePersistedSessionTitle(persistedSession.session.provider, persistedSession.session.title, persistedSession.existingIndex?.title ?? null, resolvedParentTitle),
742
1263
  messageCount: persistedSession.session.messageCount,
743
1264
  isArchived: resolveDiscoveredArchiveState(persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
744
1265
  lastMessageAt: persistedSession.session.lastMessageAt,
@@ -747,26 +1268,56 @@ export class SessionHistoryService {
747
1268
  });
748
1269
  }
749
1270
  });
750
- const persistStartedAt = Date.now();
751
- persist();
752
- persistDurationMs = Date.now() - persistStartedAt;
1271
+ const persistPass2StartedAt = Date.now();
1272
+ const persistPass2Stats = await runBatchedTransactions(persistedSessions, WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, persistPass2Transaction);
1273
+ persistPass2DurationMs = Date.now() - persistPass2StartedAt;
1274
+ persistPass2BatchCount = persistPass2Stats.batchCount;
1275
+ persistPass2MaxBatchMs = persistPass2Stats.maxBatchMs;
1276
+ persistDurationMs = persistPass1DurationMs + relationMapDurationMs + persistPass2DurationMs;
753
1277
  if (discovery.isComplete) {
754
- this.cleanupStaleHiddenSessions(workspaceId, userId, sessions);
1278
+ const cleanupStartedAt = Date.now();
1279
+ await this.cleanupStaleHiddenSessions(workspaceId, userId, sessions);
1280
+ cleanupDurationMs = Date.now() - cleanupStartedAt;
755
1281
  }
756
- this.workspaceSessionRelations.set(workspaceId, this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds));
1282
+ this.workspaceSessionRelations.set(workspaceId, relationMap);
1283
+ const listItemsStartedAt = Date.now();
757
1284
  const items = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
758
- const recentItems = items.slice(0, refreshStateCount);
1285
+ listItemsDurationMs = Date.now() - listItemsStartedAt;
1286
+ const refreshCandidates = buildSessionStateRefreshCandidates(items, refreshStateCount);
759
1287
  this.workspaceDiscoveryStatuses.set(workspaceId, {
760
1288
  refreshedAt: Date.now(),
761
1289
  isComplete: discovery.isComplete
762
1290
  });
1291
+ const refreshStateStartedAt = Date.now();
763
1292
  if (refreshStateMode === "inline") {
764
- await this.refreshRecentSessionStates(recentItems, userId);
1293
+ await this.refreshRecentSessionStates(refreshCandidates, userId);
765
1294
  }
766
1295
  else {
767
- this.scheduleWorkspaceStateRefresh(workspaceId, userId, recentItems);
1296
+ this.scheduleWorkspaceStateRefresh(workspaceId, userId, refreshCandidates);
768
1297
  }
1298
+ refreshStateDurationMs = Date.now() - refreshStateStartedAt;
769
1299
  const nextItems = this.listWorkspaceSessions(workspaceId, userId);
1300
+ if (isTerminalDebugEnabled()) {
1301
+ logTerminalDebug("workspace.discovery.completed", {
1302
+ workspaceId,
1303
+ sessionCount: sessions.length,
1304
+ returnedSessionCount: nextItems.length,
1305
+ discoverMs: discoverDurationMs,
1306
+ persistMs: persistDurationMs,
1307
+ persistPass1Ms: persistPass1DurationMs,
1308
+ persistPass1BatchCount,
1309
+ persistPass1MaxBatchMs,
1310
+ relationMapMs: relationMapDurationMs,
1311
+ persistPass2Ms: persistPass2DurationMs,
1312
+ persistPass2BatchCount,
1313
+ persistPass2MaxBatchMs,
1314
+ cleanupMs: cleanupDurationMs,
1315
+ listItemsMs: listItemsDurationMs,
1316
+ refreshStateMs: refreshStateDurationMs,
1317
+ refreshStateDeferred: refreshStateMode !== "inline",
1318
+ durationMs: terminalDebugNowMs() - debugStartedAtMs
1319
+ });
1320
+ }
770
1321
  logPerformance("workspace.discover_sessions", Date.now() - startedAt, {
771
1322
  workspaceId,
772
1323
  workspacePath: workspace.path,
@@ -774,9 +1325,20 @@ export class SessionHistoryService {
774
1325
  discoveredSessions: sessions.length,
775
1326
  returnedSessions: nextItems.length,
776
1327
  discoveryComplete: discovery.isComplete,
777
- refreshedStates: Math.min(items.length, refreshStateCount),
1328
+ providerDiagnostics: (discovery.providerDiagnostics ?? []).map((entry) => `${entry.provider}:${entry.status}:${Math.round(entry.durationMs)}ms`),
1329
+ refreshedStates: refreshCandidates.length,
778
1330
  discoverMs: discoverDurationMs,
779
1331
  persistMs: persistDurationMs,
1332
+ persistPass1Ms: persistPass1DurationMs,
1333
+ persistPass1BatchCount,
1334
+ persistPass1MaxBatchMs,
1335
+ relationMapMs: relationMapDurationMs,
1336
+ persistPass2Ms: persistPass2DurationMs,
1337
+ persistPass2BatchCount,
1338
+ persistPass2MaxBatchMs,
1339
+ cleanupMs: cleanupDurationMs,
1340
+ listItemsMs: listItemsDurationMs,
1341
+ refreshStateMs: refreshStateDurationMs,
780
1342
  refreshStateDeferred: refreshStateMode !== "inline"
781
1343
  }, {
782
1344
  thresholdMs: 500
@@ -789,6 +1351,16 @@ export class SessionHistoryService {
789
1351
  workspacePath: workspace.path,
790
1352
  discoverMs: discoverDurationMs,
791
1353
  persistMs: persistDurationMs,
1354
+ persistPass1Ms: persistPass1DurationMs,
1355
+ persistPass1BatchCount,
1356
+ persistPass1MaxBatchMs,
1357
+ relationMapMs: relationMapDurationMs,
1358
+ persistPass2Ms: persistPass2DurationMs,
1359
+ persistPass2BatchCount,
1360
+ persistPass2MaxBatchMs,
1361
+ cleanupMs: cleanupDurationMs,
1362
+ listItemsMs: listItemsDurationMs,
1363
+ refreshStateMs: refreshStateDurationMs,
792
1364
  refreshStateDeferred: refreshStateMode !== "inline",
793
1365
  error: error instanceof Error ? error.message : "unknown"
794
1366
  }, {
@@ -909,11 +1481,17 @@ export class SessionHistoryService {
909
1481
  ? discoveredSessionIds.get(buildProviderSessionKey(session.provider, session.parentProviderSessionId)) ??
910
1482
  this.sessionBindingRepository.findByProviderSession(session.provider, session.parentProviderSessionId)?.sessionId ??
911
1483
  null
912
- : null;
1484
+ : this.resolvePersistedParentSessionId(sessionId);
913
1485
  relationMap.set(sessionId, {
914
1486
  parentSessionId,
915
- isSubagent: Boolean(session.isSubagent || parentSessionId),
916
- subagentLabel: session.subagentLabel?.trim() || null
1487
+ sessionKind: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.sessionKind ?? "default",
1488
+ annotationSourceMessageId: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.annotationSourceMessageId ?? null,
1489
+ annotationSourceText: this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.annotationSourceText ?? null,
1490
+ isSubagent: session.isSubagent === true
1491
+ || this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.isSubagent === true,
1492
+ subagentLabel: session.subagentLabel?.trim()
1493
+ || this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.subagentLabel
1494
+ || null
917
1495
  });
918
1496
  }
919
1497
  return relationMap;
@@ -931,6 +1509,9 @@ export class SessionHistoryService {
931
1509
  return this.enrichSessionItem({
932
1510
  ...item,
933
1511
  parentSessionId: relation.parentSessionId,
1512
+ sessionKind: relation.sessionKind,
1513
+ annotationSourceMessageId: relation.annotationSourceMessageId,
1514
+ annotationSourceText: relation.annotationSourceText,
934
1515
  isSubagent: relation.isSubagent,
935
1516
  subagentLabel: relation.subagentLabel
936
1517
  });
@@ -942,12 +1523,18 @@ export class SessionHistoryService {
942
1523
  ? {
943
1524
  ...item,
944
1525
  parentSessionId: relation.parentSessionId,
1526
+ sessionKind: relation.sessionKind,
1527
+ annotationSourceMessageId: relation.annotationSourceMessageId,
1528
+ annotationSourceText: relation.annotationSourceText,
945
1529
  isSubagent: relation.isSubagent,
946
1530
  subagentLabel: relation.subagentLabel
947
1531
  }
948
1532
  : {
949
1533
  ...item,
950
1534
  parentSessionId: item.parentSessionId ?? null,
1535
+ sessionKind: item.sessionKind ?? "default",
1536
+ annotationSourceMessageId: item.annotationSourceMessageId ?? null,
1537
+ annotationSourceText: item.annotationSourceText ?? null,
951
1538
  isSubagent: item.isSubagent ?? false,
952
1539
  subagentLabel: item.subagentLabel ?? null
953
1540
  };
@@ -1018,16 +1605,27 @@ export class SessionHistoryService {
1018
1605
  if (shouldSkipClaudePendingBinding(binding)) {
1019
1606
  return;
1020
1607
  }
1021
- const nextTitle = (await this.sessionSyncService.readSessionTitle(binding.provider, binding.providerSessionId, binding.rawStoreRef)).trim();
1022
- if (nextTitle.length === 0 || nextTitle === currentIndex.title) {
1608
+ const nextTitle = (await this.providerDiscoveryHelperClient.readSessionTitle({
1609
+ config: this.providerSessionDiscoveryConfig,
1610
+ provider: binding.provider,
1611
+ providerSessionId: binding.providerSessionId,
1612
+ rawStoreRef: binding.rawStoreRef
1613
+ })).trim();
1614
+ const resolvedTitle = resolvePersistedSessionTitle(binding.provider, nextTitle, currentIndex.title);
1615
+ if (resolvedTitle.length === 0 || resolvedTitle === currentIndex.title) {
1023
1616
  return;
1024
1617
  }
1025
1618
  this.sessionIndexRepository.upsert({
1026
1619
  ...currentIndex,
1027
- title: nextTitle,
1620
+ title: resolvedTitle,
1028
1621
  updatedAt: nowIso()
1029
1622
  });
1030
1623
  }
1624
+ resolvePersistedParentSessionId(sessionId) {
1625
+ return (this.sessionForkRepository.findBySessionId(sessionId)?.parentSessionId
1626
+ ?? this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.parentSessionId
1627
+ ?? null);
1628
+ }
1031
1629
  async ensureSessionChangedFilesIndexed(sessionId) {
1032
1630
  if (this.sessionChangedFileService.hasIndexedSession(sessionId)) {
1033
1631
  return;
@@ -1169,7 +1767,7 @@ export class SessionHistoryService {
1169
1767
  });
1170
1768
  this.workspaceStateRefreshInflight.set(inflightKey, task);
1171
1769
  }
1172
- cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
1770
+ async cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
1173
1771
  const discoveredProviderSessionIds = new Set(sessions.map((session) => buildProviderSessionKey(session.provider, session.providerSessionId)));
1174
1772
  const discoveredRawStoreRefs = new Set(sessions.map((session) => session.rawStoreRef));
1175
1773
  const staleHiddenSessions = this.sessionIndexRepository
@@ -1189,15 +1787,12 @@ export class SessionHistoryService {
1189
1787
  if (staleHiddenSessions.length === 0) {
1190
1788
  return;
1191
1789
  }
1192
- this.deleteSessionsByIds(staleHiddenSessions.map((session) => session.sessionId));
1193
- }
1194
- deleteSessionsByIds(sessionIds) {
1195
- const remove = this.db.transaction((ids) => {
1790
+ const deleteTransaction = this.db.transaction((ids) => {
1196
1791
  for (const sessionId of ids) {
1197
1792
  this.deleteSessionById(sessionId);
1198
1793
  }
1199
1794
  });
1200
- remove(sessionIds);
1795
+ await runBatchedTransactions(staleHiddenSessions.map((session) => session.sessionId), WORKSPACE_DISCOVERY_PERSIST_BATCH_SIZE, deleteTransaction);
1201
1796
  }
1202
1797
  findPendingBindingDuplicate(sessionId, workspaceId, currentBinding, snapshot) {
1203
1798
  if (!currentBinding || !isPendingBindingValue(currentBinding.providerSessionId)) {
@@ -1291,6 +1886,9 @@ export class SessionHistoryService {
1291
1886
  this.db
1292
1887
  .prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
1293
1888
  .run(input.sourceSessionId);
1889
+ this.db
1890
+ .prepare("DELETE FROM session_forks WHERE session_id = ?")
1891
+ .run(input.sourceSessionId);
1294
1892
  this.db
1295
1893
  .prepare("DELETE FROM session_indices WHERE session_id = ?")
1296
1894
  .run(input.sourceSessionId);
@@ -1362,11 +1960,25 @@ export class SessionHistoryService {
1362
1960
  relationMap.delete(sourceSessionId);
1363
1961
  relationMap.set(targetSessionId, {
1364
1962
  parentSessionId: targetRelation?.parentSessionId ?? sourceRelation?.parentSessionId ?? fallbackParentSessionId,
1963
+ sessionKind: targetRelation?.sessionKind
1964
+ ?? sourceRelation?.sessionKind
1965
+ ?? targetIndex?.sessionKind
1966
+ ?? sourceIndex?.sessionKind
1967
+ ?? "default",
1968
+ annotationSourceMessageId: targetRelation?.annotationSourceMessageId
1969
+ ?? sourceRelation?.annotationSourceMessageId
1970
+ ?? targetIndex?.annotationSourceMessageId
1971
+ ?? sourceIndex?.annotationSourceMessageId
1972
+ ?? null,
1973
+ annotationSourceText: targetRelation?.annotationSourceText
1974
+ ?? sourceRelation?.annotationSourceText
1975
+ ?? targetIndex?.annotationSourceText
1976
+ ?? sourceIndex?.annotationSourceText
1977
+ ?? null,
1365
1978
  isSubagent: Boolean(targetRelation?.isSubagent
1366
1979
  || sourceRelation?.isSubagent
1367
1980
  || targetIndex?.isSubagent
1368
- || sourceIndex?.isSubagent
1369
- || fallbackParentSessionId),
1981
+ || sourceIndex?.isSubagent),
1370
1982
  subagentLabel: targetRelation?.subagentLabel
1371
1983
  ?? sourceRelation?.subagentLabel
1372
1984
  ?? targetIndex?.subagentLabel
@@ -1396,6 +2008,9 @@ export class SessionHistoryService {
1396
2008
  this.db
1397
2009
  .prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
1398
2010
  .run(sessionId);
2011
+ this.db
2012
+ .prepare("DELETE FROM session_forks WHERE session_id = ?")
2013
+ .run(sessionId);
1399
2014
  this.db
1400
2015
  .prepare("DELETE FROM session_indices WHERE session_id = ?")
1401
2016
  .run(sessionId);
@@ -1426,17 +2041,24 @@ export class SessionHistoryService {
1426
2041
  const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1427
2042
  const inspection = inspectSessionActivity(binding.provider, binding.rawStoreRef);
1428
2043
  const timestamp = nowIso();
2044
+ const nowMs = Date.parse(timestamp);
2045
+ if (shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs)) {
2046
+ this.sessionActivityAuthorityService.clearSession(sessionId);
2047
+ }
1429
2048
  if (shouldPreserveRuntimeTerminalState(current, inspection)) {
1430
2049
  return current;
1431
2050
  }
1432
2051
  const resolution = this.sessionActivityAuthorityService.observe(buildInspectionActivityObservation(sessionId, inspection, timestamp));
2052
+ const resolvedLastEventAt = hasInspectionEvidence(inspection)
2053
+ ? resolution.lastObservedAt ?? inspection.lastEventAt ?? current?.lastEventAt ?? null
2054
+ : current?.lastEventAt ?? null;
1433
2055
  const nextRecord = {
1434
2056
  sessionId,
1435
2057
  userId,
1436
2058
  runningState: mapResolvedRunningStateToStored(resolution.runningState, current),
1437
2059
  activitySource: mapResolutionSourceToLegacyActivitySource(resolution.activityResolutionSource, inspection),
1438
2060
  favorite: current?.favorite ?? false,
1439
- lastEventAt: resolution.lastObservedAt ?? inspection.lastEventAt ?? current?.lastEventAt ?? null,
2061
+ lastEventAt: resolvedLastEventAt,
1440
2062
  completedAt: isTerminalResolvedRunningState(resolution.runningState)
1441
2063
  ? resolution.terminalAt ?? inspection.completedAtCandidate ?? current?.completedAt ?? null
1442
2064
  : null,
@@ -1496,11 +2118,52 @@ export class SessionHistoryService {
1496
2118
  });
1497
2119
  }
1498
2120
  }
2121
+ function isProviderCliBacked(provider) {
2122
+ return provider === "claude-code" || provider === "codex" || provider === "gemini" || provider === "kimi";
2123
+ }
2124
+ function buildProviderCliAvailabilitySnapshot(commandPaths) {
2125
+ return Object.freeze(Object.fromEntries(Object.entries(commandPaths).map(([provider, commandPath]) => [
2126
+ provider,
2127
+ isCommandAvailable(commandPath)
2128
+ ])));
2129
+ }
2130
+ function buildProviderCliUnavailableMessage(provider) {
2131
+ switch (provider) {
2132
+ case "claude-code":
2133
+ return "未检测到 Claude CLI";
2134
+ case "codex":
2135
+ return "未检测到 Codex CLI";
2136
+ case "gemini":
2137
+ return "未检测到 Gemini CLI";
2138
+ case "kimi":
2139
+ return "未检测到 Kimi CLI";
2140
+ default:
2141
+ return "未检测到对应 CLI";
2142
+ }
2143
+ }
2144
+ function createCodexForkTransportFactory(commandPath, homeDir) {
2145
+ return () => {
2146
+ const client = new CodexAppServerHelperClient(commandPath, { homeDir });
2147
+ const transport = client.createForkTransport();
2148
+ return {
2149
+ ...transport,
2150
+ close() {
2151
+ transport.close();
2152
+ client.dispose();
2153
+ }
2154
+ };
2155
+ };
2156
+ }
1499
2157
  function buildInspectionActivityObservation(sessionId, inspection, observedAt) {
2158
+ const resolvedRunningState = inspection.runningState === "failed"
2159
+ ? "failed"
2160
+ : inspection.completedAtCandidate
2161
+ ? "completed"
2162
+ : inspection.runningState;
1500
2163
  return {
1501
2164
  sessionId,
1502
2165
  runId: null,
1503
- runningState: inspection.runningState,
2166
+ runningState: resolvedRunningState,
1504
2167
  source: hasInspectionEvidence(inspection) ? "inferred_log" : "unknown",
1505
2168
  confidence: "weak",
1506
2169
  detail: inspection.errorDetail,
@@ -1557,6 +2220,20 @@ function clampLimit(limit) {
1557
2220
  }
1558
2221
  return Math.max(1, Math.min(Math.trunc(limit), 100));
1559
2222
  }
2223
+ function buildSessionStateRefreshCandidates(items, recentCount) {
2224
+ const recentItems = items.slice(0, recentCount);
2225
+ const activeResidues = items.filter((item) => isSessionStateRefreshCandidate(item));
2226
+ const deduped = new Map();
2227
+ for (const item of [...recentItems, ...activeResidues]) {
2228
+ deduped.set(item.sessionId, item);
2229
+ }
2230
+ return Array.from(deduped.values());
2231
+ }
2232
+ function isSessionStateRefreshCandidate(item) {
2233
+ return item.activityState === "running"
2234
+ || item.runningState === "starting"
2235
+ || item.runningState === "running";
2236
+ }
1560
2237
  function mapSessionStateRecordRow(row) {
1561
2238
  return {
1562
2239
  sessionId: row.session_id,
@@ -1622,6 +2299,9 @@ function mergeSessionIndexRecord(input) {
1622
2299
  workspaceId: input.workspaceId,
1623
2300
  provider: (input.target?.provider ?? input.source?.provider ?? input.provider),
1624
2301
  parentSessionId: input.target?.parentSessionId ?? input.source?.parentSessionId ?? null,
2302
+ sessionKind: input.target?.sessionKind ?? input.source?.sessionKind ?? "default",
2303
+ annotationSourceMessageId: input.target?.annotationSourceMessageId ?? input.source?.annotationSourceMessageId ?? null,
2304
+ annotationSourceText: input.target?.annotationSourceText ?? input.source?.annotationSourceText ?? null,
1625
2305
  isSubagent: Boolean(input.target?.isSubagent || input.source?.isSubagent),
1626
2306
  subagentLabel: input.target?.subagentLabel ?? input.source?.subagentLabel ?? null,
1627
2307
  title: pickPreferredSessionTitle(input.target?.title ?? null, input.source?.title ?? null),
@@ -1991,6 +2671,56 @@ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
1991
2671
  function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
1992
2672
  return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
1993
2673
  }
2674
+ function resolveSessionListTitle(provider, existingTitle, fallbackContent, parentTitle = null) {
2675
+ const normalizedExistingTitle = existingTitle?.trim() ?? "";
2676
+ const normalizedParentTitle = parentTitle?.trim() ?? "";
2677
+ const fallbackTitle = buildUserMessageTitle(fallbackContent, normalizedExistingTitle || "继续对话");
2678
+ if (normalizedExistingTitle.length > 0 &&
2679
+ !isSyntheticCodexSessionTitle(normalizedExistingTitle) &&
2680
+ (normalizedParentTitle.length === 0 ||
2681
+ normalizedExistingTitle !== normalizedParentTitle)) {
2682
+ return normalizedExistingTitle;
2683
+ }
2684
+ if (normalizedParentTitle.length > 0 && normalizedExistingTitle === normalizedParentTitle) {
2685
+ return fallbackTitle;
2686
+ }
2687
+ if (provider === "codex") {
2688
+ return fallbackTitle;
2689
+ }
2690
+ return normalizedExistingTitle || fallbackTitle;
2691
+ }
2692
+ function buildUserMessageTitle(content, fallbackTitle) {
2693
+ const title = content.trim().replace(/\s+/g, " ");
2694
+ return title.slice(0, 48) || fallbackTitle;
2695
+ }
2696
+ function resolvePersistedSessionTitle(provider, discoveredTitle, existingTitle, parentTitle = null) {
2697
+ const nextTitle = discoveredTitle.trim();
2698
+ const currentTitle = existingTitle?.trim() ?? "";
2699
+ const normalizedParentTitle = parentTitle?.trim() ?? "";
2700
+ if (!currentTitle) {
2701
+ if (provider === "codex" && isSyntheticCodexSessionTitle(nextTitle)) {
2702
+ return currentTitle;
2703
+ }
2704
+ if (normalizedParentTitle.length > 0 && nextTitle === normalizedParentTitle) {
2705
+ return currentTitle;
2706
+ }
2707
+ return nextTitle;
2708
+ }
2709
+ if (nextTitle.length === 0) {
2710
+ return currentTitle;
2711
+ }
2712
+ if (provider === "codex" && isSyntheticCodexSessionTitle(nextTitle)) {
2713
+ return currentTitle;
2714
+ }
2715
+ if (normalizedParentTitle.length > 0 && nextTitle === normalizedParentTitle && currentTitle !== normalizedParentTitle) {
2716
+ return currentTitle;
2717
+ }
2718
+ return nextTitle;
2719
+ }
2720
+ function isSyntheticCodexSessionTitle(title) {
2721
+ return (/^rollout-\d{4}-\d{2}-\d{2}t/i.test(title) ||
2722
+ /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(title));
2723
+ }
1994
2724
  function shouldRemoveHiddenClaudeDebugSession(session) {
1995
2725
  const normalizedRawStoreRef = session.rawStoreRef.replaceAll("\\", "/");
1996
2726
  if (normalizedRawStoreRef.includes("/subagents/")) {
@@ -1999,11 +2729,34 @@ function shouldRemoveHiddenClaudeDebugSession(session) {
1999
2729
  return (/^agent-[^/]+$/i.test(session.providerSessionId) &&
2000
2730
  /\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
2001
2731
  }
2732
+ const STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS = 120_000;
2733
+ function shouldClearStaleRuntimeWithoutInspection(current, inspection, nowMs) {
2734
+ if (!current || current.activitySource !== "runtime") {
2735
+ return false;
2736
+ }
2737
+ if (current.runningState !== "starting" && current.runningState !== "running") {
2738
+ return false;
2739
+ }
2740
+ if (inspection.lastEventAt || inspection.completedAtCandidate || inspection.errorCode) {
2741
+ return false;
2742
+ }
2743
+ if (!current.lastEventAt) {
2744
+ return true;
2745
+ }
2746
+ const lastEventAtMs = Date.parse(current.lastEventAt);
2747
+ if (!Number.isFinite(lastEventAtMs)) {
2748
+ return true;
2749
+ }
2750
+ return nowMs - lastEventAtMs > STALE_RUNTIME_WITHOUT_INSPECTION_GRACE_MS;
2751
+ }
2002
2752
  function shouldPreserveRuntimeTerminalState(current, inspection) {
2003
2753
  if (!current || current.activitySource !== "runtime") {
2004
2754
  return false;
2005
2755
  }
2006
- if (!inspection.lastEventAt || !current.lastEventAt) {
2756
+ if (!inspection.lastEventAt) {
2757
+ return !shouldClearStaleRuntimeWithoutInspection(current, inspection, Date.now());
2758
+ }
2759
+ if (!current.lastEventAt) {
2007
2760
  return true;
2008
2761
  }
2009
2762
  if (isTerminalRunningState(current.runningState)) {
@@ -2056,4 +2809,98 @@ function resolveActivityState(runningState, completedAt, lastSeenAt) {
2056
2809
  }
2057
2810
  return "idle";
2058
2811
  }
2812
+ function buildReconstructedForkPrompt(input) {
2813
+ const lines = [
2814
+ input.sourceTitle
2815
+ ? `源会话:${input.sourceTitle}`
2816
+ : "源会话:未命名会话",
2817
+ `源 provider:${input.sourceProvider}`,
2818
+ `目标 provider:${input.targetProvider}`,
2819
+ input.sourceType === "message"
2820
+ ? "分叉方式:从指定消息点重建后续上下文"
2821
+ : "分叉方式:从整条会话重建上下文",
2822
+ "",
2823
+ "下面是需要继承到新会话里的历史文本。",
2824
+ "请把这些内容当作已经发生过的上下文事实,不要逐条复述,也不要把它们当成新的用户问题重新回答。",
2825
+ "后续我会在这条新分支里继续追加新的指令。",
2826
+ ""
2827
+ ];
2828
+ if (input.messages.length === 0) {
2829
+ lines.push("当前没有可继承的历史文本。");
2830
+ return lines.join("\n");
2831
+ }
2832
+ for (const message of input.messages) {
2833
+ lines.push(message.role === "user" ? "[用户]" : "[助手]");
2834
+ lines.push(message.content.trim());
2835
+ lines.push("");
2836
+ }
2837
+ return lines.join("\n").trim();
2838
+ }
2839
+ function buildProviderCapabilityCacheKey(provider, workspacePath) {
2840
+ return `${provider}::${workspacePath ?? ""}`;
2841
+ }
2842
+ async function runWithConcurrency(items, concurrency, worker) {
2843
+ const normalizedConcurrency = Math.max(1, Math.floor(concurrency) || 1);
2844
+ const queue = [...items];
2845
+ const runners = Array.from({
2846
+ length: Math.min(normalizedConcurrency, queue.length || 1)
2847
+ }, async () => {
2848
+ while (queue.length > 0) {
2849
+ const current = queue.shift();
2850
+ if (current === undefined) {
2851
+ return;
2852
+ }
2853
+ await worker(current);
2854
+ }
2855
+ });
2856
+ await Promise.all(runners);
2857
+ }
2858
+ async function runBatchedTransactions(items, batchSize, transaction) {
2859
+ const normalizedBatchSize = Math.max(1, Math.floor(batchSize) || 1);
2860
+ let batchCount = 0;
2861
+ let maxBatchMs = 0;
2862
+ for (let index = 0; index < items.length; index += normalizedBatchSize) {
2863
+ const batch = items.slice(index, index + normalizedBatchSize);
2864
+ const batchStartedAt = Date.now();
2865
+ transaction(batch);
2866
+ const batchDurationMs = Date.now() - batchStartedAt;
2867
+ batchCount += 1;
2868
+ maxBatchMs = Math.max(maxBatchMs, batchDurationMs);
2869
+ if (index + normalizedBatchSize < items.length) {
2870
+ await delay(0);
2871
+ }
2872
+ }
2873
+ return {
2874
+ batchCount,
2875
+ maxBatchMs
2876
+ };
2877
+ }
2878
+ function applyImmediateModelOptionFallbacks(capabilities, codexSnapshot, openCodeSnapshot) {
2879
+ if (capabilities.provider === "codex") {
2880
+ return {
2881
+ ...capabilities,
2882
+ modelOptions: codexSnapshot?.modelOptions ?? createFallbackCodexModelOptions(null),
2883
+ defaultReasoningLevel: codexSnapshot?.defaultReasoningLevel ?? null,
2884
+ limitations: codexSnapshot
2885
+ ? capabilities.limitations
2886
+ : Array.from(new Set([
2887
+ ...capabilities.limitations,
2888
+ "当前暂时使用缓存或兜底模型列表,后台会继续刷新 Codex 能力。"
2889
+ ]))
2890
+ };
2891
+ }
2892
+ if (capabilities.provider === "opencode") {
2893
+ return {
2894
+ ...capabilities,
2895
+ modelOptions: openCodeSnapshot?.modelOptions ?? createFallbackOpenCodeModelOptions(null),
2896
+ limitations: openCodeSnapshot
2897
+ ? capabilities.limitations
2898
+ : Array.from(new Set([
2899
+ ...capabilities.limitations,
2900
+ "当前暂时使用缓存或兜底模型列表,后台会继续刷新 OpenCode 能力。"
2901
+ ]))
2902
+ };
2903
+ }
2904
+ return capabilities;
2905
+ }
2059
2906
  //# sourceMappingURL=session-history-service.js.map