@jingyi0605/codingns 0.1.0

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 (405) hide show
  1. package/README.md +24 -0
  2. package/bin/codingns.mjs +173 -0
  3. package/dist/public/assets/TerminalPage-6GBZ9nXN.css +32 -0
  4. package/dist/public/assets/TerminalPage-Dj_VDew3.js +54 -0
  5. package/dist/public/assets/index-C1GZV2wq.js +106 -0
  6. package/dist/public/assets/index-DU7f8NaZ.css +1 -0
  7. package/dist/public/index.html +13 -0
  8. package/dist/public/logo.png +0 -0
  9. package/dist/public/logo.svg +162 -0
  10. package/dist/server/config/env.d.ts +24 -0
  11. package/dist/server/config/env.js +152 -0
  12. package/dist/server/config/env.js.map +1 -0
  13. package/dist/server/config/opencode-base-url-resolver.d.ts +37 -0
  14. package/dist/server/config/opencode-base-url-resolver.js +422 -0
  15. package/dist/server/config/opencode-base-url-resolver.js.map +1 -0
  16. package/dist/server/main.d.ts +1 -0
  17. package/dist/server/main.js +3 -0
  18. package/dist/server/main.js.map +1 -0
  19. package/dist/server/middlewares/auth-guard.d.ts +4 -0
  20. package/dist/server/middlewares/auth-guard.js +35 -0
  21. package/dist/server/middlewares/auth-guard.js.map +1 -0
  22. package/dist/server/modules/auth/auth-controller.d.ts +15 -0
  23. package/dist/server/modules/auth/auth-controller.js +20 -0
  24. package/dist/server/modules/auth/auth-controller.js.map +1 -0
  25. package/dist/server/modules/auth/auth-service.d.ts +44 -0
  26. package/dist/server/modules/auth/auth-service.js +151 -0
  27. package/dist/server/modules/auth/auth-service.js.map +1 -0
  28. package/dist/server/modules/bootstrap/bootstrap-controller.d.ts +10 -0
  29. package/dist/server/modules/bootstrap/bootstrap-controller.js +13 -0
  30. package/dist/server/modules/bootstrap/bootstrap-controller.js.map +1 -0
  31. package/dist/server/modules/bootstrap/bootstrap-service.d.ts +20 -0
  32. package/dist/server/modules/bootstrap/bootstrap-service.js +71 -0
  33. package/dist/server/modules/bootstrap/bootstrap-service.js.map +1 -0
  34. package/dist/server/modules/client/client-controller.d.ts +9 -0
  35. package/dist/server/modules/client/client-controller.js +65 -0
  36. package/dist/server/modules/client/client-controller.js.map +1 -0
  37. package/dist/server/modules/client/client-service.d.ts +37 -0
  38. package/dist/server/modules/client/client-service.js +186 -0
  39. package/dist/server/modules/client/client-service.js.map +1 -0
  40. package/dist/server/modules/file/file-access-guard.d.ts +27 -0
  41. package/dist/server/modules/file/file-access-guard.js +99 -0
  42. package/dist/server/modules/file/file-access-guard.js.map +1 -0
  43. package/dist/server/modules/file/file-constants.d.ts +6 -0
  44. package/dist/server/modules/file/file-constants.js +7 -0
  45. package/dist/server/modules/file/file-constants.js.map +1 -0
  46. package/dist/server/modules/file/file-content-service.d.ts +65 -0
  47. package/dist/server/modules/file/file-content-service.js +239 -0
  48. package/dist/server/modules/file/file-content-service.js.map +1 -0
  49. package/dist/server/modules/file/file-context-controller.d.ts +29 -0
  50. package/dist/server/modules/file/file-context-controller.js +52 -0
  51. package/dist/server/modules/file/file-context-controller.js.map +1 -0
  52. package/dist/server/modules/file/file-context-service.d.ts +22 -0
  53. package/dist/server/modules/file/file-context-service.js +90 -0
  54. package/dist/server/modules/file/file-context-service.js.map +1 -0
  55. package/dist/server/modules/file/file-controller.d.ts +68 -0
  56. package/dist/server/modules/file/file-controller.js +111 -0
  57. package/dist/server/modules/file/file-controller.js.map +1 -0
  58. package/dist/server/modules/file/file-preview-service.d.ts +19 -0
  59. package/dist/server/modules/file/file-preview-service.js +60 -0
  60. package/dist/server/modules/file/file-preview-service.js.map +1 -0
  61. package/dist/server/modules/file/file-search-service.d.ts +13 -0
  62. package/dist/server/modules/file/file-search-service.js +62 -0
  63. package/dist/server/modules/file/file-search-service.js.map +1 -0
  64. package/dist/server/modules/file/file-tree-service.d.ts +7 -0
  65. package/dist/server/modules/file/file-tree-service.js +43 -0
  66. package/dist/server/modules/file/file-tree-service.js.map +1 -0
  67. package/dist/server/modules/file/file-version-checker.d.ts +10 -0
  68. package/dist/server/modules/file/file-version-checker.js +30 -0
  69. package/dist/server/modules/file/file-version-checker.js.map +1 -0
  70. package/dist/server/modules/file/path-normalizer.d.ts +2 -0
  71. package/dist/server/modules/file/path-normalizer.js +80 -0
  72. package/dist/server/modules/file/path-normalizer.js.map +1 -0
  73. package/dist/server/modules/file/recent-file-service.d.ts +10 -0
  74. package/dist/server/modules/file/recent-file-service.js +28 -0
  75. package/dist/server/modules/file/recent-file-service.js.map +1 -0
  76. package/dist/server/modules/git/commit-draft-service.d.ts +7 -0
  77. package/dist/server/modules/git/commit-draft-service.js +76 -0
  78. package/dist/server/modules/git/commit-draft-service.js.map +1 -0
  79. package/dist/server/modules/git/commit-orchestrator.d.ts +28 -0
  80. package/dist/server/modules/git/commit-orchestrator.js +47 -0
  81. package/dist/server/modules/git/commit-orchestrator.js.map +1 -0
  82. package/dist/server/modules/git/commit-rule-engine.d.ts +5 -0
  83. package/dist/server/modules/git/commit-rule-engine.js +22 -0
  84. package/dist/server/modules/git/commit-rule-engine.js.map +1 -0
  85. package/dist/server/modules/git/git-command-runner.d.ts +16 -0
  86. package/dist/server/modules/git/git-command-runner.js +102 -0
  87. package/dist/server/modules/git/git-command-runner.js.map +1 -0
  88. package/dist/server/modules/git/git-controller.d.ts +104 -0
  89. package/dist/server/modules/git/git-controller.js +140 -0
  90. package/dist/server/modules/git/git-controller.js.map +1 -0
  91. package/dist/server/modules/git/git-read-service.d.ts +15 -0
  92. package/dist/server/modules/git/git-read-service.js +393 -0
  93. package/dist/server/modules/git/git-read-service.js.map +1 -0
  94. package/dist/server/modules/git/git-rule-repository.d.ts +9 -0
  95. package/dist/server/modules/git/git-rule-repository.js +43 -0
  96. package/dist/server/modules/git/git-rule-repository.js.map +1 -0
  97. package/dist/server/modules/git/git-write-service.d.ts +28 -0
  98. package/dist/server/modules/git/git-write-service.js +330 -0
  99. package/dist/server/modules/git/git-write-service.js.map +1 -0
  100. package/dist/server/modules/git/types.d.ts +99 -0
  101. package/dist/server/modules/git/types.js +2 -0
  102. package/dist/server/modules/git/types.js.map +1 -0
  103. package/dist/server/modules/git/workspace-repo-guard.d.ts +17 -0
  104. package/dist/server/modules/git/workspace-repo-guard.js +124 -0
  105. package/dist/server/modules/git/workspace-repo-guard.js.map +1 -0
  106. package/dist/server/modules/preferences/quick-phrase-controller.d.ts +17 -0
  107. package/dist/server/modules/preferences/quick-phrase-controller.js +37 -0
  108. package/dist/server/modules/preferences/quick-phrase-controller.js.map +1 -0
  109. package/dist/server/modules/preferences/quick-phrase-service.d.ts +13 -0
  110. package/dist/server/modules/preferences/quick-phrase-service.js +67 -0
  111. package/dist/server/modules/preferences/quick-phrase-service.js.map +1 -0
  112. package/dist/server/modules/provider/claude-model-options.d.ts +5 -0
  113. package/dist/server/modules/provider/claude-model-options.js +116 -0
  114. package/dist/server/modules/provider/claude-model-options.js.map +1 -0
  115. package/dist/server/modules/provider/codex-model-options.d.ts +23 -0
  116. package/dist/server/modules/provider/codex-model-options.js +308 -0
  117. package/dist/server/modules/provider/codex-model-options.js.map +1 -0
  118. package/dist/server/modules/provider/opencode-model-options.d.ts +30 -0
  119. package/dist/server/modules/provider/opencode-model-options.js +362 -0
  120. package/dist/server/modules/provider/opencode-model-options.js.map +1 -0
  121. package/dist/server/modules/provider/provider-controller.d.ts +33 -0
  122. package/dist/server/modules/provider/provider-controller.js +50 -0
  123. package/dist/server/modules/provider/provider-controller.js.map +1 -0
  124. package/dist/server/modules/sessions/session-activity-inspector.d.ts +10 -0
  125. package/dist/server/modules/sessions/session-activity-inspector.js +271 -0
  126. package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -0
  127. package/dist/server/modules/sessions/session-changed-file-service.d.ts +14 -0
  128. package/dist/server/modules/sessions/session-changed-file-service.js +175 -0
  129. package/dist/server/modules/sessions/session-changed-file-service.js.map +1 -0
  130. package/dist/server/modules/sessions/session-controller.d.ts +134 -0
  131. package/dist/server/modules/sessions/session-controller.js +253 -0
  132. package/dist/server/modules/sessions/session-controller.js.map +1 -0
  133. package/dist/server/modules/sessions/session-history-service.d.ts +128 -0
  134. package/dist/server/modules/sessions/session-history-service.js +1558 -0
  135. package/dist/server/modules/sessions/session-history-service.js.map +1 -0
  136. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +191 -0
  137. package/dist/server/modules/sessions/session-live-runtime-service.js +1303 -0
  138. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -0
  139. package/dist/server/modules/sessions/session-message-attachment-service.d.ts +42 -0
  140. package/dist/server/modules/sessions/session-message-attachment-service.js +244 -0
  141. package/dist/server/modules/sessions/session-message-attachment-service.js.map +1 -0
  142. package/dist/server/modules/sessions/session-provider-error-mapper.d.ts +2 -0
  143. package/dist/server/modules/sessions/session-provider-error-mapper.js +101 -0
  144. package/dist/server/modules/sessions/session-provider-error-mapper.js.map +1 -0
  145. package/dist/server/modules/terminal/command-template-service.d.ts +48 -0
  146. package/dist/server/modules/terminal/command-template-service.js +273 -0
  147. package/dist/server/modules/terminal/command-template-service.js.map +1 -0
  148. package/dist/server/modules/terminal/runtime/adapters/embedded-pty-runtime-adapter.d.ts +22 -0
  149. package/dist/server/modules/terminal/runtime/adapters/embedded-pty-runtime-adapter.js +30 -0
  150. package/dist/server/modules/terminal/runtime/adapters/embedded-pty-runtime-adapter.js.map +1 -0
  151. package/dist/server/modules/terminal/runtime/adapters/tmux-runtime-adapter.d.ts +31 -0
  152. package/dist/server/modules/terminal/runtime/adapters/tmux-runtime-adapter.js +199 -0
  153. package/dist/server/modules/terminal/runtime/adapters/tmux-runtime-adapter.js.map +1 -0
  154. package/dist/server/modules/terminal/runtime/pty-host-attachment-manager.d.ts +36 -0
  155. package/dist/server/modules/terminal/runtime/pty-host-attachment-manager.js +141 -0
  156. package/dist/server/modules/terminal/runtime/pty-host-attachment-manager.js.map +1 -0
  157. package/dist/server/modules/terminal/runtime/pty-runtime-manager.d.ts +29 -0
  158. package/dist/server/modules/terminal/runtime/pty-runtime-manager.js +138 -0
  159. package/dist/server/modules/terminal/runtime/pty-runtime-manager.js.map +1 -0
  160. package/dist/server/modules/terminal/runtime/terminal-log-file-store.d.ts +14 -0
  161. package/dist/server/modules/terminal/runtime/terminal-log-file-store.js +47 -0
  162. package/dist/server/modules/terminal/runtime/terminal-log-file-store.js.map +1 -0
  163. package/dist/server/modules/terminal/runtime/terminal-log-spooler.d.ts +28 -0
  164. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js +162 -0
  165. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js.map +1 -0
  166. package/dist/server/modules/terminal/runtime/terminal-output-buffer.d.ts +18 -0
  167. package/dist/server/modules/terminal/runtime/terminal-output-buffer.js +109 -0
  168. package/dist/server/modules/terminal/runtime/terminal-output-buffer.js.map +1 -0
  169. package/dist/server/modules/terminal/runtime/terminal-runtime-adapter.d.ts +34 -0
  170. package/dist/server/modules/terminal/runtime/terminal-runtime-adapter.js +2 -0
  171. package/dist/server/modules/terminal/runtime/terminal-runtime-adapter.js.map +1 -0
  172. package/dist/server/modules/terminal/runtime/terminal-runtime-manager.d.ts +41 -0
  173. package/dist/server/modules/terminal/runtime/terminal-runtime-manager.js +138 -0
  174. package/dist/server/modules/terminal/runtime/terminal-runtime-manager.js.map +1 -0
  175. package/dist/server/modules/terminal/template-port-runtime.d.ts +6 -0
  176. package/dist/server/modules/terminal/template-port-runtime.js +199 -0
  177. package/dist/server/modules/terminal/template-port-runtime.js.map +1 -0
  178. package/dist/server/modules/terminal/terminal-controller.d.ts +94 -0
  179. package/dist/server/modules/terminal/terminal-controller.js +236 -0
  180. package/dist/server/modules/terminal/terminal-controller.js.map +1 -0
  181. package/dist/server/modules/terminal/terminal-history.d.ts +18 -0
  182. package/dist/server/modules/terminal/terminal-history.js +2 -0
  183. package/dist/server/modules/terminal/terminal-history.js.map +1 -0
  184. package/dist/server/modules/terminal/terminal-paths.d.ts +1 -0
  185. package/dist/server/modules/terminal/terminal-paths.js +27 -0
  186. package/dist/server/modules/terminal/terminal-paths.js.map +1 -0
  187. package/dist/server/modules/terminal/terminal-service.d.ts +112 -0
  188. package/dist/server/modules/terminal/terminal-service.js +794 -0
  189. package/dist/server/modules/terminal/terminal-service.js.map +1 -0
  190. package/dist/server/modules/terminal/terminal-shell.d.ts +13 -0
  191. package/dist/server/modules/terminal/terminal-shell.js +293 -0
  192. package/dist/server/modules/terminal/terminal-shell.js.map +1 -0
  193. package/dist/server/modules/workbench/workbench-controller.d.ts +7 -0
  194. package/dist/server/modules/workbench/workbench-controller.js +22 -0
  195. package/dist/server/modules/workbench/workbench-controller.js.map +1 -0
  196. package/dist/server/modules/workbench/workbench-service.d.ts +19 -0
  197. package/dist/server/modules/workbench/workbench-service.js +46 -0
  198. package/dist/server/modules/workbench/workbench-service.js.map +1 -0
  199. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +65 -0
  200. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +176 -0
  201. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -0
  202. package/dist/server/modules/workspace/workspace-controller.d.ts +57 -0
  203. package/dist/server/modules/workspace/workspace-controller.js +38 -0
  204. package/dist/server/modules/workspace/workspace-controller.js.map +1 -0
  205. package/dist/server/modules/workspace/workspace-service.d.ts +81 -0
  206. package/dist/server/modules/workspace/workspace-service.js +659 -0
  207. package/dist/server/modules/workspace/workspace-service.js.map +1 -0
  208. package/dist/server/routes/auth.d.ts +3 -0
  209. package/dist/server/routes/auth.js +6 -0
  210. package/dist/server/routes/auth.js.map +1 -0
  211. package/dist/server/routes/client.d.ts +3 -0
  212. package/dist/server/routes/client.js +6 -0
  213. package/dist/server/routes/client.js.map +1 -0
  214. package/dist/server/routes/files.d.ts +3 -0
  215. package/dist/server/routes/files.js +15 -0
  216. package/dist/server/routes/files.js.map +1 -0
  217. package/dist/server/routes/git.d.ts +3 -0
  218. package/dist/server/routes/git.js +18 -0
  219. package/dist/server/routes/git.js.map +1 -0
  220. package/dist/server/routes/preferences.d.ts +3 -0
  221. package/dist/server/routes/preferences.js +5 -0
  222. package/dist/server/routes/preferences.js.map +1 -0
  223. package/dist/server/routes/providers.d.ts +3 -0
  224. package/dist/server/routes/providers.js +6 -0
  225. package/dist/server/routes/providers.js.map +1 -0
  226. package/dist/server/routes/public.d.ts +3 -0
  227. package/dist/server/routes/public.js +5 -0
  228. package/dist/server/routes/public.js.map +1 -0
  229. package/dist/server/routes/session-contexts.d.ts +3 -0
  230. package/dist/server/routes/session-contexts.js +6 -0
  231. package/dist/server/routes/session-contexts.js.map +1 -0
  232. package/dist/server/routes/sessions.d.ts +3 -0
  233. package/dist/server/routes/sessions.js +24 -0
  234. package/dist/server/routes/sessions.js.map +1 -0
  235. package/dist/server/routes/terminals.d.ts +3 -0
  236. package/dist/server/routes/terminals.js +17 -0
  237. package/dist/server/routes/terminals.js.map +1 -0
  238. package/dist/server/routes/workbench.d.ts +3 -0
  239. package/dist/server/routes/workbench.js +4 -0
  240. package/dist/server/routes/workbench.js.map +1 -0
  241. package/dist/server/routes/workspaces.d.ts +3 -0
  242. package/dist/server/routes/workspaces.js +10 -0
  243. package/dist/server/routes/workspaces.js.map +1 -0
  244. package/dist/server/server/create-server.d.ts +103 -0
  245. package/dist/server/server/create-server.js +259 -0
  246. package/dist/server/server/create-server.js.map +1 -0
  247. package/dist/server/server/start-host.d.ts +8 -0
  248. package/dist/server/server/start-host.js +40 -0
  249. package/dist/server/server/start-host.js.map +1 -0
  250. package/dist/server/server/static-web.d.ts +2 -0
  251. package/dist/server/server/static-web.js +75 -0
  252. package/dist/server/server/static-web.js.map +1 -0
  253. package/dist/server/shared/errors/app-error.d.ts +13 -0
  254. package/dist/server/shared/errors/app-error.js +16 -0
  255. package/dist/server/shared/errors/app-error.js.map +1 -0
  256. package/dist/server/shared/http/error-handler.d.ts +9 -0
  257. package/dist/server/shared/http/error-handler.js +25 -0
  258. package/dist/server/shared/http/error-handler.js.map +1 -0
  259. package/dist/server/shared/utils/hash.d.ts +4 -0
  260. package/dist/server/shared/utils/hash.js +27 -0
  261. package/dist/server/shared/utils/hash.js.map +1 -0
  262. package/dist/server/shared/utils/id.d.ts +1 -0
  263. package/dist/server/shared/utils/id.js +5 -0
  264. package/dist/server/shared/utils/id.js.map +1 -0
  265. package/dist/server/shared/utils/perf-log.d.ts +6 -0
  266. package/dist/server/shared/utils/perf-log.js +40 -0
  267. package/dist/server/shared/utils/perf-log.js.map +1 -0
  268. package/dist/server/shared/utils/time.d.ts +2 -0
  269. package/dist/server/shared/utils/time.js +7 -0
  270. package/dist/server/shared/utils/time.js.map +1 -0
  271. package/dist/server/shared/utils/tokens.d.ts +1 -0
  272. package/dist/server/shared/utils/tokens.js +5 -0
  273. package/dist/server/shared/utils/tokens.js.map +1 -0
  274. package/dist/server/storage/repositories/auth-token-repository.d.ts +9 -0
  275. package/dist/server/storage/repositories/auth-token-repository.js +45 -0
  276. package/dist/server/storage/repositories/auth-token-repository.js.map +1 -0
  277. package/dist/server/storage/repositories/auth-user-repository.d.ts +11 -0
  278. package/dist/server/storage/repositories/auth-user-repository.js +61 -0
  279. package/dist/server/storage/repositories/auth-user-repository.js.map +1 -0
  280. package/dist/server/storage/repositories/bootstrap-state-repository.d.ts +8 -0
  281. package/dist/server/storage/repositories/bootstrap-state-repository.js +29 -0
  282. package/dist/server/storage/repositories/bootstrap-state-repository.js.map +1 -0
  283. package/dist/server/storage/repositories/commit-rule-profile-repository.d.ts +8 -0
  284. package/dist/server/storage/repositories/commit-rule-profile-repository.js +48 -0
  285. package/dist/server/storage/repositories/commit-rule-profile-repository.js.map +1 -0
  286. package/dist/server/storage/repositories/file-context-binding-repository.d.ts +12 -0
  287. package/dist/server/storage/repositories/file-context-binding-repository.js +130 -0
  288. package/dist/server/storage/repositories/file-context-binding-repository.js.map +1 -0
  289. package/dist/server/storage/repositories/recent-file-repository.d.ts +10 -0
  290. package/dist/server/storage/repositories/recent-file-repository.js +64 -0
  291. package/dist/server/storage/repositories/recent-file-repository.js.map +1 -0
  292. package/dist/server/storage/repositories/session-binding-repository.d.ts +10 -0
  293. package/dist/server/storage/repositories/session-binding-repository.js +63 -0
  294. package/dist/server/storage/repositories/session-binding-repository.js.map +1 -0
  295. package/dist/server/storage/repositories/session-changed-file-repository.d.ts +11 -0
  296. package/dist/server/storage/repositories/session-changed-file-repository.js +94 -0
  297. package/dist/server/storage/repositories/session-changed-file-repository.js.map +1 -0
  298. package/dist/server/storage/repositories/session-index-repository.d.ts +11 -0
  299. package/dist/server/storage/repositories/session-index-repository.js +200 -0
  300. package/dist/server/storage/repositories/session-index-repository.js.map +1 -0
  301. package/dist/server/storage/repositories/session-message-attachment-repository.d.ts +13 -0
  302. package/dist/server/storage/repositories/session-message-attachment-repository.js +139 -0
  303. package/dist/server/storage/repositories/session-message-attachment-repository.js.map +1 -0
  304. package/dist/server/storage/repositories/session-send-queue-repository.d.ts +15 -0
  305. package/dist/server/storage/repositories/session-send-queue-repository.js +165 -0
  306. package/dist/server/storage/repositories/session-send-queue-repository.js.map +1 -0
  307. package/dist/server/storage/repositories/session-state-repository.d.ts +8 -0
  308. package/dist/server/storage/repositories/session-state-repository.js +60 -0
  309. package/dist/server/storage/repositories/session-state-repository.js.map +1 -0
  310. package/dist/server/storage/repositories/session-status-snapshot-repository.d.ts +8 -0
  311. package/dist/server/storage/repositories/session-status-snapshot-repository.js +49 -0
  312. package/dist/server/storage/repositories/session-status-snapshot-repository.js.map +1 -0
  313. package/dist/server/storage/repositories/terminal-command-template-repository.d.ts +11 -0
  314. package/dist/server/storage/repositories/terminal-command-template-repository.js +99 -0
  315. package/dist/server/storage/repositories/terminal-command-template-repository.js.map +1 -0
  316. package/dist/server/storage/repositories/terminal-instance-repository.d.ts +23 -0
  317. package/dist/server/storage/repositories/terminal-instance-repository.js +149 -0
  318. package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -0
  319. package/dist/server/storage/repositories/terminal-log-file-repository.d.ts +20 -0
  320. package/dist/server/storage/repositories/terminal-log-file-repository.js +106 -0
  321. package/dist/server/storage/repositories/terminal-log-file-repository.js.map +1 -0
  322. package/dist/server/storage/repositories/terminal-log-segment-repository.d.ts +11 -0
  323. package/dist/server/storage/repositories/terminal-log-segment-repository.js +110 -0
  324. package/dist/server/storage/repositories/terminal-log-segment-repository.js.map +1 -0
  325. package/dist/server/storage/repositories/terminal-runtime-session-repository.d.ts +24 -0
  326. package/dist/server/storage/repositories/terminal-runtime-session-repository.js +134 -0
  327. package/dist/server/storage/repositories/terminal-runtime-session-repository.js.map +1 -0
  328. package/dist/server/storage/repositories/user-quick-phrase-preference-repository.d.ts +8 -0
  329. package/dist/server/storage/repositories/user-quick-phrase-preference-repository.js +37 -0
  330. package/dist/server/storage/repositories/user-quick-phrase-preference-repository.js.map +1 -0
  331. package/dist/server/storage/repositories/workspace-repository.d.ts +16 -0
  332. package/dist/server/storage/repositories/workspace-repository.js +71 -0
  333. package/dist/server/storage/repositories/workspace-repository.js.map +1 -0
  334. package/dist/server/storage/sqlite/client.d.ts +6 -0
  335. package/dist/server/storage/sqlite/client.js +446 -0
  336. package/dist/server/storage/sqlite/client.js.map +1 -0
  337. package/dist/server/storage/sqlite/schema.sql +357 -0
  338. package/dist/server/types/domain.d.ts +306 -0
  339. package/dist/server/types/domain.js +2 -0
  340. package/dist/server/types/domain.js.map +1 -0
  341. package/dist/server/ws/terminal-ws-hub.d.ts +32 -0
  342. package/dist/server/ws/terminal-ws-hub.js +181 -0
  343. package/dist/server/ws/terminal-ws-hub.js.map +1 -0
  344. package/dist/server/ws/workbench-ws-hub.d.ts +30 -0
  345. package/dist/server/ws/workbench-ws-hub.js +480 -0
  346. package/dist/server/ws/workbench-ws-hub.js.map +1 -0
  347. package/dist/server/ws/ws-auth-guard.d.ts +7 -0
  348. package/dist/server/ws/ws-auth-guard.js +28 -0
  349. package/dist/server/ws/ws-auth-guard.js.map +1 -0
  350. package/dist/server/ws/ws-server.d.ts +11 -0
  351. package/dist/server/ws/ws-server.js +218 -0
  352. package/dist/server/ws/ws-server.js.map +1 -0
  353. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.d.ts +36 -0
  354. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js +213 -0
  355. package/node_modules/@codingns/session-sync-core/dist/claude-message-utils.js.map +1 -0
  356. package/node_modules/@codingns/session-sync-core/dist/index.d.ts +12 -0
  357. package/node_modules/@codingns/session-sync-core/dist/index.js +13 -0
  358. package/node_modules/@codingns/session-sync-core/dist/index.js.map +1 -0
  359. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +30 -0
  360. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +567 -0
  361. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -0
  362. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +41 -0
  363. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +1365 -0
  364. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -0
  365. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.d.ts +41 -0
  366. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js +280 -0
  367. package/node_modules/@codingns/session-sync-core/dist/providers/opencode-shared.js.map +1 -0
  368. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +53 -0
  369. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +745 -0
  370. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -0
  371. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +25 -0
  372. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +284 -0
  373. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -0
  374. package/node_modules/@codingns/session-sync-core/dist/registry.d.ts +7 -0
  375. package/node_modules/@codingns/session-sync-core/dist/registry.js +22 -0
  376. package/node_modules/@codingns/session-sync-core/dist/registry.js.map +1 -0
  377. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.d.ts +18 -0
  378. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js +236 -0
  379. package/node_modules/@codingns/session-sync-core/dist/runtime/active-run-registry.js.map +1 -0
  380. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.d.ts +21 -0
  381. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +732 -0
  382. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -0
  383. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-permissions.d.ts +1 -0
  384. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-permissions.js +16 -0
  385. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-permissions.js.map +1 -0
  386. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.d.ts +26 -0
  387. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +892 -0
  388. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -0
  389. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.d.ts +31 -0
  390. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js +626 -0
  391. package/node_modules/@codingns/session-sync-core/dist/runtime/opencode-runtime.js.map +1 -0
  392. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.d.ts +18 -0
  393. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js +148 -0
  394. package/node_modules/@codingns/session-sync-core/dist/runtime/provider-runtime-service.js.map +1 -0
  395. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +141 -0
  396. package/node_modules/@codingns/session-sync-core/dist/runtime/types.js +2 -0
  397. package/node_modules/@codingns/session-sync-core/dist/runtime/types.js.map +1 -0
  398. package/node_modules/@codingns/session-sync-core/dist/services.d.ts +26 -0
  399. package/node_modules/@codingns/session-sync-core/dist/services.js +85 -0
  400. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -0
  401. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +159 -0
  402. package/node_modules/@codingns/session-sync-core/dist/types.js +2 -0
  403. package/node_modules/@codingns/session-sync-core/dist/types.js.map +1 -0
  404. package/node_modules/@codingns/session-sync-core/package.json +25 -0
  405. package/package.json +34 -0
