@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,1303 @@
1
+ import { existsSync, readdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { ClaudeRuntimeAdapter, CodexRuntimeAdapter, OpenCodeRuntimeAdapter, ProviderRuntimeService } from "@codingns/session-sync-core";
4
+ import { AppError } from "../../shared/errors/app-error.js";
5
+ import { createId } from "../../shared/utils/id.js";
6
+ import { nowIso } from "../../shared/utils/time.js";
7
+ import { mapSessionProviderError } from "./session-provider-error-mapper.js";
8
+ export class SessionLiveRuntimeService {
9
+ sessionHistoryService;
10
+ sessionMessageAttachmentService;
11
+ workspaceService;
12
+ sessionChangedFileService;
13
+ sessionBindingRepository;
14
+ authUserRepository;
15
+ sessionSendQueueRepository;
16
+ sessionIndexRepository;
17
+ sessionStateRepository;
18
+ sessionStatusSnapshotRepository;
19
+ config;
20
+ providerRuntimeService;
21
+ externalRuntimeSnapshots = new Map();
22
+ runtimeListeners = new Map();
23
+ runtimeMessageSeenSessions = new Set();
24
+ runtimeHistoryFallbackSentSessions = new Set();
25
+ queueDispatchSessions = new Set();
26
+ queueRetryTimers = new Map();
27
+ constructor(sessionHistoryService, sessionMessageAttachmentService, workspaceService, sessionChangedFileService, sessionBindingRepository, authUserRepository, sessionSendQueueRepository, sessionIndexRepository, sessionStateRepository, sessionStatusSnapshotRepository, config) {
28
+ this.sessionHistoryService = sessionHistoryService;
29
+ this.sessionMessageAttachmentService = sessionMessageAttachmentService;
30
+ this.workspaceService = workspaceService;
31
+ this.sessionChangedFileService = sessionChangedFileService;
32
+ this.sessionBindingRepository = sessionBindingRepository;
33
+ this.authUserRepository = authUserRepository;
34
+ this.sessionSendQueueRepository = sessionSendQueueRepository;
35
+ this.sessionIndexRepository = sessionIndexRepository;
36
+ this.sessionStateRepository = sessionStateRepository;
37
+ this.sessionStatusSnapshotRepository = sessionStatusSnapshotRepository;
38
+ this.config = config;
39
+ this.providerRuntimeService = new ProviderRuntimeService(createProviderRuntimeAdapters(config));
40
+ }
41
+ async startLiveSession(input) {
42
+ const requestStartedAt = nowIso();
43
+ const capabilities = this.sessionHistoryService.getProviderCapabilitiesSnapshot(input.provider);
44
+ const workspace = this.workspaceService.getWorkspaceOrThrow(input.workspaceId);
45
+ const sessionId = createId();
46
+ const persistedAttachments = this.persistMessageAttachments(sessionId, input.clientRequestId, input.runtimeOptions?.attachments ?? []);
47
+ const providerPrompt = this.sessionMessageAttachmentService.buildProviderPrompt(input.provider, input.content, persistedAttachments.runtimeAttachments);
48
+ this.ensureCapability(capabilities.canStartSession, "provider", "provider 不支持 start-live");
49
+ this.ensureCapability(capabilities.canSendMessage, "provider", "provider 不支持实时对话");
50
+ const handle = await this.launchRuntimeRun({
51
+ sessionId,
52
+ workspaceId: workspace.id,
53
+ workspacePath: workspace.path,
54
+ provider: input.provider,
55
+ providerSessionId: null,
56
+ rawStoreRef: null,
57
+ options: {
58
+ content: input.content,
59
+ clientRequestId: input.clientRequestId,
60
+ model: input.runtimeOptions?.model ?? null,
61
+ reasoningLevel: input.runtimeOptions?.reasoningLevel ?? null,
62
+ permissionMode: input.runtimeOptions?.permissionMode ?? null,
63
+ providerPrompt,
64
+ attachments: persistedAttachments.runtimeAttachments
65
+ }
66
+ }, "start");
67
+ const snapshot = handle.getSnapshot();
68
+ this.attachRuntimePersistence(handle, sessionId, workspace.id, input.userId);
69
+ this.createRuntimeBackedSession({
70
+ sessionId,
71
+ workspaceId: workspace.id,
72
+ userId: input.userId,
73
+ provider: input.provider,
74
+ initialContent: input.content,
75
+ snapshot
76
+ });
77
+ const binding = this.sessionHistoryService.getBindingOrThrow(sessionId);
78
+ const acceptedMessage = await this.findAcceptedUserMessage(sessionId, this.sessionMessageAttachmentService.buildAcceptedContentCandidates(input.content, providerPrompt), requestStartedAt);
79
+ const acceptedAt = acceptedMessage?.timestamp ?? nowIso();
80
+ const boundAttachments = this.sessionMessageAttachmentService.bindClientRequestToMessage(sessionId, input.clientRequestId, acceptedMessage?.messageId ?? null);
81
+ return {
82
+ sessionId,
83
+ provider: input.provider,
84
+ providerSessionId: binding.providerSessionId,
85
+ acceptedAt,
86
+ clientRequestId: input.clientRequestId,
87
+ message: (acceptedMessage
88
+ ? {
89
+ ...acceptedMessage,
90
+ attachments: boundAttachments
91
+ }
92
+ : null) ??
93
+ createSyntheticUserMessage(input.provider, binding.providerSessionId, input.content, acceptedAt, 1, boundAttachments.length > 0
94
+ ? boundAttachments
95
+ : persistedAttachments.messageAttachments),
96
+ session: this.sessionHistoryService.getSession(sessionId, input.userId)
97
+ };
98
+ }
99
+ async sendLiveMessage(input) {
100
+ return this.sendLiveMessageDirect(input);
101
+ }
102
+ async listQueuedMessages(sessionId, userId) {
103
+ const session = await this.resolveQueueDispatchSession(sessionId, userId);
104
+ this.maybeDispatchQueuedMessages(session);
105
+ return this.sessionSendQueueRepository
106
+ .listBySessionAndUser(sessionId, userId)
107
+ .map(mapQueueItemRecordToView);
108
+ }
109
+ async enqueueLiveMessage(input) {
110
+ const session = await this.resolveQueueDispatchSession(input.sessionId, input.userId);
111
+ this.persistMessageAttachments(input.sessionId, input.clientRequestId, input.runtimeOptions?.attachments ?? []);
112
+ const timestamp = nowIso();
113
+ const queueItem = {
114
+ id: createId(),
115
+ sessionId: input.sessionId,
116
+ userId: input.userId,
117
+ content: input.content,
118
+ clientRequestId: input.clientRequestId,
119
+ model: input.runtimeOptions?.model ?? null,
120
+ reasoningLevel: input.runtimeOptions?.reasoningLevel ?? null,
121
+ permissionMode: input.runtimeOptions?.permissionMode ?? null,
122
+ status: "queued",
123
+ orderIndex: this.sessionSendQueueRepository.getNextOrderIndex(input.sessionId),
124
+ errorDetail: null,
125
+ createdAt: timestamp,
126
+ updatedAt: timestamp,
127
+ dispatchedAt: null
128
+ };
129
+ this.sessionSendQueueRepository.insert(queueItem);
130
+ this.maybeDispatchQueuedMessages(session);
131
+ return mapQueueItemRecordToView(queueItem);
132
+ }
133
+ async steerQueuedMessage(sessionId, userId, queueItemId) {
134
+ const session = await this.resolveQueueDispatchSession(sessionId, userId);
135
+ const queueItem = this.sessionSendQueueRepository.findBySessionUserAndId(sessionId, userId, queueItemId);
136
+ if (!queueItem) {
137
+ throw new AppError({
138
+ statusCode: 404,
139
+ errorCode: "QUEUE_ITEM_NOT_FOUND",
140
+ detail: "未找到对应的发送队列项",
141
+ field: "queueItemId"
142
+ });
143
+ }
144
+ if (queueItem.status !== "queued" && queueItem.status !== "failed") {
145
+ throw new AppError({
146
+ statusCode: 409,
147
+ errorCode: "QUEUE_ITEM_NOT_STEERABLE",
148
+ detail: "该队列项已经开始发送,当前不能再引导",
149
+ field: "queueItemId"
150
+ });
151
+ }
152
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(sessionId);
153
+ if (!runtimeSnapshot || !isActiveRuntimeState(runtimeSnapshot.runningState)) {
154
+ throw new AppError({
155
+ statusCode: 409,
156
+ errorCode: "SESSION_NOT_RUNNING",
157
+ detail: "当前会话不在运行中,无法立刻引导这条消息",
158
+ field: "queueItemId"
159
+ });
160
+ }
161
+ const capabilities = await this.sessionHistoryService.getSessionCapabilities(sessionId);
162
+ if (capabilities.inRunInputMode === "none") {
163
+ throw new AppError({
164
+ statusCode: 409,
165
+ errorCode: "QUEUE_STEER_NOT_SUPPORTED",
166
+ detail: "当前 provider 不支持把等待消息立刻引导到正在运行的会话",
167
+ field: "queueItemId"
168
+ });
169
+ }
170
+ const dispatchStartedAt = nowIso();
171
+ const claimed = this.sessionSendQueueRepository.markDispatching(queueItem.id, dispatchStartedAt);
172
+ if (!claimed) {
173
+ throw new AppError({
174
+ statusCode: 409,
175
+ errorCode: "QUEUE_ITEM_NOT_STEERABLE",
176
+ detail: "该队列项状态已经变化,请刷新后重试",
177
+ field: "queueItemId"
178
+ });
179
+ }
180
+ const restoredAttachments = queueItem.clientRequestId
181
+ ? this.sessionMessageAttachmentService.getRuntimeAttachments(sessionId, queueItem.clientRequestId)
182
+ : [];
183
+ const persistedAttachments = {
184
+ messageAttachments: restoredAttachments,
185
+ runtimeAttachments: restoredAttachments
186
+ };
187
+ try {
188
+ const result = await this.sendLiveMessageDirect({
189
+ sessionId,
190
+ userId,
191
+ content: queueItem.content,
192
+ clientRequestId: queueItem.clientRequestId,
193
+ runtimeOptions: {
194
+ model: queueItem.model,
195
+ reasoningLevel: queueItem.reasoningLevel,
196
+ permissionMode: queueItem.permissionMode,
197
+ attachments: []
198
+ }
199
+ }, persistedAttachments);
200
+ this.sessionSendQueueRepository.delete(queueItem.id);
201
+ return {
202
+ ...result,
203
+ queueItemId: queueItem.id,
204
+ session
205
+ };
206
+ }
207
+ catch (error) {
208
+ if (isQueueDispatchDeferredError(error)) {
209
+ this.sessionSendQueueRepository.markQueued(queueItem.id, nowIso());
210
+ this.scheduleQueueRetry(sessionId);
211
+ }
212
+ else {
213
+ this.sessionSendQueueRepository.markFailed(queueItem.id, error instanceof Error ? error.message : "QUEUE_STEER_FAILED", nowIso());
214
+ }
215
+ throw error;
216
+ }
217
+ }
218
+ async deleteQueuedMessage(sessionId, userId, queueItemId) {
219
+ this.sessionHistoryService.getSession(sessionId, userId);
220
+ const queueItem = this.sessionSendQueueRepository.findBySessionUserAndId(sessionId, userId, queueItemId);
221
+ if (!queueItem) {
222
+ throw new AppError({
223
+ statusCode: 404,
224
+ errorCode: "QUEUE_ITEM_NOT_FOUND",
225
+ detail: "未找到对应的发送队列项",
226
+ field: "queueItemId"
227
+ });
228
+ }
229
+ if (queueItem.status !== "queued" && queueItem.status !== "failed") {
230
+ throw new AppError({
231
+ statusCode: 409,
232
+ errorCode: "QUEUE_ITEM_NOT_DELETABLE",
233
+ detail: "该队列项已经开始发送,当前不允许删除",
234
+ field: "queueItemId"
235
+ });
236
+ }
237
+ this.sessionSendQueueRepository.delete(queueItemId);
238
+ this.sessionMessageAttachmentService.deletePendingAttachments(sessionId, queueItem.clientRequestId);
239
+ return {
240
+ sessionId,
241
+ queueItemId,
242
+ deleted: true
243
+ };
244
+ }
245
+ getClaudeHookBridgeConfig() {
246
+ const bridgeUrl = `http://127.0.0.1:${this.config.port}/api/providers/claude-code/hook-bridge/events`;
247
+ const scriptPath = path.resolve(process.cwd(), "scripts", "claude-hook-bridge.cjs");
248
+ const command = `node "${scriptPath}" --url "${bridgeUrl}" --token "${this.config.claudeHookBridgeToken}"`;
249
+ return {
250
+ provider: "claude-code",
251
+ bridgeUrl,
252
+ token: this.config.claudeHookBridgeToken,
253
+ scriptPath,
254
+ command,
255
+ supportedEvents: ["UserPromptSubmit", "SessionStart", "Stop", "StopFailure", "SessionEnd"]
256
+ };
257
+ }
258
+ async ingestClaudeHookEvent(payload) {
259
+ const hookEventName = normalizeClaudeHookEventName(payload.hook_event_name);
260
+ if (!hookEventName) {
261
+ throw new AppError({
262
+ statusCode: 400,
263
+ errorCode: "INVALID_INPUT",
264
+ detail: "hook_event_name 不能为空",
265
+ field: "hook_event_name"
266
+ });
267
+ }
268
+ if (!isSupportedClaudeHookEvent(hookEventName)) {
269
+ return {
270
+ accepted: true,
271
+ ignored: true,
272
+ sessionId: null
273
+ };
274
+ }
275
+ const providerSessionId = normalizeRequiredText(payload.session_id, "session_id");
276
+ const workspacePath = normalizeRequiredText(payload.cwd, "cwd");
277
+ const workspace = this.workspaceService.findWorkspaceByPath(workspacePath);
278
+ if (!workspace) {
279
+ return {
280
+ accepted: true,
281
+ ignored: true,
282
+ sessionId: null
283
+ };
284
+ }
285
+ const binding = await this.resolveClaudeExternalBinding({
286
+ providerSessionId,
287
+ workspaceId: workspace.id,
288
+ workspacePath: workspace.path,
289
+ transcriptPath: normalizeOptionalText(payload.transcript_path)
290
+ });
291
+ const timestamp = nowIso();
292
+ const runtimeUpdate = mapClaudeHookToRuntimeUpdate(hookEventName, payload, timestamp);
293
+ if (!runtimeUpdate) {
294
+ return {
295
+ accepted: true,
296
+ ignored: true,
297
+ sessionId: binding.sessionId
298
+ };
299
+ }
300
+ if (this.shouldIgnoreClaudeExternalRuntimeUpdate(binding.sessionId)) {
301
+ this.clearExternalRuntimeSnapshot(binding.sessionId);
302
+ return {
303
+ accepted: true,
304
+ ignored: true,
305
+ sessionId: binding.sessionId
306
+ };
307
+ }
308
+ await this.applyExternalRuntimeUpdate({
309
+ sessionId: binding.sessionId,
310
+ workspaceId: workspace.id,
311
+ providerSessionId,
312
+ rawStoreRef: binding.rawStoreRef,
313
+ ...runtimeUpdate
314
+ });
315
+ return {
316
+ accepted: true,
317
+ ignored: false,
318
+ sessionId: binding.sessionId
319
+ };
320
+ }
321
+ async getSessionRuntime(sessionId, userId) {
322
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(sessionId);
323
+ const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId) ?? null;
324
+ const session = runtimeSnapshot || externalRuntimeSnapshot
325
+ ? this.sessionHistoryService.getSession(sessionId, userId)
326
+ : await this.sessionHistoryService.refreshRuntimeFallbackSession(sessionId, userId);
327
+ this.maybeDispatchQueuedMessages(session);
328
+ const capabilities = await this.sessionHistoryService.getSessionCapabilities(sessionId);
329
+ const contextUsage = await this.sessionHistoryService.getSessionContextUsage(sessionId).catch(() => null);
330
+ if (runtimeSnapshot) {
331
+ return {
332
+ sessionId,
333
+ provider: session.provider,
334
+ providerSessionId: runtimeSnapshot.providerSessionId ?? session.providerSessionId,
335
+ runningState: runtimeSnapshot.runningState,
336
+ hasActiveRun: true,
337
+ canAttach: true,
338
+ canInterrupt: runtimeSnapshot.supportsInterrupt,
339
+ inRunInputMode: capabilities.inRunInputMode,
340
+ detail: runtimeSnapshot.detail,
341
+ errorCode: runtimeSnapshot.runningState === "failed"
342
+ ? runtimeSnapshot.errorCode ?? session.lastErrorCode
343
+ : null,
344
+ errorDetail: runtimeSnapshot.runningState === "failed"
345
+ ? runtimeSnapshot.detail ?? session.lastErrorDetail
346
+ : null,
347
+ updatedAt: runtimeSnapshot.lastEventAt ?? runtimeSnapshot.startedAt,
348
+ contextUsage
349
+ };
350
+ }
351
+ if (externalRuntimeSnapshot) {
352
+ return {
353
+ sessionId,
354
+ provider: "claude-code",
355
+ providerSessionId: externalRuntimeSnapshot.providerSessionId,
356
+ runningState: externalRuntimeSnapshot.runningState,
357
+ hasActiveRun: true,
358
+ canAttach: false,
359
+ canInterrupt: false,
360
+ inRunInputMode: capabilities.inRunInputMode,
361
+ detail: externalRuntimeSnapshot.detail,
362
+ errorCode: session.runningState === "failed" ? session.lastErrorCode : null,
363
+ errorDetail: session.runningState === "failed" ? session.lastErrorDetail : null,
364
+ updatedAt: externalRuntimeSnapshot.updatedAt,
365
+ contextUsage
366
+ };
367
+ }
368
+ const persistedErrorCode = session.runningState === "failed" ? session.lastErrorCode : null;
369
+ const persistedErrorDetail = session.runningState === "failed" ? session.lastErrorDetail : null;
370
+ return {
371
+ sessionId,
372
+ provider: session.provider,
373
+ providerSessionId: session.providerSessionId,
374
+ runningState: session.runningState ?? "idle",
375
+ hasActiveRun: false,
376
+ canAttach: false,
377
+ canInterrupt: false,
378
+ inRunInputMode: capabilities.inRunInputMode,
379
+ detail: persistedErrorDetail,
380
+ errorCode: persistedErrorCode,
381
+ errorDetail: persistedErrorDetail,
382
+ updatedAt: session.lastEventAt ?? session.updatedAt,
383
+ contextUsage
384
+ };
385
+ }
386
+ async interruptSession(sessionId, userId) {
387
+ this.sessionHistoryService.getSession(sessionId, userId);
388
+ const runtime = this.providerRuntimeService.getSnapshot(sessionId);
389
+ if (!runtime || (runtime.runningState !== "running" && runtime.runningState !== "starting")) {
390
+ throw new AppError({
391
+ statusCode: 409,
392
+ errorCode: "SESSION_NOT_RUNNING",
393
+ detail: "当前会话不在运行中,无法中断",
394
+ field: "sessionId"
395
+ });
396
+ }
397
+ const interrupted = await this.providerRuntimeService.interrupt(sessionId).catch((error) => {
398
+ if (error instanceof Error && error.message === "INTERRUPT_NOT_SUPPORTED") {
399
+ throw new AppError({
400
+ statusCode: 400,
401
+ errorCode: "CAPABILITY_NOT_SUPPORTED",
402
+ detail: "当前 provider 不支持中断",
403
+ field: "sessionId"
404
+ });
405
+ }
406
+ throw mapSessionProviderError(error);
407
+ });
408
+ return {
409
+ sessionId,
410
+ interrupted: true,
411
+ detail: interrupted.detail ?? "interrupt requested"
412
+ };
413
+ }
414
+ subscribeRuntime(sessionId, onEnvelope) {
415
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(sessionId);
416
+ const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId) ?? null;
417
+ if (runtimeSnapshot) {
418
+ void onEnvelope({
419
+ type: "session.runtime_status",
420
+ sessionId,
421
+ status: runtimeSnapshot.runningState,
422
+ detail: runtimeSnapshot.detail,
423
+ timestamp: runtimeSnapshot.lastEventAt ?? runtimeSnapshot.startedAt
424
+ });
425
+ }
426
+ if (externalRuntimeSnapshot) {
427
+ void onEnvelope({
428
+ type: "session.runtime_status",
429
+ sessionId,
430
+ status: externalRuntimeSnapshot.runningState,
431
+ detail: externalRuntimeSnapshot.detail,
432
+ timestamp: externalRuntimeSnapshot.updatedAt
433
+ });
434
+ }
435
+ const runtimeSubscription = this.providerRuntimeService.subscribe(sessionId, async (event) => {
436
+ const envelope = this.mapRuntimeEventToEnvelope(sessionId, event);
437
+ if (!envelope) {
438
+ return;
439
+ }
440
+ await onEnvelope(envelope);
441
+ });
442
+ const externalSubscription = this.subscribeExternalRuntime(sessionId, onEnvelope);
443
+ return {
444
+ close: () => {
445
+ runtimeSubscription.close();
446
+ externalSubscription.close();
447
+ }
448
+ };
449
+ }
450
+ async dispose() {
451
+ this.queueRetryTimers.forEach((timer) => {
452
+ clearTimeout(timer);
453
+ });
454
+ this.queueRetryTimers.clear();
455
+ this.runtimeMessageSeenSessions.clear();
456
+ this.runtimeHistoryFallbackSentSessions.clear();
457
+ await this.providerRuntimeService.dispose();
458
+ this.externalRuntimeSnapshots.clear();
459
+ this.runtimeListeners.clear();
460
+ }
461
+ subscribeExternalRuntime(sessionId, listener) {
462
+ const listeners = this.runtimeListeners.get(sessionId) ?? new Set();
463
+ listeners.add(listener);
464
+ this.runtimeListeners.set(sessionId, listeners);
465
+ let closed = false;
466
+ return {
467
+ close: () => {
468
+ if (closed) {
469
+ return;
470
+ }
471
+ closed = true;
472
+ const nextListeners = this.runtimeListeners.get(sessionId);
473
+ if (!nextListeners) {
474
+ return;
475
+ }
476
+ nextListeners.delete(listener);
477
+ if (nextListeners.size === 0) {
478
+ this.runtimeListeners.delete(sessionId);
479
+ }
480
+ }
481
+ };
482
+ }
483
+ async emitExternalRuntimeEnvelope(envelope) {
484
+ const listeners = this.runtimeListeners.get(envelope.sessionId);
485
+ if (!listeners || listeners.size === 0) {
486
+ return;
487
+ }
488
+ await Promise.all([...listeners].map(async (listener) => {
489
+ await listener(envelope);
490
+ }));
491
+ }
492
+ async resolveClaudeExternalBinding(input) {
493
+ const rawStoreRef = input.transcriptPath ??
494
+ findClaudeSessionFile(this.config.claudeCodeHomeDir, input.providerSessionId) ??
495
+ buildClaudeRawStoreRef(this.config.claudeCodeHomeDir, input.workspacePath, input.providerSessionId);
496
+ let binding = this.sessionBindingRepository.findByProviderSession("claude-code", input.providerSessionId) ??
497
+ this.sessionBindingRepository.findByRawStoreRef("claude-code", rawStoreRef);
498
+ if (!binding) {
499
+ const userIds = this.authUserRepository.listIds();
500
+ const bootstrapUserId = userIds[0] ?? null;
501
+ if (bootstrapUserId) {
502
+ await this.sessionHistoryService.discoverWorkspaceSessions(input.workspaceId, bootstrapUserId, {
503
+ force: true,
504
+ refreshStateMode: "deferred"
505
+ }).catch(() => {
506
+ return;
507
+ });
508
+ }
509
+ binding =
510
+ this.sessionBindingRepository.findByProviderSession("claude-code", input.providerSessionId) ??
511
+ this.sessionBindingRepository.findByRawStoreRef("claude-code", rawStoreRef);
512
+ }
513
+ if (binding) {
514
+ return {
515
+ sessionId: binding.sessionId,
516
+ rawStoreRef: binding.rawStoreRef
517
+ };
518
+ }
519
+ const sessionId = createId();
520
+ const timestamp = nowIso();
521
+ this.sessionHistoryService.persistSessionBinding(sessionId, input.workspaceId, {
522
+ provider: "claude-code",
523
+ providerSessionId: input.providerSessionId,
524
+ rawStoreRef
525
+ });
526
+ this.sessionIndexRepository.upsert({
527
+ sessionId,
528
+ workspaceId: input.workspaceId,
529
+ provider: "claude-code",
530
+ parentSessionId: null,
531
+ isSubagent: false,
532
+ subagentLabel: null,
533
+ title: `Claude 会话 ${input.providerSessionId.slice(0, 8)}`,
534
+ messageCount: 0,
535
+ isArchived: false,
536
+ lastMessageAt: null,
537
+ createdAt: timestamp,
538
+ updatedAt: timestamp
539
+ });
540
+ this.upsertSnapshot(sessionId, {
541
+ syncStatus: "idle",
542
+ syncCursor: null,
543
+ lastSyncAt: null,
544
+ lastErrorCode: null,
545
+ lastErrorDetail: null,
546
+ resumedAt: null
547
+ });
548
+ return {
549
+ sessionId,
550
+ rawStoreRef
551
+ };
552
+ }
553
+ async applyExternalRuntimeUpdate(input) {
554
+ const userIds = this.authUserRepository.listIds();
555
+ if (userIds.length === 0) {
556
+ return;
557
+ }
558
+ const existingIndex = this.sessionIndexRepository.findIndexRecordBySessionId(input.sessionId);
559
+ if (existingIndex) {
560
+ const nextLastMessageAt = existingIndex.lastMessageAt && existingIndex.lastMessageAt.localeCompare(input.timestamp) >= 0
561
+ ? existingIndex.lastMessageAt
562
+ : input.timestamp;
563
+ this.sessionIndexRepository.upsert({
564
+ ...existingIndex,
565
+ lastMessageAt: nextLastMessageAt,
566
+ updatedAt: input.timestamp
567
+ });
568
+ }
569
+ for (const userId of userIds) {
570
+ const current = this.sessionStateRepository.findBySessionAndUser(input.sessionId, userId);
571
+ if (current?.lastEventAt && current.lastEventAt.localeCompare(input.timestamp) > 0) {
572
+ continue;
573
+ }
574
+ this.sessionStateRepository.upsert({
575
+ sessionId: input.sessionId,
576
+ userId,
577
+ runningState: input.runningState,
578
+ activitySource: "runtime",
579
+ favorite: current?.favorite ?? false,
580
+ lastEventAt: input.timestamp,
581
+ completedAt: isTerminalSessionRunningState(input.runningState) ? input.timestamp : null,
582
+ lastSeenAt: current?.lastSeenAt ?? null,
583
+ updatedAt: nowIso()
584
+ });
585
+ }
586
+ this.upsertSnapshot(input.sessionId, {
587
+ syncStatus: input.runningState === "failed" ? "error" : "idle",
588
+ syncCursor: this.sessionStatusSnapshotRepository.findBySessionId(input.sessionId)?.syncCursor ?? null,
589
+ lastSyncAt: input.timestamp,
590
+ lastErrorCode: input.runningState === "failed" ? "CLAUDE_HOOK_STOP_FAILURE" : null,
591
+ lastErrorDetail: input.runningState === "failed" ? (input.detail ?? "Claude hook failed") : null,
592
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(input.sessionId)?.resumedAt ?? null
593
+ });
594
+ if (input.runningState === "running") {
595
+ this.externalRuntimeSnapshots.set(input.sessionId, {
596
+ sessionId: input.sessionId,
597
+ provider: "claude-code",
598
+ providerSessionId: input.providerSessionId,
599
+ rawStoreRef: input.rawStoreRef,
600
+ runningState: input.runningState,
601
+ detail: input.detail,
602
+ updatedAt: input.timestamp
603
+ });
604
+ }
605
+ else {
606
+ this.externalRuntimeSnapshots.delete(input.sessionId);
607
+ }
608
+ const envelope = input.runningState === "failed"
609
+ ? {
610
+ type: "session.runtime_error",
611
+ sessionId: input.sessionId,
612
+ error_code: "CLAUDE_HOOK_STOP_FAILURE",
613
+ detail: input.detail ?? "Claude hook failed",
614
+ timestamp: input.timestamp
615
+ }
616
+ : {
617
+ type: "session.runtime_status",
618
+ sessionId: input.sessionId,
619
+ status: input.runningState,
620
+ detail: input.detail,
621
+ timestamp: input.timestamp
622
+ };
623
+ await this.emitExternalRuntimeEnvelope(envelope);
624
+ if (isTerminalSessionRunningState(input.runningState)) {
625
+ void this.dispatchNextQueuedMessage(input.sessionId);
626
+ }
627
+ }
628
+ async startRuntimeRun(request, userId, mode) {
629
+ this.runtimeMessageSeenSessions.delete(request.sessionId);
630
+ this.runtimeHistoryFallbackSentSessions.delete(request.sessionId);
631
+ if (request.provider === "claude-code") {
632
+ this.clearExternalRuntimeSnapshot(request.sessionId);
633
+ }
634
+ const handle = await this.launchRuntimeRun(request, mode);
635
+ const snapshot = handle.getSnapshot();
636
+ const currentState = this.sessionStateRepository.findBySessionAndUser(request.sessionId, userId);
637
+ this.attachRuntimePersistence(handle, request.sessionId, request.workspaceId, userId);
638
+ this.sessionHistoryService.persistSessionBinding(request.sessionId, request.workspaceId, snapshot);
639
+ this.sessionStateRepository.upsert({
640
+ sessionId: request.sessionId,
641
+ userId,
642
+ runningState: toStoredRunningState(snapshot.runningState),
643
+ activitySource: "runtime",
644
+ favorite: currentState?.favorite ?? false,
645
+ lastEventAt: snapshot.lastEventAt,
646
+ completedAt: snapshot.completedAt,
647
+ lastSeenAt: currentState?.lastSeenAt ?? null,
648
+ updatedAt: nowIso()
649
+ });
650
+ }
651
+ async sendLiveMessageDirect(input, persistedAttachments) {
652
+ const requestStartedAt = nowIso();
653
+ const session = this.sessionHistoryService.getSession(input.sessionId, input.userId);
654
+ const capabilities = await this.sessionHistoryService.getSessionCapabilities(input.sessionId);
655
+ const workspace = this.workspaceService.getWorkspaceOrThrow(session.workspaceId);
656
+ const runtimeMode = shouldStartNativeSessionOnFirstMessage(session);
657
+ const resolvedAttachments = persistedAttachments
658
+ ?? this.persistMessageAttachments(input.sessionId, input.clientRequestId, input.runtimeOptions?.attachments ?? []);
659
+ const providerPrompt = this.sessionMessageAttachmentService.buildProviderPrompt(session.provider, input.content, resolvedAttachments.runtimeAttachments);
660
+ this.ensureCapability(capabilities.canSendMessage, "sessionId", "provider 不支持实时对话");
661
+ const runtimeRequest = {
662
+ sessionId: input.sessionId,
663
+ workspaceId: session.workspaceId,
664
+ workspacePath: workspace.path,
665
+ provider: session.provider,
666
+ providerSessionId: runtimeMode === "start" ? null : session.providerSessionId,
667
+ rawStoreRef: runtimeMode === "start" ? null : session.rawStoreRef,
668
+ options: {
669
+ content: input.content,
670
+ clientRequestId: input.clientRequestId,
671
+ model: input.runtimeOptions?.model ?? null,
672
+ reasoningLevel: input.runtimeOptions?.reasoningLevel ?? null,
673
+ permissionMode: input.runtimeOptions?.permissionMode ?? null,
674
+ providerPrompt,
675
+ attachments: resolvedAttachments.runtimeAttachments
676
+ }
677
+ };
678
+ const activeRun = this.providerRuntimeService.getSnapshot(input.sessionId);
679
+ const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(input.sessionId);
680
+ if (activeRun &&
681
+ activeRun.provider === "claude-code" &&
682
+ isActiveRuntimeState(activeRun.runningState)) {
683
+ this.clearExternalRuntimeSnapshot(input.sessionId);
684
+ }
685
+ if (!activeRun &&
686
+ session.provider === "claude-code" &&
687
+ externalRuntimeSnapshot &&
688
+ isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
689
+ throw new AppError({
690
+ statusCode: 409,
691
+ errorCode: "SESSION_EXTERNAL_RUN_ACTIVE",
692
+ detail: "当前 Claude 外部会话仍在运行,不能直接追加;请加入队列或等待当前轮结束",
693
+ field: "sessionId"
694
+ });
695
+ }
696
+ if (activeRun && isActiveRuntimeState(activeRun.runningState)) {
697
+ await this.providerRuntimeService.submitToActiveRun(input.sessionId, runtimeRequest.options)
698
+ .catch((error) => {
699
+ throw mapSessionProviderError(error);
700
+ });
701
+ }
702
+ else {
703
+ await this.startRuntimeRun(runtimeRequest, input.userId, runtimeMode);
704
+ }
705
+ const binding = this.sessionHistoryService.getBindingOrThrow(input.sessionId);
706
+ const acceptedMessage = await this.findAcceptedUserMessage(input.sessionId, this.sessionMessageAttachmentService.buildAcceptedContentCandidates(input.content, providerPrompt), requestStartedAt);
707
+ const acceptedAt = acceptedMessage?.timestamp ?? nowIso();
708
+ const boundAttachments = this.sessionMessageAttachmentService.bindClientRequestToMessage(input.sessionId, input.clientRequestId, acceptedMessage?.messageId ?? null);
709
+ return {
710
+ sessionId: input.sessionId,
711
+ provider: session.provider,
712
+ providerSessionId: binding.providerSessionId,
713
+ acceptedAt,
714
+ clientRequestId: input.clientRequestId,
715
+ message: (acceptedMessage
716
+ ? {
717
+ ...acceptedMessage,
718
+ attachments: boundAttachments
719
+ }
720
+ : null) ??
721
+ createSyntheticUserMessage(session.provider, binding.providerSessionId, input.content, acceptedAt, Math.max(session.messageCount + 1, 1), boundAttachments.length > 0
722
+ ? boundAttachments
723
+ : resolvedAttachments.messageAttachments)
724
+ };
725
+ }
726
+ async dispatchNextQueuedMessage(sessionId) {
727
+ if (this.queueDispatchSessions.has(sessionId)) {
728
+ return;
729
+ }
730
+ this.queueDispatchSessions.add(sessionId);
731
+ try {
732
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(sessionId);
733
+ const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId);
734
+ if ((runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState))
735
+ || (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
736
+ return;
737
+ }
738
+ const nextQueueItem = this.sessionSendQueueRepository.findNextQueued(sessionId);
739
+ if (!nextQueueItem) {
740
+ return;
741
+ }
742
+ const dispatchStartedAt = nowIso();
743
+ const claimed = this.sessionSendQueueRepository.markDispatching(nextQueueItem.id, dispatchStartedAt);
744
+ if (!claimed) {
745
+ return;
746
+ }
747
+ const session = await this.findSessionForQueueDispatch(nextQueueItem);
748
+ if (session && this.shouldBlockQueueDispatch(session)) {
749
+ this.sessionSendQueueRepository.markQueued(nextQueueItem.id, nowIso());
750
+ this.scheduleQueueRetry(sessionId);
751
+ return;
752
+ }
753
+ const restoredAttachments = nextQueueItem.clientRequestId
754
+ ? this.sessionMessageAttachmentService.getRuntimeAttachments(sessionId, nextQueueItem.clientRequestId)
755
+ : [];
756
+ const persistedAttachments = {
757
+ messageAttachments: restoredAttachments,
758
+ runtimeAttachments: restoredAttachments
759
+ };
760
+ try {
761
+ await this.sendLiveMessageDirect({
762
+ sessionId: nextQueueItem.sessionId,
763
+ userId: nextQueueItem.userId,
764
+ content: nextQueueItem.content,
765
+ clientRequestId: nextQueueItem.clientRequestId,
766
+ runtimeOptions: {
767
+ model: nextQueueItem.model,
768
+ reasoningLevel: nextQueueItem.reasoningLevel,
769
+ permissionMode: nextQueueItem.permissionMode,
770
+ attachments: []
771
+ }
772
+ }, persistedAttachments);
773
+ this.sessionSendQueueRepository.delete(nextQueueItem.id);
774
+ }
775
+ catch (error) {
776
+ if (isQueueDispatchDeferredError(error)) {
777
+ this.sessionSendQueueRepository.markQueued(nextQueueItem.id, nowIso());
778
+ this.scheduleQueueRetry(sessionId);
779
+ return;
780
+ }
781
+ this.sessionSendQueueRepository.markFailed(nextQueueItem.id, error instanceof Error ? error.message : "QUEUE_DISPATCH_FAILED", nowIso());
782
+ }
783
+ }
784
+ finally {
785
+ this.queueDispatchSessions.delete(sessionId);
786
+ }
787
+ }
788
+ maybeDispatchQueuedMessages(session) {
789
+ if (this.shouldBlockQueueDispatch(session)) {
790
+ return;
791
+ }
792
+ void this.dispatchNextQueuedMessage(session.sessionId);
793
+ }
794
+ shouldBlockQueueDispatch(session) {
795
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(session.sessionId);
796
+ if (runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState)) {
797
+ return true;
798
+ }
799
+ const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(session.sessionId);
800
+ if (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState)) {
801
+ return true;
802
+ }
803
+ if (session.provider === "claude-code" && isPendingSessionRunningState(session.runningState)) {
804
+ return true;
805
+ }
806
+ return false;
807
+ }
808
+ scheduleQueueRetry(sessionId) {
809
+ if (this.queueRetryTimers.has(sessionId)) {
810
+ return;
811
+ }
812
+ const timer = setTimeout(() => {
813
+ this.queueRetryTimers.delete(sessionId);
814
+ void this.dispatchNextQueuedMessage(sessionId);
815
+ }, 1200);
816
+ this.queueRetryTimers.set(sessionId, timer);
817
+ }
818
+ async findSessionForQueueDispatch(queueItem) {
819
+ if (!queueItem) {
820
+ return null;
821
+ }
822
+ try {
823
+ return await this.resolveQueueDispatchSession(queueItem.sessionId, queueItem.userId);
824
+ }
825
+ catch {
826
+ return null;
827
+ }
828
+ }
829
+ async resolveQueueDispatchSession(sessionId, userId) {
830
+ const session = this.sessionHistoryService.getSession(sessionId, userId);
831
+ if (session.provider !== "claude-code"
832
+ || !isPendingSessionRunningState(session.runningState)) {
833
+ return session;
834
+ }
835
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(sessionId);
836
+ const externalRuntimeSnapshot = this.externalRuntimeSnapshots.get(sessionId);
837
+ if ((runtimeSnapshot && isActiveRuntimeState(runtimeSnapshot.runningState))
838
+ || (externalRuntimeSnapshot && isActiveRuntimeState(externalRuntimeSnapshot.runningState))) {
839
+ return session;
840
+ }
841
+ return Promise.resolve(this.sessionHistoryService.refreshRuntimeFallbackSession(sessionId, userId))
842
+ .then((refreshedSession) => refreshedSession ?? session)
843
+ .catch(() => session);
844
+ }
845
+ async launchRuntimeRun(request, mode) {
846
+ try {
847
+ return await (mode === "start"
848
+ ? this.providerRuntimeService.startSession(request)
849
+ : this.providerRuntimeService.continueSession(request));
850
+ }
851
+ catch (error) {
852
+ throw mapSessionProviderError(error);
853
+ }
854
+ }
855
+ attachRuntimePersistence(handle, sessionId, workspaceId, userId) {
856
+ handle.attach(async (event) => {
857
+ await this.persistRuntimeEvent(sessionId, workspaceId, userId, event);
858
+ });
859
+ }
860
+ createRuntimeBackedSession(input) {
861
+ const timestamp = nowIso();
862
+ const providerSessionId = input.snapshot.providerSessionId ?? `pending://${input.provider}/${input.sessionId}`;
863
+ const rawStoreRef = input.snapshot.rawStoreRef ?? `pending://${input.provider}/${input.sessionId}`;
864
+ this.sessionHistoryService.persistSessionBinding(input.sessionId, input.workspaceId, {
865
+ provider: input.snapshot.provider,
866
+ providerSessionId,
867
+ rawStoreRef
868
+ });
869
+ this.sessionIndexRepository.upsert({
870
+ sessionId: input.sessionId,
871
+ workspaceId: input.workspaceId,
872
+ provider: input.provider,
873
+ parentSessionId: null,
874
+ isSubagent: false,
875
+ subagentLabel: null,
876
+ title: buildSessionTitle(input.initialContent),
877
+ messageCount: 0,
878
+ isArchived: false,
879
+ lastMessageAt: input.snapshot.lastEventAt,
880
+ createdAt: timestamp,
881
+ updatedAt: timestamp
882
+ });
883
+ this.upsertSnapshot(input.sessionId, {
884
+ syncStatus: "idle",
885
+ syncCursor: null,
886
+ lastSyncAt: input.snapshot.lastEventAt ?? timestamp,
887
+ lastErrorCode: null,
888
+ lastErrorDetail: null,
889
+ resumedAt: null
890
+ });
891
+ this.sessionStateRepository.upsert({
892
+ sessionId: input.sessionId,
893
+ userId: input.userId,
894
+ runningState: toStoredRunningState(input.snapshot.runningState),
895
+ activitySource: "runtime",
896
+ favorite: false,
897
+ lastEventAt: input.snapshot.lastEventAt,
898
+ completedAt: input.snapshot.completedAt,
899
+ lastSeenAt: null,
900
+ updatedAt: timestamp
901
+ });
902
+ }
903
+ async persistRuntimeEvent(sessionId, workspaceId, userId, event) {
904
+ this.sessionHistoryService.persistSessionBinding(sessionId, workspaceId, {
905
+ provider: event.provider,
906
+ providerSessionId: event.providerSessionId,
907
+ rawStoreRef: event.rawStoreRef
908
+ });
909
+ const currentState = this.sessionStateRepository.findBySessionAndUser(sessionId, userId);
910
+ const currentRunningState = currentState?.runningState ?? null;
911
+ const shouldPreserveTerminalState = isTerminalSessionRunningState(currentRunningState);
912
+ if (event.type === "message") {
913
+ this.runtimeMessageSeenSessions.add(sessionId);
914
+ this.runtimeHistoryFallbackSentSessions.delete(sessionId);
915
+ const workspace = this.workspaceService.getWorkspaceOrThrow(workspaceId);
916
+ await this.sessionHistoryService.syncSessionTitle(sessionId).catch(() => {
917
+ return;
918
+ });
919
+ const existing = this.sessionIndexRepository.findIndexRecordBySessionId(sessionId);
920
+ if (existing) {
921
+ this.sessionIndexRepository.upsert({
922
+ ...existing,
923
+ messageCount: existing.messageCount + 1,
924
+ lastMessageAt: event.message.timestamp,
925
+ updatedAt: event.message.timestamp
926
+ });
927
+ }
928
+ this.sessionChangedFileService.recordMessages(sessionId, workspaceId, workspace.path, [event.message]);
929
+ this.sessionStateRepository.upsert({
930
+ sessionId,
931
+ userId,
932
+ runningState: shouldPreserveTerminalState ? currentRunningState : "running",
933
+ activitySource: "runtime",
934
+ favorite: currentState?.favorite ?? false,
935
+ lastEventAt: event.message.timestamp,
936
+ completedAt: shouldPreserveTerminalState ? currentState?.completedAt ?? null : null,
937
+ lastSeenAt: currentState?.lastSeenAt ?? null,
938
+ updatedAt: nowIso()
939
+ });
940
+ this.upsertSnapshot(sessionId, {
941
+ syncStatus: "idle",
942
+ syncCursor: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.syncCursor ?? null,
943
+ lastSyncAt: event.message.timestamp,
944
+ lastErrorCode: null,
945
+ lastErrorDetail: null,
946
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
947
+ });
948
+ return;
949
+ }
950
+ if (shouldPreserveTerminalState) {
951
+ this.sessionStateRepository.upsert({
952
+ sessionId,
953
+ userId,
954
+ runningState: currentRunningState,
955
+ activitySource: "runtime",
956
+ favorite: currentState?.favorite ?? false,
957
+ lastEventAt: event.timestamp,
958
+ completedAt: currentState?.completedAt ?? null,
959
+ lastSeenAt: currentState?.lastSeenAt ?? null,
960
+ updatedAt: nowIso()
961
+ });
962
+ this.upsertSnapshot(sessionId, {
963
+ syncStatus: "idle",
964
+ syncCursor: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.syncCursor ?? null,
965
+ lastSyncAt: event.timestamp,
966
+ lastErrorCode: null,
967
+ lastErrorDetail: null,
968
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
969
+ });
970
+ await this.maybeEmitRuntimeHistoryFallback(sessionId, event);
971
+ return;
972
+ }
973
+ const completedAt = event.status === "completed" || event.status === "interrupted" || event.status === "failed"
974
+ ? event.timestamp
975
+ : this.sessionStateRepository.findBySessionAndUser(sessionId, userId)?.completedAt ?? null;
976
+ if (completedAt) {
977
+ await this.sessionHistoryService.syncSessionTitle(sessionId).catch(() => {
978
+ return;
979
+ });
980
+ }
981
+ this.sessionStateRepository.upsert({
982
+ sessionId,
983
+ userId,
984
+ runningState: toStoredRunningState(event.status),
985
+ activitySource: "runtime",
986
+ favorite: currentState?.favorite ?? false,
987
+ lastEventAt: event.timestamp,
988
+ completedAt,
989
+ lastSeenAt: currentState?.lastSeenAt ?? null,
990
+ updatedAt: nowIso()
991
+ });
992
+ this.upsertSnapshot(sessionId, {
993
+ syncStatus: event.type === "error" ? "error" : "idle",
994
+ syncCursor: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.syncCursor ?? null,
995
+ lastSyncAt: event.timestamp,
996
+ lastErrorCode: event.type === "error" ? event.errorCode : null,
997
+ lastErrorDetail: event.type === "error" ? (event.detail ?? "runtime failed") : null,
998
+ resumedAt: this.sessionStatusSnapshotRepository.findBySessionId(sessionId)?.resumedAt ?? null
999
+ });
1000
+ await this.maybeEmitRuntimeHistoryFallback(sessionId, event);
1001
+ if (isTerminalRuntimeEventStatus(event.status)) {
1002
+ void this.dispatchNextQueuedMessage(sessionId);
1003
+ }
1004
+ }
1005
+ async findAcceptedUserMessage(sessionId, content, minTimestamp) {
1006
+ try {
1007
+ return await withTimeout(this.sessionHistoryService.findLatestUserMessage(sessionId, content, 12, minTimestamp), 1200);
1008
+ }
1009
+ catch {
1010
+ return null;
1011
+ }
1012
+ }
1013
+ persistMessageAttachments(sessionId, clientRequestId, attachments) {
1014
+ if (!clientRequestId || attachments.length === 0) {
1015
+ return {
1016
+ messageAttachments: [],
1017
+ runtimeAttachments: []
1018
+ };
1019
+ }
1020
+ return this.sessionMessageAttachmentService.persistImageAttachments({
1021
+ sessionId,
1022
+ clientRequestId,
1023
+ attachments
1024
+ });
1025
+ }
1026
+ mapRuntimeEventToEnvelope(sessionId, event) {
1027
+ if (event.type === "message") {
1028
+ return {
1029
+ type: "session.runtime_message",
1030
+ sessionId,
1031
+ message: event.message,
1032
+ source: "runtime"
1033
+ };
1034
+ }
1035
+ if (event.type === "error") {
1036
+ return {
1037
+ type: "session.runtime_error",
1038
+ sessionId,
1039
+ error_code: event.errorCode,
1040
+ detail: event.detail ?? "runtime failed",
1041
+ timestamp: event.timestamp
1042
+ };
1043
+ }
1044
+ if (event.type === "interrupted") {
1045
+ return {
1046
+ type: "session.interrupted",
1047
+ sessionId,
1048
+ detail: event.detail,
1049
+ timestamp: event.timestamp
1050
+ };
1051
+ }
1052
+ return {
1053
+ type: "session.runtime_status",
1054
+ sessionId,
1055
+ status: event.status,
1056
+ detail: event.detail,
1057
+ timestamp: event.timestamp
1058
+ };
1059
+ }
1060
+ async maybeEmitRuntimeHistoryFallback(sessionId, event) {
1061
+ if (event.provider !== "claude-code") {
1062
+ return;
1063
+ }
1064
+ if (event.status === "starting") {
1065
+ return;
1066
+ }
1067
+ if (this.runtimeMessageSeenSessions.has(sessionId)) {
1068
+ return;
1069
+ }
1070
+ if (this.runtimeHistoryFallbackSentSessions.has(sessionId)) {
1071
+ return;
1072
+ }
1073
+ const envelope = await Promise.resolve(this.sessionHistoryService.readRecentHistoryEnvelope(sessionId)).catch(() => {
1074
+ return null;
1075
+ });
1076
+ if (!envelope) {
1077
+ return;
1078
+ }
1079
+ this.runtimeHistoryFallbackSentSessions.add(sessionId);
1080
+ await this.emitExternalRuntimeEnvelope(envelope);
1081
+ }
1082
+ ensureCapability(enabled, field, detail) {
1083
+ if (enabled) {
1084
+ return;
1085
+ }
1086
+ throw new AppError({
1087
+ statusCode: 400,
1088
+ errorCode: "CAPABILITY_NOT_SUPPORTED",
1089
+ detail,
1090
+ field
1091
+ });
1092
+ }
1093
+ upsertSnapshot(sessionId, input) {
1094
+ this.sessionStatusSnapshotRepository.upsert({
1095
+ sessionId,
1096
+ ...input,
1097
+ updatedAt: nowIso()
1098
+ });
1099
+ }
1100
+ shouldIgnoreClaudeExternalRuntimeUpdate(sessionId) {
1101
+ const runtimeSnapshot = this.providerRuntimeService.getSnapshot(sessionId);
1102
+ return Boolean(runtimeSnapshot &&
1103
+ runtimeSnapshot.provider === "claude-code" &&
1104
+ isActiveRuntimeState(runtimeSnapshot.runningState));
1105
+ }
1106
+ clearExternalRuntimeSnapshot(sessionId) {
1107
+ this.externalRuntimeSnapshots.delete(sessionId);
1108
+ }
1109
+ }
1110
+ function createSyntheticUserMessage(provider, providerSessionId, content, timestamp, sequence, attachments = []) {
1111
+ const syntheticId = createId();
1112
+ return {
1113
+ messageId: `synthetic-${syntheticId}`,
1114
+ provider: provider,
1115
+ providerSessionId,
1116
+ role: "user",
1117
+ kind: "text",
1118
+ content,
1119
+ toolCall: null,
1120
+ attachments,
1121
+ timestamp,
1122
+ sequence,
1123
+ rawRef: `synthetic://${provider}/${providerSessionId}/${syntheticId}`
1124
+ };
1125
+ }
1126
+ function withTimeout(promise, timeoutMs) {
1127
+ return new Promise((resolve, reject) => {
1128
+ const timer = setTimeout(() => {
1129
+ reject(new Error("TIMEOUT"));
1130
+ }, timeoutMs);
1131
+ void promise
1132
+ .then((value) => {
1133
+ clearTimeout(timer);
1134
+ resolve(value);
1135
+ })
1136
+ .catch((error) => {
1137
+ clearTimeout(timer);
1138
+ reject(error);
1139
+ });
1140
+ });
1141
+ }
1142
+ function toStoredRunningState(state) {
1143
+ return state;
1144
+ }
1145
+ function normalizeClaudeHookEventName(value) {
1146
+ const normalized = value?.trim();
1147
+ return normalized && normalized.length > 0 ? normalized : null;
1148
+ }
1149
+ function isSupportedClaudeHookEvent(value) {
1150
+ return (value === "UserPromptSubmit" ||
1151
+ value === "SessionStart" ||
1152
+ value === "Stop" ||
1153
+ value === "StopFailure" ||
1154
+ value === "SessionEnd");
1155
+ }
1156
+ function normalizeRequiredText(value, field) {
1157
+ const normalized = value?.trim();
1158
+ if (!normalized) {
1159
+ throw new AppError({
1160
+ statusCode: 400,
1161
+ errorCode: "INVALID_INPUT",
1162
+ detail: `${field} 不能为空`,
1163
+ field
1164
+ });
1165
+ }
1166
+ return normalized;
1167
+ }
1168
+ function normalizeOptionalText(value) {
1169
+ const normalized = value?.trim();
1170
+ return normalized && normalized.length > 0 ? normalized : null;
1171
+ }
1172
+ function mapClaudeHookToRuntimeUpdate(hookEventName, payload, timestamp) {
1173
+ if (hookEventName === "UserPromptSubmit") {
1174
+ return {
1175
+ runningState: "running",
1176
+ detail: "Claude Code 外部会话正在响应新的用户输入",
1177
+ timestamp
1178
+ };
1179
+ }
1180
+ if (hookEventName === "SessionStart") {
1181
+ return {
1182
+ runningState: "running",
1183
+ detail: "Claude Code 外部会话已启动",
1184
+ timestamp
1185
+ };
1186
+ }
1187
+ if (hookEventName === "StopFailure") {
1188
+ return {
1189
+ runningState: "failed",
1190
+ detail: "Claude Code 外部会话执行失败",
1191
+ timestamp
1192
+ };
1193
+ }
1194
+ if (hookEventName === "Stop") {
1195
+ if (payload.stop_hook_active) {
1196
+ return {
1197
+ runningState: "running",
1198
+ detail: "Claude Code 外部会话仍在继续执行",
1199
+ timestamp
1200
+ };
1201
+ }
1202
+ return {
1203
+ runningState: "completed",
1204
+ detail: "Claude Code 外部会话本轮输出已结束",
1205
+ timestamp
1206
+ };
1207
+ }
1208
+ if (hookEventName === "SessionEnd") {
1209
+ return {
1210
+ runningState: "completed",
1211
+ detail: payload.reason?.trim() ? `Claude Code 外部会话已结束:${payload.reason.trim()}` : "Claude Code 外部会话已结束",
1212
+ timestamp
1213
+ };
1214
+ }
1215
+ return null;
1216
+ }
1217
+ function buildSessionTitle(content) {
1218
+ const title = content.trim().replace(/\s+/g, " ");
1219
+ return title.slice(0, 48) || "继续对话";
1220
+ }
1221
+ function shouldStartNativeSessionOnFirstMessage(session) {
1222
+ if (session.provider !== "codex" && session.provider !== "opencode") {
1223
+ return "continue";
1224
+ }
1225
+ if (session.messageCount > 0) {
1226
+ return "continue";
1227
+ }
1228
+ return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(session.providerSessionId)
1229
+ ? "continue"
1230
+ : "start";
1231
+ }
1232
+ function isActiveRuntimeState(state) {
1233
+ return state === "starting" || state === "running";
1234
+ }
1235
+ function isTerminalRuntimeEventStatus(status) {
1236
+ return status === "completed" || status === "interrupted" || status === "failed";
1237
+ }
1238
+ function isPendingSessionRunningState(state) {
1239
+ return state === "starting" || state === "running";
1240
+ }
1241
+ function isQueueDispatchDeferredError(error) {
1242
+ if (error instanceof AppError) {
1243
+ return error.errorCode === "ACTIVE_RUN_EXISTS" || error.errorCode === "SESSION_NOT_RUNNING";
1244
+ }
1245
+ if (error instanceof Error) {
1246
+ return error.message === "ACTIVE_RUN_EXISTS";
1247
+ }
1248
+ return false;
1249
+ }
1250
+ function mapQueueItemRecordToView(record) {
1251
+ return {
1252
+ id: record.id,
1253
+ sessionId: record.sessionId,
1254
+ content: record.content,
1255
+ clientRequestId: record.clientRequestId,
1256
+ model: record.model,
1257
+ reasoningLevel: record.reasoningLevel,
1258
+ permissionMode: record.permissionMode,
1259
+ status: record.status,
1260
+ orderIndex: record.orderIndex,
1261
+ errorDetail: record.errorDetail,
1262
+ createdAt: record.createdAt,
1263
+ updatedAt: record.updatedAt
1264
+ };
1265
+ }
1266
+ function isTerminalSessionRunningState(state) {
1267
+ return state === "completed" || state === "interrupted" || state === "failed";
1268
+ }
1269
+ function createProviderRuntimeAdapters(config) {
1270
+ return [
1271
+ new ClaudeRuntimeAdapter({
1272
+ homeDir: config.claudeCodeHomeDir
1273
+ }),
1274
+ new CodexRuntimeAdapter(),
1275
+ new OpenCodeRuntimeAdapter({
1276
+ baseUrl: config.opencodeBaseUrl,
1277
+ baseUrlResolver: config.opencodeBaseUrlResolver?.resolve.bind(config.opencodeBaseUrlResolver)
1278
+ })
1279
+ ];
1280
+ }
1281
+ function buildClaudeRawStoreRef(homeDir, workspacePath, sessionId) {
1282
+ return path.join(homeDir, "projects", workspaceSlug(workspacePath), `${sessionId}.jsonl`);
1283
+ }
1284
+ function findClaudeSessionFile(homeDir, sessionId) {
1285
+ const projectsDir = path.join(homeDir, "projects");
1286
+ if (!existsSync(projectsDir)) {
1287
+ return null;
1288
+ }
1289
+ const candidates = readdirSync(projectsDir, { withFileTypes: true })
1290
+ .filter((entry) => entry.isDirectory())
1291
+ .map((entry) => path.join(projectsDir, entry.name, `${sessionId}.jsonl`))
1292
+ .filter((candidate) => existsSync(candidate));
1293
+ return candidates[0] ?? null;
1294
+ }
1295
+ function workspaceSlug(workspacePath) {
1296
+ const trimmed = workspacePath.replace(/[\\/]+$/, "");
1297
+ const normalizedDriveLetter = trimmed.replace(/^[A-Z](?=:)/, (value) => value.toLowerCase());
1298
+ return normalizedDriveLetter
1299
+ .replaceAll(":", "-")
1300
+ .replaceAll("\\", "-")
1301
+ .replaceAll("/", "-");
1302
+ }
1303
+ //# sourceMappingURL=session-live-runtime-service.js.map