@@ -0,0 +1,1558 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { CapabilityService, ClaudeCodeAdapter, CodexAdapter, OpenCodeAdapter, ProviderRegistry, SessionSyncService } from "@codingns/session-sync-core";
3
+ import { AppError } from "../../shared/errors/app-error.js";
4
+ import { createId } from "../../shared/utils/id.js";
5
+ import { logPerformance } from "../../shared/utils/perf-log.js";
6
+ import { nowIso } from "../../shared/utils/time.js";
7
+ import { inspectSessionActivity } from "./session-activity-inspector.js";
8
+ import { mapSessionProviderError } from "./session-provider-error-mapper.js";
9
+ import { enrichClaudeCapabilities } from "../provider/claude-model-options.js";
10
+ import { CodexModelOptionsService, enrichCodexCapabilities } from "../provider/codex-model-options.js";
11
+ import { OpenCodeModelOptionsService, enrichOpenCodeCapabilities } from "../provider/opencode-model-options.js";
12
+ export class SessionHistoryService {
13
+ db;
14
+ workspaceRepository;
15
+ sessionBindingRepository;
16
+ sessionChangedFileService;
17
+ sessionIndexRepository;
18
+ sessionMessageAttachmentService;
19
+ sessionStateRepository;
20
+ sessionStatusSnapshotRepository;
21
+ providerRegistry;
22
+ sessionSyncService;
23
+ capabilityService;
24
+ claudeCodeHomeDir;
25
+ codexModelOptionsService;
26
+ openCodeModelOptionsService;
27
+ workspaceDiscoveryStatuses = new Map();
28
+ workspaceDiscoveryInflight = new Map();
29
+ workspaceStateRefreshInflight = new Map();
30
+ workspaceSessionRelations = new Map();
31
+ constructor(db, workspaceRepository, sessionBindingRepository, sessionChangedFileService, sessionIndexRepository, sessionMessageAttachmentService, sessionStateRepository, sessionStatusSnapshotRepository, config) {
32
+ this.db = db;
33
+ this.workspaceRepository = workspaceRepository;
34
+ this.sessionBindingRepository = sessionBindingRepository;
35
+ this.sessionChangedFileService = sessionChangedFileService;
36
+ this.sessionIndexRepository = sessionIndexRepository;
37
+ this.sessionMessageAttachmentService = sessionMessageAttachmentService;
38
+ this.sessionStateRepository = sessionStateRepository;
39
+ this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
40
+ this.claudeCodeHomeDir = config.claudeCodeHomeDir;
41
+ this.providerRegistry = new ProviderRegistry([
42
+ new ClaudeCodeAdapter({ homeDir: config.claudeCodeHomeDir }),
43
+ new CodexAdapter({ homeDir: config.codexHomeDir }),
44
+ new OpenCodeAdapter({
45
+ baseUrl: config.opencodeBaseUrl,
46
+ baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver),
47
+ dataDir: config.opencodeDataDir,
48
+ dbPath: config.opencodeDbPath
49
+ })
50
+ ]);
51
+ this.sessionSyncService = new SessionSyncService(this.providerRegistry);
52
+ this.capabilityService = new CapabilityService(this.providerRegistry);
53
+ this.codexModelOptionsService = new CodexModelOptionsService({
54
+ commandPath: config.codexCliPath
55
+ });
56
+ this.openCodeModelOptionsService = new OpenCodeModelOptionsService({
57
+ baseUrl: config.opencodeBaseUrl,
58
+ baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver),
59
+ commandPath: config.opencodeCliPath
60
+ });
61
+ }
62
+ async discoverWorkspaceSessions(workspaceId, userId, options) {
63
+ const maxAgeMs = options?.maxAgeMs ?? 0;
64
+ const force = options?.force ?? false;
65
+ const discoveryStatus = this.workspaceDiscoveryStatuses.get(workspaceId);
66
+ const lastRefreshedAt = discoveryStatus?.refreshedAt ?? 0;
67
+ if (!force &&
68
+ discoveryStatus?.isComplete === true &&
69
+ maxAgeMs > 0 &&
70
+ Date.now() - lastRefreshedAt <= maxAgeMs) {
71
+ return this.listWorkspaceSessions(workspaceId, userId);
72
+ }
73
+ const inflight = this.workspaceDiscoveryInflight.get(workspaceId);
74
+ if (inflight) {
75
+ return inflight;
76
+ }
77
+ const task = this.runDiscoverWorkspaceSessions(workspaceId, userId, options?.refreshStateMode ?? "inline").finally(() => {
78
+ this.workspaceDiscoveryInflight.delete(workspaceId);
79
+ });
80
+ this.workspaceDiscoveryInflight.set(workspaceId, task);
81
+ return task;
82
+ }
83
+ needsWorkspaceDiscovery(workspaceId, maxAgeMs) {
84
+ if (maxAgeMs <= 0) {
85
+ return true;
86
+ }
87
+ const discoveryStatus = this.workspaceDiscoveryStatuses.get(workspaceId);
88
+ if (!discoveryStatus) {
89
+ return true;
90
+ }
91
+ if (!discoveryStatus.isComplete) {
92
+ return true;
93
+ }
94
+ return Date.now() - discoveryStatus.refreshedAt > maxAgeMs;
95
+ }
96
+ async readSessionHistory(sessionId, cursor, limit, direction = "forward", userId) {
97
+ const startedAt = Date.now();
98
+ const binding = this.getBindingOrThrow(sessionId);
99
+ const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
100
+ const safeLimit = clampLimit(limit);
101
+ const knownTotalMessageCount = direction === "backward" && cursor === null
102
+ ? this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null
103
+ : null;
104
+ let readDurationMs = 0;
105
+ let refreshStateDurationMs = 0;
106
+ this.upsertSnapshot(sessionId, {
107
+ syncStatus: "syncing",
108
+ syncCursor: current?.syncCursor ?? cursor,
109
+ lastSyncAt: current?.lastSyncAt ?? null,
110
+ lastErrorCode: null,
111
+ lastErrorDetail: null,
112
+ resumedAt: current?.resumedAt ?? null
113
+ });
114
+ try {
115
+ const readStartedAt = Date.now();
116
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, safeLimit, direction, knownTotalMessageCount);
117
+ readDurationMs = Date.now() - readStartedAt;
118
+ this.upsertSnapshot(sessionId, {
119
+ syncStatus: "idle",
120
+ syncCursor: direction === "backward" && cursor !== null
121
+ ? current?.syncCursor ?? page.cursor
122
+ : page.cursor,
123
+ lastSyncAt: nowIso(),
124
+ lastErrorCode: null,
125
+ lastErrorDetail: null,
126
+ resumedAt: current?.resumedAt ?? null
127
+ });
128
+ logPerformance("session.read_history", Date.now() - startedAt, {
129
+ sessionId,
130
+ provider: binding.provider,
131
+ direction,
132
+ limit: safeLimit,
133
+ hasCursor: cursor !== null,
134
+ messageCount: page.messages.length,
135
+ total: page.total,
136
+ readMs: readDurationMs,
137
+ refreshStateMs: refreshStateDurationMs
138
+ }, {
139
+ thresholdMs: 300
140
+ });
141
+ return page;
142
+ }
143
+ catch (error) {
144
+ logPerformance("session.read_history.failed", Date.now() - startedAt, {
145
+ sessionId,
146
+ provider: binding.provider,
147
+ direction,
148
+ limit: safeLimit,
149
+ hasCursor: cursor !== null,
150
+ readMs: readDurationMs,
151
+ refreshStateMs: refreshStateDurationMs,
152
+ error: error instanceof Error ? error.message : "unknown"
153
+ }, {
154
+ thresholdMs: 0,
155
+ force: true
156
+ });
157
+ this.markSessionError(sessionId, "PROVIDER_READ_FAILED", error);
158
+ throw mapSessionProviderError(error);
159
+ }
160
+ }
161
+ async findLatestUserMessage(sessionId, content, maxAttempts = 12, minTimestamp = null) {
162
+ const binding = this.getBindingOrThrow(sessionId);
163
+ const acceptedContents = new Set((Array.isArray(content) ? content : [content]).filter((value) => value.trim().length > 0));
164
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
165
+ const knownTotalMessageCount = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId)?.messageCount ?? null;
166
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, 30, "backward", knownTotalMessageCount);
167
+ const matched = [...page.messages]
168
+ .reverse()
169
+ .find((message) => message.role === "user" &&
170
+ acceptedContents.has(message.content) &&
171
+ isMessageAtOrAfter(message.timestamp, minTimestamp));
172
+ if (matched) {
173
+ return matched;
174
+ }
175
+ if (attempt < maxAttempts - 1) {
176
+ await delay(100);
177
+ }
178
+ }
179
+ return null;
180
+ }
181
+ readSessionAttachment(sessionId, attachmentId) {
182
+ return this.sessionMessageAttachmentService.readAttachmentContent(sessionId, attachmentId);
183
+ }
184
+ getSession(sessionId, userId) {
185
+ return this.enrichSessionItem(this.getSessionListItemOrThrow(sessionId, userId));
186
+ }
187
+ async refreshRuntimeFallbackSession(sessionId, userId) {
188
+ await this.refreshSessionState(sessionId, userId);
189
+ return this.enrichSessionItem(this.getSessionListItemOrThrow(sessionId, userId));
190
+ }
191
+ async syncSessionTitle(sessionId) {
192
+ const binding = this.getBindingOrThrow(sessionId);
193
+ await this.syncSessionTitleFromProvider(sessionId, binding);
194
+ }
195
+ async syncWorkspaceSessionTitles(workspaceId, userId) {
196
+ const sessions = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
197
+ for (const session of sessions) {
198
+ await this.syncSessionTitle(session.sessionId).catch(() => {
199
+ return;
200
+ });
201
+ }
202
+ }
203
+ async listSessionChangedFiles(sessionId, userId) {
204
+ this.getSession(sessionId, userId);
205
+ await this.ensureSessionChangedFilesIndexed(sessionId);
206
+ return this.sessionChangedFileService.listBySessionId(sessionId);
207
+ }
208
+ listWorkspaceSessions(workspaceId, userId) {
209
+ return this.enrichSessionItems(workspaceId, this.sessionIndexRepository.listByWorkspace(workspaceId, userId));
210
+ }
211
+ getProviderCapabilitiesSnapshot(provider) {
212
+ try {
213
+ return this.capabilityService.getProviderCapabilities(provider);
214
+ }
215
+ catch (error) {
216
+ throw mapSessionProviderError(error);
217
+ }
218
+ }
219
+ async getProviderCapabilities(provider, workspaceId) {
220
+ try {
221
+ const workspacePath = workspaceId ? this.getWorkspaceOrThrow(workspaceId).path : null;
222
+ return await this.enrichProviderCapabilities(this.capabilityService.getProviderCapabilities(provider), workspacePath);
223
+ }
224
+ catch (error) {
225
+ throw mapSessionProviderError(error);
226
+ }
227
+ }
228
+ async getSessionCapabilities(sessionId) {
229
+ const binding = this.getBindingOrThrow(sessionId);
230
+ const workspace = this.getWorkspaceOrThrow(binding.workspaceId);
231
+ return this.capabilityService
232
+ .getSessionCapabilities(binding.provider, binding.providerSessionId)
233
+ .then((capabilities) => this.enrichProviderCapabilities(capabilities, workspace.path))
234
+ .catch((error) => {
235
+ throw mapSessionProviderError(error);
236
+ });
237
+ }
238
+ async enrichProviderCapabilities(capabilities, workspacePath) {
239
+ const claudeEnriched = enrichClaudeCapabilities(capabilities, {
240
+ claudeHomeDir: this.claudeCodeHomeDir,
241
+ workspacePath
242
+ });
243
+ const codexEnriched = await enrichCodexCapabilities(claudeEnriched, this.codexModelOptionsService);
244
+ return enrichOpenCodeCapabilities(codexEnriched, this.openCodeModelOptionsService, workspacePath);
245
+ }
246
+ async getSessionContextUsage(sessionId) {
247
+ const binding = this.getBindingOrThrow(sessionId);
248
+ try {
249
+ return await this.sessionSyncService.readContextUsage(binding.provider, binding.providerSessionId, binding.rawStoreRef);
250
+ }
251
+ catch (error) {
252
+ throw mapSessionProviderError(error);
253
+ }
254
+ }
255
+ async resumeSession(sessionId) {
256
+ const binding = this.getBindingOrThrow(sessionId);
257
+ try {
258
+ const result = await this.sessionSyncService.resumeSession(binding.provider, binding.providerSessionId, binding.rawStoreRef);
259
+ this.upsertSnapshot(sessionId, {
260
+ syncStatus: "idle",
261
+ syncCursor: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.syncCursor ?? null,
262
+ lastSyncAt: result.resumedAt,
263
+ lastErrorCode: null,
264
+ lastErrorDetail: null,
265
+ resumedAt: result.resumedAt
266
+ });
267
+ return {
268
+ sessionId,
269
+ provider: result.provider,
270
+ providerSessionId: result.providerSessionId,
271
+ resumedAt: result.resumedAt
272
+ };
273
+ }
274
+ catch (error) {
275
+ this.markSessionError(sessionId, "RESUME_FAILED", error);
276
+ throw mapSessionProviderError(error);
277
+ }
278
+ }
279
+ async startSession(input) {
280
+ const workspace = this.getWorkspaceOrThrow(input.workspaceId);
281
+ if (input.provider === "codex" || input.provider === "claude-code" || input.provider === "opencode") {
282
+ throw new AppError({
283
+ statusCode: 409,
284
+ errorCode: "SESSION_START_DEFERRED",
285
+ detail: "当前 provider 仅支持在首条消息发送时通过 start-live 创建原生会话",
286
+ field: "provider"
287
+ });
288
+ }
289
+ try {
290
+ const result = await this.sessionSyncService.startSession(input.provider, workspace.path, {
291
+ initialPrompt: input.initialPrompt
292
+ });
293
+ const sessionId = createId();
294
+ const timestamp = nowIso();
295
+ const persist = this.db.transaction(() => {
296
+ this.sessionBindingRepository.upsert({
297
+ sessionId,
298
+ workspaceId: workspace.id,
299
+ provider: result.session.provider,
300
+ providerSessionId: result.session.providerSessionId,
301
+ rawStoreRef: result.session.rawStoreRef,
302
+ createdAt: timestamp,
303
+ updatedAt: timestamp
304
+ });
305
+ this.sessionIndexRepository.upsert({
306
+ sessionId,
307
+ workspaceId: workspace.id,
308
+ provider: result.session.provider,
309
+ parentSessionId: result.session.parentProviderSessionId ?? null,
310
+ isSubagent: result.session.isSubagent ?? false,
311
+ subagentLabel: result.session.subagentLabel ?? null,
312
+ title: result.session.title,
313
+ messageCount: result.session.messageCount,
314
+ isArchived: false,
315
+ lastMessageAt: result.session.lastMessageAt,
316
+ createdAt: timestamp,
317
+ updatedAt: timestamp
318
+ });
319
+ this.sessionStatusSnapshotRepository.upsert({
320
+ sessionId,
321
+ syncStatus: "idle",
322
+ syncCursor: result.initialCursor,
323
+ lastSyncAt: timestamp,
324
+ lastErrorCode: null,
325
+ lastErrorDetail: null,
326
+ resumedAt: null,
327
+ updatedAt: timestamp
328
+ });
329
+ this.sessionStateRepository.upsert({
330
+ sessionId,
331
+ userId: input.userId,
332
+ runningState: "idle",
333
+ activitySource: "none",
334
+ favorite: false,
335
+ lastEventAt: result.session.lastMessageAt,
336
+ completedAt: null,
337
+ lastSeenAt: null,
338
+ updatedAt: timestamp
339
+ });
340
+ });
341
+ persist();
342
+ return this.getSessionListItemOrThrow(sessionId, input.userId);
343
+ }
344
+ catch (error) {
345
+ throw mapSessionProviderError(error);
346
+ }
347
+ }
348
+ async sendMessage(sessionId, content, clientRequestId) {
349
+ const binding = this.getBindingOrThrow(sessionId);
350
+ const result = await this.sessionSyncService
351
+ .sendMessage(binding.provider, binding.providerSessionId, binding.rawStoreRef, content, clientRequestId)
352
+ .catch((error) => {
353
+ this.markSessionError(sessionId, "SEND_FAILED", error);
354
+ throw mapSessionProviderError(error);
355
+ });
356
+ const existing = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
357
+ this.sessionIndexRepository.upsert({
358
+ sessionId,
359
+ workspaceId: binding.workspaceId,
360
+ provider: binding.provider,
361
+ parentSessionId: existing?.parentSessionId ?? null,
362
+ isSubagent: existing?.isSubagent ?? false,
363
+ subagentLabel: existing?.subagentLabel ?? null,
364
+ title: existing?.title ?? result.message.content.slice(0, 48),
365
+ messageCount: (existing?.messageCount ?? 0) + 1,
366
+ isArchived: existing?.isArchived ?? false,
367
+ lastMessageAt: result.message.timestamp,
368
+ createdAt: existing?.createdAt ?? nowIso(),
369
+ updatedAt: result.message.timestamp
370
+ });
371
+ this.upsertSnapshot(sessionId, {
372
+ syncStatus: "idle",
373
+ syncCursor: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.syncCursor ?? null,
374
+ lastSyncAt: result.acceptedAt,
375
+ lastErrorCode: null,
376
+ lastErrorDetail: null,
377
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
378
+ });
379
+ return {
380
+ sessionId,
381
+ ...result
382
+ };
383
+ }
384
+ async subscribeSession(sessionId, cursor, limit, onEnvelope) {
385
+ const sentMessageIds = new Set();
386
+ const safeLimit = clampLimit(limit);
387
+ let currentCursor = cursor;
388
+ const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
389
+ let closed = false;
390
+ let polling = false;
391
+ this.upsertSnapshot(sessionId, {
392
+ syncStatus: "syncing",
393
+ syncCursor: current?.syncCursor ?? cursor,
394
+ lastSyncAt: current?.lastSyncAt ?? null,
395
+ lastErrorCode: null,
396
+ lastErrorDetail: null,
397
+ resumedAt: current?.resumedAt ?? null
398
+ });
399
+ try {
400
+ await this.pullSessionHistory(sessionId, currentCursor, safeLimit, sentMessageIds, onEnvelope, "session.backfill")
401
+ .then((nextCursor) => {
402
+ currentCursor = nextCursor;
403
+ });
404
+ }
405
+ catch (error) {
406
+ this.markSessionError(sessionId, "SUBSCRIBE_FAILED", error);
407
+ throw mapSessionProviderError(error);
408
+ }
409
+ const timer = setInterval(() => {
410
+ if (closed || polling) {
411
+ return;
412
+ }
413
+ polling = true;
414
+ void this.pullSessionHistory(sessionId, currentCursor, safeLimit, sentMessageIds, onEnvelope, "session.delta", () => closed)
415
+ .then((nextCursor) => {
416
+ currentCursor = nextCursor;
417
+ })
418
+ .catch((error) => {
419
+ this.markSessionError(sessionId, "SUBSCRIBE_FAILED", error);
420
+ })
421
+ .finally(() => {
422
+ polling = false;
423
+ });
424
+ }, 300);
425
+ return {
426
+ close() {
427
+ closed = true;
428
+ clearInterval(timer);
429
+ }
430
+ };
431
+ }
432
+ async readRecentHistoryEnvelope(sessionId, limit = 20) {
433
+ const binding = this.getBindingOrThrow(sessionId);
434
+ if (shouldSkipClaudePendingBinding(binding)) {
435
+ return null;
436
+ }
437
+ const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
438
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, null, clampLimit(limit), "backward", currentIndex?.messageCount ?? null);
439
+ if (!page.messages.some((message) => message.role !== "user")) {
440
+ return null;
441
+ }
442
+ await this.syncSessionTitleFromProvider(sessionId, binding);
443
+ this.upsertSnapshot(sessionId, {
444
+ syncStatus: "idle",
445
+ syncCursor: page.cursor,
446
+ lastSyncAt: nowIso(),
447
+ lastErrorCode: null,
448
+ lastErrorDetail: null,
449
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
450
+ });
451
+ return {
452
+ type: "session.delta",
453
+ sessionId,
454
+ cursor: page.cursor,
455
+ messages: page.messages
456
+ };
457
+ }
458
+ async markSessionSeen(sessionId, userId) {
459
+ const existing = this.sessionStateRepository.findBySessionAndUser(sessionId, userId) ??
460
+ (await this.refreshSessionState(sessionId, userId));
461
+ const seenAt = nowIso();
462
+ this.sessionStateRepository.upsert({
463
+ sessionId,
464
+ userId,
465
+ runningState: existing?.runningState ?? "idle",
466
+ activitySource: existing?.activitySource ?? "none",
467
+ favorite: existing?.favorite ?? false,
468
+ lastEventAt: existing?.lastEventAt ?? null,
469
+ completedAt: existing?.completedAt ?? null,
470
+ lastSeenAt: seenAt,
471
+ updatedAt: seenAt
472
+ });
473
+ }
474
+ async updateSessionFavoriteState(input) {
475
+ this.getBindingOrThrow(input.sessionId);
476
+ const existingItem = this.getSessionListItemOrThrow(input.sessionId, input.userId);
477
+ const currentState = this.sessionStateRepository.findBySessionAndUser(input.sessionId, input.userId) ??
478
+ (await this.refreshSessionState(input.sessionId, input.userId));
479
+ const timestamp = nowIso();
480
+ this.sessionStateRepository.upsert({
481
+ sessionId: input.sessionId,
482
+ userId: input.userId,
483
+ runningState: currentState?.runningState ?? "idle",
484
+ activitySource: currentState?.activitySource ?? "none",
485
+ favorite: input.isFavorite,
486
+ lastEventAt: currentState?.lastEventAt ?? existingItem.lastEventAt ?? null,
487
+ completedAt: currentState?.completedAt ?? existingItem.completedAt ?? null,
488
+ lastSeenAt: currentState?.lastSeenAt ?? existingItem.lastSeenAt ?? null,
489
+ updatedAt: timestamp
490
+ });
491
+ return this.enrichSessionItem({
492
+ ...this.getSessionListItemOrThrow(input.sessionId, input.userId),
493
+ isFavorite: input.isFavorite
494
+ });
495
+ }
496
+ async updateSessionArchiveState(input) {
497
+ const binding = this.getBindingOrThrow(input.sessionId);
498
+ const existing = this.getSessionListItemOrThrow(input.sessionId, input.userId);
499
+ const timestamp = nowIso();
500
+ let nextRawStoreRef = binding.rawStoreRef;
501
+ let nextArchivedState = input.isArchived;
502
+ if (binding.provider === "codex") {
503
+ const result = await this.sessionSyncService
504
+ .updateSessionArchiveState(binding.provider, binding.providerSessionId, binding.rawStoreRef, input.isArchived)
505
+ .catch((error) => {
506
+ throw mapSessionProviderError(error);
507
+ });
508
+ this.sessionBindingRepository.upsert({
509
+ ...binding,
510
+ rawStoreRef: result.rawStoreRef,
511
+ updatedAt: timestamp
512
+ });
513
+ nextRawStoreRef = result.rawStoreRef;
514
+ nextArchivedState = result.isArchived;
515
+ }
516
+ this.sessionIndexRepository.upsert({
517
+ sessionId: input.sessionId,
518
+ workspaceId: existing.workspaceId,
519
+ provider: existing.provider,
520
+ parentSessionId: existing.parentSessionId ?? null,
521
+ isSubagent: existing.isSubagent ?? false,
522
+ subagentLabel: existing.subagentLabel ?? null,
523
+ title: existing.title,
524
+ messageCount: existing.messageCount,
525
+ isArchived: nextArchivedState,
526
+ lastMessageAt: existing.lastMessageAt,
527
+ createdAt: existing.createdAt,
528
+ updatedAt: timestamp
529
+ });
530
+ return this.enrichSessionItem({
531
+ ...this.getSessionListItemOrThrow(input.sessionId, input.userId),
532
+ rawStoreRef: nextRawStoreRef,
533
+ isArchived: nextArchivedState
534
+ });
535
+ }
536
+ async renameSessionTitle(sessionId, userId, title) {
537
+ const binding = this.getBindingOrThrow(sessionId);
538
+ const existing = this.getSessionListItemOrThrow(sessionId, userId);
539
+ const nextTitle = title.trim();
540
+ const timestamp = nowIso();
541
+ try {
542
+ await this.sessionSyncService.renameSessionTitle(binding.provider, binding.providerSessionId, binding.rawStoreRef, nextTitle);
543
+ }
544
+ catch (error) {
545
+ throw mapSessionProviderError(error);
546
+ }
547
+ this.sessionIndexRepository.renameTitle(sessionId, nextTitle, timestamp);
548
+ return this.enrichSessionItem({
549
+ ...existing,
550
+ title: nextTitle,
551
+ updatedAt: timestamp
552
+ });
553
+ }
554
+ getBindingOrThrow(sessionId) {
555
+ const binding = this.sessionBindingRepository.findBySessionId(sessionId);
556
+ if (!binding) {
557
+ throw new AppError({
558
+ statusCode: 404,
559
+ errorCode: "SESSION_NOT_FOUND",
560
+ detail: "session 不存在"
561
+ });
562
+ }
563
+ return binding;
564
+ }
565
+ persistSessionBinding(sessionId, workspaceId, snapshot) {
566
+ if (!snapshot.providerSessionId || !snapshot.rawStoreRef) {
567
+ return;
568
+ }
569
+ const resolvedSnapshot = {
570
+ provider: snapshot.provider,
571
+ providerSessionId: snapshot.providerSessionId,
572
+ rawStoreRef: snapshot.rawStoreRef
573
+ };
574
+ const currentBinding = this.sessionBindingRepository.findBySessionId(sessionId);
575
+ const timestamp = nowIso();
576
+ const duplicateBinding = this.findPendingBindingDuplicate(sessionId, currentBinding, resolvedSnapshot);
577
+ this.db.transaction(() => {
578
+ if (duplicateBinding) {
579
+ // 新建运行时会话会先写入 pending 绑定,后台发现链路可能在真 ID 回填前先落一条重复记录。
580
+ // 这里保留当前 runtime session,把扫描出的重复会话并回当前会话,避免 provider_session_id 撞唯一键。
581
+ this.mergeSessionIntoTarget({
582
+ workspaceId,
583
+ targetSessionId: sessionId,
584
+ sourceSessionId: duplicateBinding.sessionId,
585
+ provider: resolvedSnapshot.provider,
586
+ timestamp
587
+ });
588
+ }
589
+ const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
590
+ this.sessionBindingRepository.upsert({
591
+ sessionId,
592
+ workspaceId,
593
+ provider: resolvedSnapshot.provider,
594
+ providerSessionId: resolvedSnapshot.providerSessionId,
595
+ rawStoreRef: resolvedSnapshot.rawStoreRef,
596
+ createdAt: pickEarlierIso(currentBinding?.createdAt ?? null, duplicateBinding?.createdAt ?? null)
597
+ ?? timestamp,
598
+ updatedAt: timestamp
599
+ });
600
+ if (currentIndex) {
601
+ this.sessionIndexRepository.upsert({
602
+ ...currentIndex,
603
+ updatedAt: timestamp
604
+ });
605
+ }
606
+ })();
607
+ }
608
+ async runDiscoverWorkspaceSessions(workspaceId, userId, refreshStateMode = "inline") {
609
+ const startedAt = Date.now();
610
+ const workspace = this.getWorkspaceOrThrow(workspaceId);
611
+ let discoverDurationMs = 0;
612
+ let persistDurationMs = 0;
613
+ const refreshStateCount = 10;
614
+ try {
615
+ const discoverStartedAt = Date.now();
616
+ const knownSessions = this.buildKnownSessionSummaries(this.sessionIndexRepository.listByWorkspace(workspaceId, userId), workspace.path);
617
+ const discovery = await this.sessionSyncService
618
+ .discoverWorkspaceSessions(workspace.path, {
619
+ knownSessions
620
+ })
621
+ .catch((error) => {
622
+ throw mapSessionProviderError(error);
623
+ });
624
+ const sessions = discovery.sessions;
625
+ discoverDurationMs = Date.now() - discoverStartedAt;
626
+ const timestamp = nowIso();
627
+ const discoveredSessionIds = new Map();
628
+ const persistedSessions = [];
629
+ const persist = this.db.transaction(() => {
630
+ for (const session of sessions) {
631
+ const existing = this.sessionBindingRepository.findByProviderSession(session.provider, session.providerSessionId) ?? this.sessionBindingRepository.findByRawStoreRef(session.provider, session.rawStoreRef);
632
+ const currentSnapshot = existing
633
+ ? this.sessionStatusSnapshotRepository.findBySessionId(existing.sessionId)
634
+ : null;
635
+ const sessionId = existing?.sessionId ?? createId();
636
+ const createdAt = existing?.createdAt ?? timestamp;
637
+ const existingIndex = existing
638
+ ? this.sessionIndexRepository.findIndexRecordBySessionId(existing.sessionId)
639
+ : null;
640
+ this.sessionBindingRepository.upsert({
641
+ sessionId,
642
+ workspaceId: workspace.id,
643
+ provider: session.provider,
644
+ providerSessionId: session.providerSessionId,
645
+ rawStoreRef: session.rawStoreRef,
646
+ createdAt,
647
+ updatedAt: timestamp
648
+ });
649
+ this.sessionIndexRepository.upsert({
650
+ sessionId,
651
+ workspaceId: workspace.id,
652
+ provider: session.provider,
653
+ title: session.title,
654
+ messageCount: session.messageCount,
655
+ isArchived: resolveDiscoveredArchiveState(existingIndex?.isArchived ?? false, session.isArchived),
656
+ lastMessageAt: session.lastMessageAt,
657
+ createdAt,
658
+ updatedAt: timestamp
659
+ });
660
+ this.sessionStatusSnapshotRepository.upsert({
661
+ sessionId,
662
+ syncStatus: currentSnapshot?.syncStatus ?? "idle",
663
+ syncCursor: currentSnapshot?.syncCursor ?? null,
664
+ lastSyncAt: currentSnapshot?.lastSyncAt ?? null,
665
+ lastErrorCode: currentSnapshot?.lastErrorCode ?? null,
666
+ lastErrorDetail: currentSnapshot?.lastErrorDetail ?? null,
667
+ resumedAt: currentSnapshot?.resumedAt ?? null,
668
+ updatedAt: timestamp
669
+ });
670
+ discoveredSessionIds.set(buildProviderSessionKey(session.provider, session.providerSessionId), sessionId);
671
+ persistedSessions.push({
672
+ session,
673
+ sessionId,
674
+ createdAt,
675
+ existingIndex
676
+ });
677
+ }
678
+ const relationMap = this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds);
679
+ for (const persistedSession of persistedSessions) {
680
+ const relation = relationMap.get(persistedSession.sessionId);
681
+ this.sessionIndexRepository.upsert({
682
+ sessionId: persistedSession.sessionId,
683
+ workspaceId: workspace.id,
684
+ provider: persistedSession.session.provider,
685
+ parentSessionId: relation?.parentSessionId ?? null,
686
+ isSubagent: relation?.isSubagent ?? false,
687
+ subagentLabel: relation?.subagentLabel ?? null,
688
+ title: persistedSession.session.title,
689
+ messageCount: persistedSession.session.messageCount,
690
+ isArchived: resolveDiscoveredArchiveState(persistedSession.existingIndex?.isArchived ?? false, persistedSession.session.isArchived),
691
+ lastMessageAt: persistedSession.session.lastMessageAt,
692
+ createdAt: persistedSession.createdAt,
693
+ updatedAt: timestamp
694
+ });
695
+ }
696
+ });
697
+ const persistStartedAt = Date.now();
698
+ persist();
699
+ persistDurationMs = Date.now() - persistStartedAt;
700
+ if (discovery.isComplete) {
701
+ this.cleanupStaleHiddenSessions(workspaceId, userId, sessions);
702
+ }
703
+ this.workspaceSessionRelations.set(workspaceId, this.buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds));
704
+ const items = this.sessionIndexRepository.listByWorkspace(workspaceId, userId);
705
+ const recentItems = items.slice(0, refreshStateCount);
706
+ this.workspaceDiscoveryStatuses.set(workspaceId, {
707
+ refreshedAt: Date.now(),
708
+ isComplete: discovery.isComplete
709
+ });
710
+ if (refreshStateMode === "inline") {
711
+ await this.refreshRecentSessionStates(recentItems, userId);
712
+ }
713
+ else {
714
+ this.scheduleWorkspaceStateRefresh(workspaceId, userId, recentItems);
715
+ }
716
+ const nextItems = this.listWorkspaceSessions(workspaceId, userId);
717
+ logPerformance("workspace.discover_sessions", Date.now() - startedAt, {
718
+ workspaceId,
719
+ workspacePath: workspace.path,
720
+ knownSessions: knownSessions.length,
721
+ discoveredSessions: sessions.length,
722
+ returnedSessions: nextItems.length,
723
+ discoveryComplete: discovery.isComplete,
724
+ refreshedStates: Math.min(items.length, refreshStateCount),
725
+ discoverMs: discoverDurationMs,
726
+ persistMs: persistDurationMs,
727
+ refreshStateDeferred: refreshStateMode !== "inline"
728
+ }, {
729
+ thresholdMs: 500
730
+ });
731
+ return nextItems;
732
+ }
733
+ catch (error) {
734
+ logPerformance("workspace.discover_sessions.failed", Date.now() - startedAt, {
735
+ workspaceId,
736
+ workspacePath: workspace.path,
737
+ discoverMs: discoverDurationMs,
738
+ persistMs: persistDurationMs,
739
+ refreshStateDeferred: refreshStateMode !== "inline",
740
+ error: error instanceof Error ? error.message : "unknown"
741
+ }, {
742
+ thresholdMs: 0,
743
+ force: true
744
+ });
745
+ throw error;
746
+ }
747
+ }
748
+ async readPage(sessionId, provider, providerSessionId, rawStoreRef, cursor, limit, direction = "forward", knownTotalMessageCount = null) {
749
+ if (shouldShortCircuitMissingSyntheticCodexHistory(provider, rawStoreRef)) {
750
+ return {
751
+ messages: [],
752
+ cursor,
753
+ nextCursor: null,
754
+ total: 0
755
+ };
756
+ }
757
+ const historyTask = direction === "backward" && cursor === null && typeof knownTotalMessageCount === "number"
758
+ ? this.sessionSyncService.readRecentHistory(provider, providerSessionId, rawStoreRef, knownTotalMessageCount, limit)
759
+ : this.sessionSyncService.readHistory(provider, providerSessionId, rawStoreRef, cursor, limit, direction);
760
+ return historyTask
761
+ .then((page) => {
762
+ const messages = this.sessionMessageAttachmentService.enrichMessages(sessionId, page.messages);
763
+ this.persistSessionChangedFiles(sessionId, messages);
764
+ return {
765
+ ...page,
766
+ messages
767
+ };
768
+ })
769
+ .catch((error) => {
770
+ if (shouldTreatMissingSyntheticHistoryAsEmpty(provider, rawStoreRef, error)) {
771
+ return {
772
+ messages: [],
773
+ cursor,
774
+ nextCursor: null,
775
+ total: 0
776
+ };
777
+ }
778
+ throw mapSessionProviderError(error);
779
+ });
780
+ }
781
+ buildWorkspaceSessionRelationMap(sessions, discoveredSessionIds) {
782
+ const relationMap = new Map();
783
+ for (const session of sessions) {
784
+ const sessionId = discoveredSessionIds.get(buildProviderSessionKey(session.provider, session.providerSessionId));
785
+ if (!sessionId) {
786
+ continue;
787
+ }
788
+ const parentSessionId = session.parentProviderSessionId
789
+ ? discoveredSessionIds.get(buildProviderSessionKey(session.provider, session.parentProviderSessionId)) ??
790
+ this.sessionBindingRepository.findByProviderSession(session.provider, session.parentProviderSessionId)?.sessionId ??
791
+ null
792
+ : null;
793
+ relationMap.set(sessionId, {
794
+ parentSessionId,
795
+ isSubagent: Boolean(session.isSubagent || parentSessionId),
796
+ subagentLabel: session.subagentLabel?.trim() || null
797
+ });
798
+ }
799
+ return relationMap;
800
+ }
801
+ enrichSessionItems(workspaceId, items) {
802
+ const relationMap = this.workspaceSessionRelations.get(workspaceId);
803
+ if (!relationMap) {
804
+ return items.map((item) => this.enrichSessionItem(item));
805
+ }
806
+ return items.map((item) => {
807
+ const relation = relationMap.get(item.sessionId);
808
+ if (!relation) {
809
+ return this.enrichSessionItem(item);
810
+ }
811
+ return {
812
+ ...item,
813
+ parentSessionId: relation.parentSessionId,
814
+ isSubagent: relation.isSubagent,
815
+ subagentLabel: relation.subagentLabel
816
+ };
817
+ });
818
+ }
819
+ enrichSessionItem(item) {
820
+ const relation = this.workspaceSessionRelations.get(item.workspaceId)?.get(item.sessionId);
821
+ if (!relation) {
822
+ return {
823
+ ...item,
824
+ parentSessionId: item.parentSessionId ?? null,
825
+ isSubagent: item.isSubagent ?? false,
826
+ subagentLabel: item.subagentLabel ?? null
827
+ };
828
+ }
829
+ return {
830
+ ...item,
831
+ parentSessionId: relation.parentSessionId,
832
+ isSubagent: relation.isSubagent,
833
+ subagentLabel: relation.subagentLabel
834
+ };
835
+ }
836
+ async pullSessionHistory(sessionId, cursor, limit, sentMessageIds, onEnvelope, envelopeType, isClosed = () => false) {
837
+ let currentCursor = cursor;
838
+ while (!isClosed()) {
839
+ const binding = this.getBindingOrThrow(sessionId);
840
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, currentCursor, limit);
841
+ const messages = page.messages.filter((message) => {
842
+ if (sentMessageIds.has(message.messageId)) {
843
+ return false;
844
+ }
845
+ sentMessageIds.add(message.messageId);
846
+ return true;
847
+ });
848
+ if (messages.length > 0) {
849
+ await this.syncSessionTitleFromProvider(sessionId, binding);
850
+ this.upsertSnapshot(sessionId, {
851
+ syncStatus: "idle",
852
+ syncCursor: page.cursor,
853
+ lastSyncAt: nowIso(),
854
+ lastErrorCode: null,
855
+ lastErrorDetail: null,
856
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
857
+ });
858
+ await onEnvelope({
859
+ type: envelopeType,
860
+ sessionId,
861
+ cursor: page.cursor,
862
+ messages
863
+ });
864
+ }
865
+ currentCursor = page.cursor;
866
+ if (!page.nextCursor) {
867
+ return currentCursor;
868
+ }
869
+ }
870
+ return currentCursor;
871
+ }
872
+ async syncSessionTitleFromProvider(sessionId, binding) {
873
+ const currentIndex = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
874
+ if (!currentIndex) {
875
+ return;
876
+ }
877
+ if (shouldSkipClaudePendingBinding(binding)) {
878
+ return;
879
+ }
880
+ const nextTitle = (await this.sessionSyncService.readSessionTitle(binding.provider, binding.providerSessionId, binding.rawStoreRef)).trim();
881
+ if (nextTitle.length === 0 || nextTitle === currentIndex.title) {
882
+ return;
883
+ }
884
+ this.sessionIndexRepository.upsert({
885
+ ...currentIndex,
886
+ title: nextTitle,
887
+ updatedAt: nowIso()
888
+ });
889
+ }
890
+ async ensureSessionChangedFilesIndexed(sessionId) {
891
+ if (this.sessionChangedFileService.hasIndexedSession(sessionId)) {
892
+ return;
893
+ }
894
+ const binding = this.getBindingOrThrow(sessionId);
895
+ const seenCursors = new Set();
896
+ let cursor = null;
897
+ while (true) {
898
+ const page = await this.readPage(sessionId, binding.provider, binding.providerSessionId, binding.rawStoreRef, cursor, 100, "forward");
899
+ if (!page.nextCursor || seenCursors.has(page.nextCursor)) {
900
+ this.sessionChangedFileService.markSessionIndexed(sessionId, nowIso());
901
+ return;
902
+ }
903
+ seenCursors.add(page.nextCursor);
904
+ cursor = page.nextCursor;
905
+ }
906
+ }
907
+ persistSessionChangedFiles(sessionId, messages) {
908
+ if (messages.length === 0) {
909
+ return;
910
+ }
911
+ const binding = this.sessionBindingRepository.findBySessionId(sessionId);
912
+ if (!binding) {
913
+ return;
914
+ }
915
+ const workspace = this.workspaceRepository.findById(binding.workspaceId);
916
+ if (!workspace) {
917
+ return;
918
+ }
919
+ this.sessionChangedFileService.recordMessages(sessionId, binding.workspaceId, workspace.path, messages);
920
+ }
921
+ getWorkspaceOrThrow(workspaceId) {
922
+ const workspace = this.workspaceRepository.findById(workspaceId);
923
+ if (!workspace) {
924
+ throw new AppError({
925
+ statusCode: 404,
926
+ errorCode: "WORKSPACE_NOT_FOUND",
927
+ detail: "工作区不存在"
928
+ });
929
+ }
930
+ return workspace;
931
+ }
932
+ getSessionListItemOrThrow(sessionId, userId) {
933
+ const item = this.sessionIndexRepository.findBySessionId(sessionId, userId);
934
+ if (!item) {
935
+ throw new AppError({
936
+ statusCode: 500,
937
+ errorCode: "SESSION_INDEX_MISSING",
938
+ detail: "session 索引缺失"
939
+ });
940
+ }
941
+ return item;
942
+ }
943
+ async refreshRecentSessionStates(sessions, userId) {
944
+ for (let index = 0; index < sessions.length; index += 1) {
945
+ if (index > 0) {
946
+ await delay(0);
947
+ }
948
+ await this.refreshSessionState(sessions[index].sessionId, userId);
949
+ }
950
+ }
951
+ scheduleWorkspaceStateRefresh(workspaceId, userId, sessions) {
952
+ if (sessions.length === 0) {
953
+ return;
954
+ }
955
+ const inflightKey = `${workspaceId}:${userId}`;
956
+ if (this.workspaceStateRefreshInflight.has(inflightKey)) {
957
+ return;
958
+ }
959
+ const startedAt = Date.now();
960
+ const task = delay(0)
961
+ .then(() => this.refreshRecentSessionStates(sessions, userId))
962
+ .then(() => {
963
+ logPerformance("workspace.refresh_recent_session_states", Date.now() - startedAt, {
964
+ workspaceId,
965
+ refreshedStates: sessions.length
966
+ }, {
967
+ thresholdMs: 300
968
+ });
969
+ })
970
+ .catch((error) => {
971
+ logPerformance("workspace.refresh_recent_session_states.failed", Date.now() - startedAt, {
972
+ workspaceId,
973
+ refreshedStates: sessions.length,
974
+ error: error instanceof Error ? error.message : "unknown"
975
+ }, {
976
+ thresholdMs: 0,
977
+ force: true
978
+ });
979
+ })
980
+ .finally(() => {
981
+ this.workspaceStateRefreshInflight.delete(inflightKey);
982
+ });
983
+ this.workspaceStateRefreshInflight.set(inflightKey, task);
984
+ }
985
+ cleanupStaleHiddenSessions(workspaceId, userId, sessions) {
986
+ const discoveredProviderSessionIds = new Set(sessions.map((session) => buildProviderSessionKey(session.provider, session.providerSessionId)));
987
+ const discoveredRawStoreRefs = new Set(sessions.map((session) => session.rawStoreRef));
988
+ const staleHiddenSessions = this.sessionIndexRepository
989
+ .listByWorkspace(workspaceId, userId)
990
+ .filter((session) => {
991
+ if (discoveredProviderSessionIds.has(buildProviderSessionKey(session.provider, session.providerSessionId))) {
992
+ return false;
993
+ }
994
+ if (discoveredRawStoreRefs.has(session.rawStoreRef)) {
995
+ return false;
996
+ }
997
+ return ((session.provider === "codex" &&
998
+ (isLegacyCodingNsRolloutSession(session.providerSessionId, session.rawStoreRef) ||
999
+ shouldRemoveMissingSyntheticCodexSession(session.rawStoreRef))) ||
1000
+ (session.provider === "claude-code" && shouldRemoveHiddenClaudeDebugSession(session)));
1001
+ });
1002
+ if (staleHiddenSessions.length === 0) {
1003
+ return;
1004
+ }
1005
+ this.deleteSessionsByIds(staleHiddenSessions.map((session) => session.sessionId));
1006
+ }
1007
+ deleteSessionsByIds(sessionIds) {
1008
+ const remove = this.db.transaction((ids) => {
1009
+ for (const sessionId of ids) {
1010
+ this.deleteSessionById(sessionId);
1011
+ }
1012
+ });
1013
+ remove(sessionIds);
1014
+ }
1015
+ findPendingBindingDuplicate(sessionId, currentBinding, snapshot) {
1016
+ if (!currentBinding || !isPendingBindingValue(currentBinding.providerSessionId)) {
1017
+ return null;
1018
+ }
1019
+ if (isPendingBindingValue(snapshot.providerSessionId)) {
1020
+ return null;
1021
+ }
1022
+ const existing = this.sessionBindingRepository.findByProviderSession(snapshot.provider, snapshot.providerSessionId) ?? this.sessionBindingRepository.findByRawStoreRef(snapshot.provider, snapshot.rawStoreRef);
1023
+ if (!existing || existing.sessionId === sessionId) {
1024
+ return null;
1025
+ }
1026
+ return existing;
1027
+ }
1028
+ mergeSessionIntoTarget(input) {
1029
+ if (input.targetSessionId === input.sourceSessionId) {
1030
+ return;
1031
+ }
1032
+ const targetBinding = this.sessionBindingRepository.findBySessionId(input.targetSessionId);
1033
+ const sourceBinding = this.sessionBindingRepository.findBySessionId(input.sourceSessionId);
1034
+ if (!targetBinding || !sourceBinding) {
1035
+ return;
1036
+ }
1037
+ const targetIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.targetSessionId);
1038
+ const sourceIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sourceSessionId);
1039
+ const targetSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(input.targetSessionId);
1040
+ const sourceSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(input.sourceSessionId);
1041
+ const targetStates = this.listSessionStatesBySessionId(input.targetSessionId);
1042
+ const sourceStates = this.listSessionStatesBySessionId(input.sourceSessionId);
1043
+ const targetStateByUserId = new Map(targetStates.map((state) => [state.userId, state]));
1044
+ this.copyChangedFilesToTarget(input.targetSessionId, input.sourceSessionId);
1045
+ this.copyChangedFileIndexStateToTarget(input.targetSessionId, input.sourceSessionId);
1046
+ for (const sourceState of sourceStates) {
1047
+ this.sessionStateRepository.upsert(mergeSessionStateRecord(targetStateByUserId.get(sourceState.userId) ?? null, {
1048
+ ...sourceState,
1049
+ sessionId: input.targetSessionId
1050
+ }));
1051
+ }
1052
+ const mergedIndex = mergeSessionIndexRecord({
1053
+ targetSessionId: input.targetSessionId,
1054
+ workspaceId: input.workspaceId,
1055
+ provider: input.provider,
1056
+ target: targetIndex,
1057
+ source: sourceIndex,
1058
+ timestamp: input.timestamp
1059
+ });
1060
+ if (mergedIndex) {
1061
+ this.sessionIndexRepository.upsert(mergedIndex);
1062
+ }
1063
+ const mergedSnapshot = mergeSessionStatusSnapshot({
1064
+ targetSessionId: input.targetSessionId,
1065
+ target: targetSnapshot,
1066
+ source: sourceSnapshot,
1067
+ timestamp: input.timestamp
1068
+ });
1069
+ if (mergedSnapshot) {
1070
+ this.sessionStatusSnapshotRepository.upsert(mergedSnapshot);
1071
+ }
1072
+ this.db
1073
+ .prepare(`UPDATE session_indices
1074
+ SET parent_session_id = ?
1075
+ WHERE parent_session_id = ?`)
1076
+ .run(input.targetSessionId, input.sourceSessionId);
1077
+ this.db
1078
+ .prepare(`UPDATE session_message_attachments
1079
+ SET session_id = ?
1080
+ WHERE session_id = ?`)
1081
+ .run(input.targetSessionId, input.sourceSessionId);
1082
+ this.db
1083
+ .prepare(`UPDATE session_file_context_bindings
1084
+ SET session_id = ?
1085
+ WHERE session_id = ?`)
1086
+ .run(input.targetSessionId, input.sourceSessionId);
1087
+ this.db
1088
+ .prepare(`UPDATE session_send_queue
1089
+ SET session_id = ?
1090
+ WHERE session_id = ?`)
1091
+ .run(input.targetSessionId, input.sourceSessionId);
1092
+ this.db
1093
+ .prepare("DELETE FROM session_changed_files WHERE session_id = ?")
1094
+ .run(input.sourceSessionId);
1095
+ this.db
1096
+ .prepare("DELETE FROM session_changed_file_states WHERE session_id = ?")
1097
+ .run(input.sourceSessionId);
1098
+ this.db
1099
+ .prepare("DELETE FROM session_states WHERE session_id = ?")
1100
+ .run(input.sourceSessionId);
1101
+ this.db
1102
+ .prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
1103
+ .run(input.sourceSessionId);
1104
+ this.db
1105
+ .prepare("DELETE FROM session_indices WHERE session_id = ?")
1106
+ .run(input.sourceSessionId);
1107
+ this.db
1108
+ .prepare("DELETE FROM session_bindings WHERE session_id = ?")
1109
+ .run(input.sourceSessionId);
1110
+ this.rewriteWorkspaceSessionRelations(input.workspaceId, input.targetSessionId, input.sourceSessionId, targetIndex, sourceIndex);
1111
+ }
1112
+ listSessionStatesBySessionId(sessionId) {
1113
+ return this.db
1114
+ .prepare(`SELECT
1115
+ session_id AS session_id,
1116
+ user_id AS user_id,
1117
+ running_state AS running_state,
1118
+ activity_source AS activity_source,
1119
+ favorite AS favorite,
1120
+ last_event_at AS last_event_at,
1121
+ completed_at AS completed_at,
1122
+ last_seen_at AS last_seen_at,
1123
+ updated_at AS updated_at
1124
+ FROM session_states
1125
+ WHERE session_id = ?`)
1126
+ .all(sessionId)
1127
+ .map((row) => mapSessionStateRecordRow(row));
1128
+ }
1129
+ copyChangedFilesToTarget(targetSessionId, sourceSessionId) {
1130
+ this.db
1131
+ .prepare(`INSERT INTO session_changed_files (
1132
+ session_id,
1133
+ workspace_id,
1134
+ path,
1135
+ first_detected_at,
1136
+ last_detected_at,
1137
+ last_tool_name
1138
+ )
1139
+ SELECT ?, workspace_id, path, first_detected_at, last_detected_at, last_tool_name
1140
+ FROM session_changed_files
1141
+ WHERE session_id = ?
1142
+ ON CONFLICT(session_id, path) DO UPDATE SET
1143
+ workspace_id = excluded.workspace_id,
1144
+ first_detected_at = MIN(session_changed_files.first_detected_at, excluded.first_detected_at),
1145
+ last_detected_at = MAX(session_changed_files.last_detected_at, excluded.last_detected_at),
1146
+ last_tool_name = COALESCE(excluded.last_tool_name, session_changed_files.last_tool_name)`)
1147
+ .run(targetSessionId, sourceSessionId);
1148
+ }
1149
+ copyChangedFileIndexStateToTarget(targetSessionId, sourceSessionId) {
1150
+ this.db
1151
+ .prepare(`INSERT INTO session_changed_file_states (
1152
+ session_id,
1153
+ indexed_at,
1154
+ updated_at
1155
+ )
1156
+ SELECT ?, indexed_at, updated_at
1157
+ FROM session_changed_file_states
1158
+ WHERE session_id = ?
1159
+ ON CONFLICT(session_id) DO UPDATE SET
1160
+ indexed_at = MIN(session_changed_file_states.indexed_at, excluded.indexed_at),
1161
+ updated_at = MAX(session_changed_file_states.updated_at, excluded.updated_at)`)
1162
+ .run(targetSessionId, sourceSessionId);
1163
+ }
1164
+ rewriteWorkspaceSessionRelations(workspaceId, targetSessionId, sourceSessionId, targetIndex, sourceIndex) {
1165
+ const relationMap = this.workspaceSessionRelations.get(workspaceId);
1166
+ if (!relationMap) {
1167
+ return;
1168
+ }
1169
+ const sourceRelation = relationMap.get(sourceSessionId);
1170
+ const targetRelation = relationMap.get(targetSessionId);
1171
+ const fallbackParentSessionId = targetIndex?.parentSessionId ?? sourceIndex?.parentSessionId ?? null;
1172
+ relationMap.delete(sourceSessionId);
1173
+ relationMap.set(targetSessionId, {
1174
+ parentSessionId: targetRelation?.parentSessionId ?? sourceRelation?.parentSessionId ?? fallbackParentSessionId,
1175
+ isSubagent: Boolean(targetRelation?.isSubagent
1176
+ || sourceRelation?.isSubagent
1177
+ || targetIndex?.isSubagent
1178
+ || sourceIndex?.isSubagent
1179
+ || fallbackParentSessionId),
1180
+ subagentLabel: targetRelation?.subagentLabel
1181
+ ?? sourceRelation?.subagentLabel
1182
+ ?? targetIndex?.subagentLabel
1183
+ ?? sourceIndex?.subagentLabel
1184
+ ?? null
1185
+ });
1186
+ for (const relation of relationMap.values()) {
1187
+ if (relation.parentSessionId === sourceSessionId) {
1188
+ relation.parentSessionId = targetSessionId;
1189
+ }
1190
+ }
1191
+ }
1192
+ deleteSessionById(sessionId) {
1193
+ this.sessionChangedFileService.deleteBySessionId(sessionId);
1194
+ this.db
1195
+ .prepare("DELETE FROM session_message_attachments WHERE session_id = ?")
1196
+ .run(sessionId);
1197
+ this.db
1198
+ .prepare("DELETE FROM session_send_queue WHERE session_id = ?")
1199
+ .run(sessionId);
1200
+ this.db
1201
+ .prepare("DELETE FROM session_file_context_bindings WHERE session_id = ?")
1202
+ .run(sessionId);
1203
+ this.db
1204
+ .prepare("DELETE FROM session_states WHERE session_id = ?")
1205
+ .run(sessionId);
1206
+ this.db
1207
+ .prepare("DELETE FROM session_status_snapshots WHERE session_id = ?")
1208
+ .run(sessionId);
1209
+ this.db
1210
+ .prepare("DELETE FROM session_indices WHERE session_id = ?")
1211
+ .run(sessionId);
1212
+ this.db
1213
+ .prepare("DELETE FROM session_bindings WHERE session_id = ?")
1214
+ .run(sessionId);
1215
+ }
1216
+ buildKnownSessionSummaries(sessions, workspacePath) {
1217
+ return sessions.map((session) => {
1218
+ const stats = safeStat(session.rawStoreRef);
1219
+ return {
1220
+ provider: session.provider,
1221
+ providerSessionId: session.providerSessionId,
1222
+ title: session.title,
1223
+ workspacePath,
1224
+ rawStoreRef: session.rawStoreRef,
1225
+ lastMessageAt: session.lastMessageAt,
1226
+ messageCount: session.messageCount,
1227
+ sourceMtimeMs: stats?.mtimeMs,
1228
+ sourceSizeBytes: stats?.size
1229
+ };
1230
+ });
1231
+ }
1232
+ async refreshSessionState(sessionId, userId) {
1233
+ const binding = this.getBindingOrThrow(sessionId);
1234
+ const current = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
1235
+ const inspection = inspectSessionActivity(binding.provider, binding.rawStoreRef);
1236
+ const timestamp = nowIso();
1237
+ if (shouldPreserveRuntimeTerminalState(current, inspection)) {
1238
+ return current;
1239
+ }
1240
+ const nextRecord = {
1241
+ sessionId,
1242
+ userId,
1243
+ runningState: inspection.runningState,
1244
+ activitySource: inspection.lastEventAt || inspection.completedAtCandidate ? "inferred" : "none",
1245
+ favorite: current?.favorite ?? false,
1246
+ lastEventAt: inspection.lastEventAt,
1247
+ completedAt: inspection.completedAtCandidate,
1248
+ lastSeenAt: current?.lastSeenAt ?? null,
1249
+ updatedAt: timestamp
1250
+ };
1251
+ this.sessionStateRepository.upsert(nextRecord);
1252
+ const currentSnapshot = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1253
+ this.sessionStatusSnapshotRepository.upsert({
1254
+ sessionId,
1255
+ syncStatus: inspection.runningState === "failed" ? "error" : currentSnapshot?.syncStatus ?? "idle",
1256
+ syncCursor: currentSnapshot?.syncCursor ?? null,
1257
+ lastSyncAt: inspection.lastEventAt ?? inspection.completedAtCandidate ?? currentSnapshot?.lastSyncAt ?? null,
1258
+ lastErrorCode: inspection.runningState === "failed"
1259
+ ? inspection.errorCode
1260
+ : currentSnapshot?.lastErrorCode ?? null,
1261
+ lastErrorDetail: inspection.runningState === "failed"
1262
+ ? inspection.errorDetail
1263
+ : currentSnapshot?.lastErrorDetail ?? null,
1264
+ resumedAt: currentSnapshot?.resumedAt ?? null,
1265
+ updatedAt: timestamp
1266
+ });
1267
+ return nextRecord;
1268
+ }
1269
+ upsertSnapshot(sessionId, input) {
1270
+ this.sessionStatusSnapshotRepository.upsert({
1271
+ sessionId,
1272
+ ...input,
1273
+ updatedAt: nowIso()
1274
+ });
1275
+ }
1276
+ markSessionError(sessionId, errorCode, error) {
1277
+ const current = this.sessionStatusSnapshotRepository.findBySessionId(sessionId);
1278
+ this.sessionStatusSnapshotRepository.upsert({
1279
+ sessionId,
1280
+ syncStatus: "error",
1281
+ syncCursor: current?.syncCursor ?? null,
1282
+ lastSyncAt: current?.lastSyncAt ?? null,
1283
+ lastErrorCode: errorCode,
1284
+ lastErrorDetail: error instanceof Error ? error.message : "unknown",
1285
+ resumedAt: current?.resumedAt ?? null,
1286
+ updatedAt: nowIso()
1287
+ });
1288
+ }
1289
+ }
1290
+ function clampLimit(limit) {
1291
+ if (!Number.isFinite(limit)) {
1292
+ return 50;
1293
+ }
1294
+ return Math.max(1, Math.min(Math.trunc(limit), 100));
1295
+ }
1296
+ function mapSessionStateRecordRow(row) {
1297
+ return {
1298
+ sessionId: row.session_id,
1299
+ userId: row.user_id,
1300
+ runningState: row.running_state,
1301
+ activitySource: row.activity_source,
1302
+ favorite: row.favorite === 1,
1303
+ lastEventAt: row.last_event_at,
1304
+ completedAt: row.completed_at,
1305
+ lastSeenAt: row.last_seen_at,
1306
+ updatedAt: row.updated_at
1307
+ };
1308
+ }
1309
+ function mergeSessionStateRecord(target, source) {
1310
+ if (!target) {
1311
+ return source;
1312
+ }
1313
+ const preferred = pickPreferredSessionState(target, source);
1314
+ return {
1315
+ sessionId: source.sessionId,
1316
+ userId: source.userId,
1317
+ runningState: preferred.runningState,
1318
+ activitySource: preferred.activitySource,
1319
+ favorite: target.favorite || source.favorite,
1320
+ lastEventAt: pickLaterIso(target.lastEventAt, source.lastEventAt),
1321
+ completedAt: pickLaterIso(target.completedAt, source.completedAt),
1322
+ lastSeenAt: pickLaterIso(target.lastSeenAt, source.lastSeenAt),
1323
+ updatedAt: pickLaterIso(target.updatedAt, source.updatedAt) ?? preferred.updatedAt
1324
+ };
1325
+ }
1326
+ function pickPreferredSessionState(left, right) {
1327
+ const leftPriority = scoreSessionState(left);
1328
+ const rightPriority = scoreSessionState(right);
1329
+ if (leftPriority !== rightPriority) {
1330
+ return leftPriority > rightPriority ? left : right;
1331
+ }
1332
+ return (pickLaterIso(left.updatedAt, right.updatedAt) ?? left.updatedAt) === right.updatedAt
1333
+ ? right
1334
+ : left;
1335
+ }
1336
+ function scoreSessionState(record) {
1337
+ if (record.activitySource === "runtime"
1338
+ && (record.runningState === "starting" || record.runningState === "running")) {
1339
+ return 6;
1340
+ }
1341
+ if (record.activitySource === "runtime") {
1342
+ return 5;
1343
+ }
1344
+ if (record.activitySource === "inferred" && record.runningState === "running") {
1345
+ return 4;
1346
+ }
1347
+ if (record.activitySource === "inferred") {
1348
+ return 3;
1349
+ }
1350
+ return record.favorite ? 2 : 1;
1351
+ }
1352
+ function mergeSessionIndexRecord(input) {
1353
+ if (!input.target && !input.source) {
1354
+ return null;
1355
+ }
1356
+ return {
1357
+ sessionId: input.targetSessionId,
1358
+ workspaceId: input.workspaceId,
1359
+ provider: (input.target?.provider ?? input.source?.provider ?? input.provider),
1360
+ parentSessionId: input.target?.parentSessionId ?? input.source?.parentSessionId ?? null,
1361
+ isSubagent: Boolean(input.target?.isSubagent || input.source?.isSubagent),
1362
+ subagentLabel: input.target?.subagentLabel ?? input.source?.subagentLabel ?? null,
1363
+ title: pickPreferredSessionTitle(input.target?.title ?? null, input.source?.title ?? null),
1364
+ messageCount: Math.max(input.target?.messageCount ?? 0, input.source?.messageCount ?? 0),
1365
+ isArchived: Boolean(input.target?.isArchived || input.source?.isArchived),
1366
+ lastMessageAt: pickLaterIso(input.target?.lastMessageAt ?? null, input.source?.lastMessageAt ?? null),
1367
+ createdAt: pickEarlierIso(input.target?.createdAt ?? null, input.source?.createdAt ?? null) ?? input.timestamp,
1368
+ updatedAt: input.timestamp
1369
+ };
1370
+ }
1371
+ function mergeSessionStatusSnapshot(input) {
1372
+ if (!input.target && !input.source) {
1373
+ return null;
1374
+ }
1375
+ const preferred = pickPreferredSnapshot(input.target, input.source);
1376
+ return {
1377
+ sessionId: input.targetSessionId,
1378
+ syncStatus: preferred?.syncStatus ?? "idle",
1379
+ syncCursor: input.target?.syncCursor ?? input.source?.syncCursor ?? null,
1380
+ lastSyncAt: pickLaterIso(input.target?.lastSyncAt ?? null, input.source?.lastSyncAt ?? null),
1381
+ lastErrorCode: preferred?.lastErrorCode ?? null,
1382
+ lastErrorDetail: preferred?.lastErrorDetail ?? null,
1383
+ resumedAt: pickLaterIso(input.target?.resumedAt ?? null, input.source?.resumedAt ?? null),
1384
+ updatedAt: input.timestamp
1385
+ };
1386
+ }
1387
+ function pickPreferredSnapshot(left, right) {
1388
+ if (!left) {
1389
+ return right;
1390
+ }
1391
+ if (!right) {
1392
+ return left;
1393
+ }
1394
+ if (left.syncStatus === "error" && right.syncStatus !== "error") {
1395
+ return left;
1396
+ }
1397
+ if (right.syncStatus === "error" && left.syncStatus !== "error") {
1398
+ return right;
1399
+ }
1400
+ return (pickLaterIso(left.updatedAt, right.updatedAt) ?? left.updatedAt) === right.updatedAt
1401
+ ? right
1402
+ : left;
1403
+ }
1404
+ function pickPreferredSessionTitle(target, source) {
1405
+ const normalizedTarget = target?.trim() ?? "";
1406
+ const normalizedSource = source?.trim() ?? "";
1407
+ if (!normalizedTarget) {
1408
+ return normalizedSource;
1409
+ }
1410
+ if (!normalizedSource) {
1411
+ return normalizedTarget;
1412
+ }
1413
+ if (looksLikeGeneratedSessionTitle(normalizedTarget) && !looksLikeGeneratedSessionTitle(normalizedSource)) {
1414
+ return normalizedSource;
1415
+ }
1416
+ return normalizedTarget;
1417
+ }
1418
+ function looksLikeGeneratedSessionTitle(title) {
1419
+ return /^(Claude|Codex|OpenCode)\s+会话\b/i.test(title);
1420
+ }
1421
+ function pickEarlierIso(left, right) {
1422
+ if (!left) {
1423
+ return right;
1424
+ }
1425
+ if (!right) {
1426
+ return left;
1427
+ }
1428
+ return left.localeCompare(right) <= 0 ? left : right;
1429
+ }
1430
+ function pickLaterIso(left, right) {
1431
+ if (!left) {
1432
+ return right;
1433
+ }
1434
+ if (!right) {
1435
+ return left;
1436
+ }
1437
+ return left.localeCompare(right) >= 0 ? left : right;
1438
+ }
1439
+ function buildProviderSessionKey(provider, providerSessionId) {
1440
+ return `${provider}::${providerSessionId}`;
1441
+ }
1442
+ function shouldSkipClaudePendingBinding(binding) {
1443
+ if (binding.provider !== "claude-code") {
1444
+ return false;
1445
+ }
1446
+ if (isPendingBindingValue(binding.providerSessionId)) {
1447
+ return true;
1448
+ }
1449
+ const normalizedRawStoreRef = binding.rawStoreRef.replaceAll("\\", "/").toLowerCase();
1450
+ return normalizedRawStoreRef.includes("/.pending-");
1451
+ }
1452
+ function isPendingBindingValue(value) {
1453
+ return value.trim().toLowerCase().startsWith("pending://");
1454
+ }
1455
+ function resolveDiscoveredArchiveState(existingArchived, discoveredArchived) {
1456
+ if (existingArchived) {
1457
+ return true;
1458
+ }
1459
+ return discoveredArchived === true;
1460
+ }
1461
+ function isMessageAtOrAfter(timestamp, minTimestamp) {
1462
+ if (!minTimestamp) {
1463
+ return true;
1464
+ }
1465
+ const messageAt = Date.parse(timestamp);
1466
+ const minAt = Date.parse(minTimestamp);
1467
+ if (!Number.isFinite(messageAt) || !Number.isFinite(minAt)) {
1468
+ return true;
1469
+ }
1470
+ return messageAt >= minAt;
1471
+ }
1472
+ function delay(ms) {
1473
+ return new Promise((resolve) => {
1474
+ setTimeout(resolve, ms);
1475
+ });
1476
+ }
1477
+ function safeStat(filePath) {
1478
+ try {
1479
+ const stats = statSync(filePath);
1480
+ return {
1481
+ mtimeMs: stats.mtimeMs,
1482
+ size: stats.size
1483
+ };
1484
+ }
1485
+ catch {
1486
+ return null;
1487
+ }
1488
+ }
1489
+ function shouldTreatMissingSyntheticHistoryAsEmpty(provider, rawStoreRef, error) {
1490
+ if (provider !== "codex") {
1491
+ return false;
1492
+ }
1493
+ if (!isSyntheticCodexRawStoreRef(rawStoreRef)) {
1494
+ return false;
1495
+ }
1496
+ const detail = error instanceof Error ? error.message : String(error);
1497
+ return detail.includes("ENOENT");
1498
+ }
1499
+ function shouldShortCircuitMissingSyntheticCodexHistory(provider, rawStoreRef) {
1500
+ return provider === "codex" && isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
1501
+ }
1502
+ function isSyntheticCodexRawStoreRef(rawStoreRef) {
1503
+ const normalizedRawStoreRef = rawStoreRef.replaceAll("\\", "/").toLowerCase();
1504
+ return (normalizedRawStoreRef.includes("/runtime/codex/") ||
1505
+ normalizedRawStoreRef.startsWith("runtime/codex/"));
1506
+ }
1507
+ function isLegacyCodingNsRolloutSession(providerSessionId, rawStoreRef) {
1508
+ if (!providerSessionId.startsWith("rollout-")) {
1509
+ return false;
1510
+ }
1511
+ if (!existsSync(rawStoreRef)) {
1512
+ return false;
1513
+ }
1514
+ try {
1515
+ const firstLine = readFileSync(rawStoreRef, "utf8")
1516
+ .split(/\r?\n/, 1)
1517
+ .at(0)
1518
+ ?.trim();
1519
+ if (!firstLine) {
1520
+ return false;
1521
+ }
1522
+ const record = JSON.parse(firstLine);
1523
+ return record.type === "session_meta" && record.payload?.source === "codingns";
1524
+ }
1525
+ catch {
1526
+ return false;
1527
+ }
1528
+ }
1529
+ function shouldRemoveMissingSyntheticCodexSession(rawStoreRef) {
1530
+ return isSyntheticCodexRawStoreRef(rawStoreRef) && !existsSync(rawStoreRef);
1531
+ }
1532
+ function shouldRemoveHiddenClaudeDebugSession(session) {
1533
+ const normalizedRawStoreRef = session.rawStoreRef.replaceAll("\\", "/");
1534
+ if (normalizedRawStoreRef.includes("/subagents/")) {
1535
+ return false;
1536
+ }
1537
+ return (/^agent-[^/]+$/i.test(session.providerSessionId) &&
1538
+ /\/agent-[^/]+\.jsonl$/i.test(normalizedRawStoreRef));
1539
+ }
1540
+ function shouldPreserveRuntimeTerminalState(current, inspection) {
1541
+ if (!current || current.activitySource !== "runtime") {
1542
+ return false;
1543
+ }
1544
+ if (!inspection.lastEventAt || !current.lastEventAt) {
1545
+ return true;
1546
+ }
1547
+ if (isTerminalRunningState(current.runningState)) {
1548
+ return inspection.lastEventAt.localeCompare(current.lastEventAt) <= 0;
1549
+ }
1550
+ if (current.runningState === "starting" || current.runningState === "running") {
1551
+ return inspection.lastEventAt.localeCompare(current.lastEventAt) < 0;
1552
+ }
1553
+ return false;
1554
+ }
1555
+ function isTerminalRunningState(state) {
1556
+ return state === "completed" || state === "interrupted" || state === "failed";
1557
+ }
1558
+ //# sourceMappingURL=session-history-service.js.map