@jingyi0605/codingns 0.2.0 → 0.3.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 (328) hide show
  1. package/README.md +44 -0
  2. package/bin/codingns.mjs +918 -55
  3. package/dist/public/assets/{TerminalPage-BlbQuWi1.js → TerminalPage-Dfw1QUqW.js} +19 -19
  4. package/dist/public/assets/index-DR2rPNi7.css +1 -0
  5. package/dist/public/assets/index-DTOruahn.js +114 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/config/env.d.ts +3 -0
  8. package/dist/server/config/env.js +35 -3
  9. package/dist/server/config/env.js.map +1 -1
  10. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +89 -0
  11. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +138 -0
  12. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -0
  13. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +115 -0
  14. package/dist/server/modules/assistant-capability/assistant-capability-service.js +241 -0
  15. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -0
  16. package/dist/server/modules/butler/butler-control-session-service.d.ts +3 -1
  17. package/dist/server/modules/butler/butler-control-session-service.js +96 -33
  18. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  19. package/dist/server/modules/butler/butler-follow-up-scheduler.d.ts +9 -0
  20. package/dist/server/modules/butler/butler-follow-up-scheduler.js +47 -11
  21. package/dist/server/modules/butler/butler-follow-up-scheduler.js.map +1 -1
  22. package/dist/server/modules/butler/butler-follow-up-service.d.ts +7 -1
  23. package/dist/server/modules/butler/butler-follow-up-service.js +10 -0
  24. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  25. package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
  26. package/dist/server/modules/butler/butler-session-service.js +82 -16
  27. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  28. package/dist/server/modules/butler/butler-session-summary-service.d.ts +8 -1
  29. package/dist/server/modules/butler/butler-session-summary-service.js +34 -7
  30. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  31. package/dist/server/modules/butler/context-aggregator.js +44 -13
  32. package/dist/server/modules/butler/context-aggregator.js.map +1 -1
  33. package/dist/server/modules/butler/patrol-scheduler.d.ts +9 -0
  34. package/dist/server/modules/butler/patrol-scheduler.js +63 -9
  35. package/dist/server/modules/butler/patrol-scheduler.js.map +1 -1
  36. package/dist/server/modules/butler/session-summary-scheduler.d.ts +9 -0
  37. package/dist/server/modules/butler/session-summary-scheduler.js +47 -11
  38. package/dist/server/modules/butler/session-summary-scheduler.js.map +1 -1
  39. package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.d.ts +38 -0
  40. package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.js +99 -0
  41. package/dist/server/modules/debug-target/debug-runtime-reconciliation-scheduler.js.map +1 -0
  42. package/dist/server/modules/debug-target/debug-target-controller.d.ts +70 -0
  43. package/dist/server/modules/debug-target/debug-target-controller.js +113 -0
  44. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -0
  45. package/dist/server/modules/debug-target/debug-target-service.d.ts +105 -0
  46. package/dist/server/modules/debug-target/debug-target-service.js +1644 -0
  47. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -0
  48. package/dist/server/modules/debug-target/framework-compatibility-matrix.d.ts +4 -0
  49. package/dist/server/modules/debug-target/framework-compatibility-matrix.js +45 -0
  50. package/dist/server/modules/debug-target/framework-compatibility-matrix.js.map +1 -0
  51. package/dist/server/modules/debug-target/launch-adapter-registry.d.ts +25 -0
  52. package/dist/server/modules/debug-target/launch-adapter-registry.js +445 -0
  53. package/dist/server/modules/debug-target/launch-adapter-registry.js.map +1 -0
  54. package/dist/server/modules/file/file-constants.d.ts +1 -0
  55. package/dist/server/modules/file/file-constants.js +1 -0
  56. package/dist/server/modules/file/file-constants.js.map +1 -1
  57. package/dist/server/modules/file/file-content-service.d.ts +2 -1
  58. package/dist/server/modules/file/file-content-service.js +53 -0
  59. package/dist/server/modules/file/file-content-service.js.map +1 -1
  60. package/dist/server/modules/file/file-controller.js +12 -3
  61. package/dist/server/modules/file/file-controller.js.map +1 -1
  62. package/dist/server/modules/file/file-preview-link-service.js +6 -37
  63. package/dist/server/modules/file/file-preview-link-service.js.map +1 -1
  64. package/dist/server/modules/file/file-preview-service.d.ts +6 -12
  65. package/dist/server/modules/file/file-preview-service.js +114 -28
  66. package/dist/server/modules/file/file-preview-service.js.map +1 -1
  67. package/dist/server/modules/file/file-preview-types.d.ts +37 -0
  68. package/dist/server/modules/file/file-preview-types.js +84 -0
  69. package/dist/server/modules/file/file-preview-types.js.map +1 -0
  70. package/dist/server/modules/git/commit-orchestrator.d.ts +4 -1
  71. package/dist/server/modules/git/commit-orchestrator.js +18 -1
  72. package/dist/server/modules/git/commit-orchestrator.js.map +1 -1
  73. package/dist/server/modules/git/git-auth.d.ts +25 -0
  74. package/dist/server/modules/git/git-auth.js +88 -0
  75. package/dist/server/modules/git/git-auth.js.map +1 -0
  76. package/dist/server/modules/git/git-controller.d.ts +12 -0
  77. package/dist/server/modules/git/git-controller.js +18 -1
  78. package/dist/server/modules/git/git-controller.js.map +1 -1
  79. package/dist/server/modules/git/git-read-service.d.ts +3 -1
  80. package/dist/server/modules/git/git-read-service.js +119 -2
  81. package/dist/server/modules/git/git-read-service.js.map +1 -1
  82. package/dist/server/modules/git/git-remote-credential-service.d.ts +9 -0
  83. package/dist/server/modules/git/git-remote-credential-service.js +76 -0
  84. package/dist/server/modules/git/git-remote-credential-service.js.map +1 -0
  85. package/dist/server/modules/git/git-write-service.d.ts +5 -2
  86. package/dist/server/modules/git/git-write-service.js +33 -17
  87. package/dist/server/modules/git/git-write-service.js.map +1 -1
  88. package/dist/server/modules/git/types.d.ts +26 -0
  89. package/dist/server/modules/git/workspace-repo-guard.js +3 -2
  90. package/dist/server/modules/git/workspace-repo-guard.js.map +1 -1
  91. package/dist/server/modules/provider/codex-model-options.d.ts +3 -1
  92. package/dist/server/modules/provider/codex-model-options.js +4 -1
  93. package/dist/server/modules/provider/codex-model-options.js.map +1 -1
  94. package/dist/server/modules/provider/opencode-model-options.d.ts +3 -1
  95. package/dist/server/modules/provider/opencode-model-options.js +5 -1
  96. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  97. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +24 -0
  98. package/dist/server/modules/provider/provider-discovery-helper-client.js +14 -0
  99. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  100. package/dist/server/modules/provider/provider-discovery-helper-process.js +54 -0
  101. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  102. package/dist/server/modules/sessions/session-activity-authority-service.js +13 -1
  103. package/dist/server/modules/sessions/session-activity-authority-service.js.map +1 -1
  104. package/dist/server/modules/sessions/session-activity-inspector.js +21 -5
  105. package/dist/server/modules/sessions/session-activity-inspector.js.map +1 -1
  106. package/dist/server/modules/sessions/session-controller.d.ts +5 -0
  107. package/dist/server/modules/sessions/session-controller.js +16 -0
  108. package/dist/server/modules/sessions/session-controller.js.map +1 -1
  109. package/dist/server/modules/sessions/session-history-service.d.ts +27 -7
  110. package/dist/server/modules/sessions/session-history-service.js +439 -81
  111. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  112. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +2 -1
  113. package/dist/server/modules/sessions/session-live-runtime-service.js +12 -0
  114. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  115. package/dist/server/modules/skills/skill-controller.d.ts +23 -0
  116. package/dist/server/modules/skills/skill-controller.js +35 -0
  117. package/dist/server/modules/skills/skill-controller.js.map +1 -0
  118. package/dist/server/modules/skills/skill-manager-service.d.ts +86 -0
  119. package/dist/server/modules/skills/skill-manager-service.js +557 -0
  120. package/dist/server/modules/skills/skill-manager-service.js.map +1 -0
  121. package/dist/server/modules/skills/skill-reconciler.d.ts +21 -0
  122. package/dist/server/modules/skills/skill-reconciler.js +99 -0
  123. package/dist/server/modules/skills/skill-reconciler.js.map +1 -0
  124. package/dist/server/modules/skills/skill-sync-planner.d.ts +8 -0
  125. package/dist/server/modules/skills/skill-sync-planner.js +20 -0
  126. package/dist/server/modules/skills/skill-sync-planner.js.map +1 -0
  127. package/dist/server/modules/skills/skill-target-adapter.d.ts +34 -0
  128. package/dist/server/modules/skills/skill-target-adapter.js +65 -0
  129. package/dist/server/modules/skills/skill-target-adapter.js.map +1 -0
  130. package/dist/server/modules/tailscale/tailscale-controller.d.ts +15 -0
  131. package/dist/server/modules/tailscale/tailscale-controller.js +33 -0
  132. package/dist/server/modules/tailscale/tailscale-controller.js.map +1 -0
  133. package/dist/server/modules/tailscale/tailscale-helper-client.d.ts +41 -0
  134. package/dist/server/modules/tailscale/tailscale-helper-client.js +135 -0
  135. package/dist/server/modules/tailscale/tailscale-helper-client.js.map +1 -0
  136. package/dist/server/modules/tailscale/tailscale-helper-process.d.ts +1 -0
  137. package/dist/server/modules/tailscale/tailscale-helper-process.js +327 -0
  138. package/dist/server/modules/tailscale/tailscale-helper-process.js.map +1 -0
  139. package/dist/server/modules/tailscale/tailscale-manager.d.ts +41 -0
  140. package/dist/server/modules/tailscale/tailscale-manager.js +259 -0
  141. package/dist/server/modules/tailscale/tailscale-manager.js.map +1 -0
  142. package/dist/server/modules/tailscale/tailscale-service.d.ts +43 -0
  143. package/dist/server/modules/tailscale/tailscale-service.js +201 -0
  144. package/dist/server/modules/tailscale/tailscale-service.js.map +1 -0
  145. package/dist/server/modules/tasks/event-loop-monitor.d.ts +21 -0
  146. package/dist/server/modules/tasks/event-loop-monitor.js +64 -0
  147. package/dist/server/modules/tasks/event-loop-monitor.js.map +1 -0
  148. package/dist/server/modules/tasks/observability-controller.d.ts +30 -0
  149. package/dist/server/modules/tasks/observability-controller.js +44 -0
  150. package/dist/server/modules/tasks/observability-controller.js.map +1 -0
  151. package/dist/server/modules/tasks/observability-service.d.ts +32 -0
  152. package/dist/server/modules/tasks/observability-service.js +104 -0
  153. package/dist/server/modules/tasks/observability-service.js.map +1 -0
  154. package/dist/server/modules/tasks/scheduler-metrics.d.ts +41 -0
  155. package/dist/server/modules/tasks/scheduler-metrics.js +92 -0
  156. package/dist/server/modules/tasks/scheduler-metrics.js.map +1 -0
  157. package/dist/server/modules/tasks/task-activity-log.d.ts +39 -0
  158. package/dist/server/modules/tasks/task-activity-log.js +43 -0
  159. package/dist/server/modules/tasks/task-activity-log.js.map +1 -0
  160. package/dist/server/modules/tasks/task-helper-client.d.ts +11 -0
  161. package/dist/server/modules/tasks/task-helper-client.js +132 -0
  162. package/dist/server/modules/tasks/task-helper-client.js.map +1 -0
  163. package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +16 -0
  164. package/dist/server/modules/tasks/task-helper-process-handlers.js +14 -0
  165. package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -0
  166. package/dist/server/modules/tasks/task-helper-process.d.ts +1 -0
  167. package/dist/server/modules/tasks/task-helper-process.js +49 -0
  168. package/dist/server/modules/tasks/task-helper-process.js.map +1 -0
  169. package/dist/server/modules/tasks/task-lane-executors.d.ts +2 -0
  170. package/dist/server/modules/tasks/task-lane-executors.js +15 -0
  171. package/dist/server/modules/tasks/task-lane-executors.js.map +1 -0
  172. package/dist/server/modules/tasks/task-manager.d.ts +15 -0
  173. package/dist/server/modules/tasks/task-manager.js +36 -0
  174. package/dist/server/modules/tasks/task-manager.js.map +1 -0
  175. package/dist/server/modules/tasks/task-metrics.d.ts +9 -0
  176. package/dist/server/modules/tasks/task-metrics.js +81 -0
  177. package/dist/server/modules/tasks/task-metrics.js.map +1 -0
  178. package/dist/server/modules/tasks/task-registry.d.ts +7 -0
  179. package/dist/server/modules/tasks/task-registry.js +21 -0
  180. package/dist/server/modules/tasks/task-registry.js.map +1 -0
  181. package/dist/server/modules/tasks/task-scheduler.d.ts +31 -0
  182. package/dist/server/modules/tasks/task-scheduler.js +473 -0
  183. package/dist/server/modules/tasks/task-scheduler.js.map +1 -0
  184. package/dist/server/modules/tasks/task-types.d.ts +106 -0
  185. package/dist/server/modules/tasks/task-types.js +23 -0
  186. package/dist/server/modules/tasks/task-types.js.map +1 -0
  187. package/dist/server/modules/terminal/command-template-service.d.ts +4 -0
  188. package/dist/server/modules/terminal/command-template-service.js +5 -3
  189. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  190. package/dist/server/modules/terminal/runtime/terminal-log-spooler.d.ts +7 -3
  191. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js +95 -15
  192. package/dist/server/modules/terminal/runtime/terminal-log-spooler.js.map +1 -1
  193. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.d.ts +21 -0
  194. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +144 -0
  195. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -0
  196. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.d.ts +1 -0
  197. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +187 -0
  198. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -0
  199. package/dist/server/modules/terminal/terminal-service.d.ts +12 -0
  200. package/dist/server/modules/terminal/terminal-service.js +34 -17
  201. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  202. package/dist/server/modules/workbench/workbench-service.d.ts +23 -2
  203. package/dist/server/modules/workbench/workbench-service.js +126 -15
  204. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  205. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +5 -1
  206. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +88 -19
  207. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  208. package/dist/server/modules/workspace/workspace-code-composition.d.ts +2 -0
  209. package/dist/server/modules/workspace/workspace-code-composition.js +154 -0
  210. package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -0
  211. package/dist/server/modules/workspace/workspace-controller.d.ts +14 -0
  212. package/dist/server/modules/workspace/workspace-controller.js +19 -0
  213. package/dist/server/modules/workspace/workspace-controller.js.map +1 -1
  214. package/dist/server/modules/workspace/workspace-service.d.ts +21 -14
  215. package/dist/server/modules/workspace/workspace-service.js +183 -234
  216. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  217. package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +35 -0
  218. package/dist/server/modules/worktree/worktree-cleanup-service.js +210 -0
  219. package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -0
  220. package/dist/server/modules/worktree/worktree-controller.d.ts +44 -0
  221. package/dist/server/modules/worktree/worktree-controller.js +40 -0
  222. package/dist/server/modules/worktree/worktree-controller.js.map +1 -0
  223. package/dist/server/modules/worktree/worktree-manager.d.ts +34 -0
  224. package/dist/server/modules/worktree/worktree-manager.js +292 -0
  225. package/dist/server/modules/worktree/worktree-manager.js.map +1 -0
  226. package/dist/server/modules/worktree/worktree-merge-service.d.ts +52 -0
  227. package/dist/server/modules/worktree/worktree-merge-service.js +293 -0
  228. package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -0
  229. package/dist/server/modules/worktree/worktree-sync-service.d.ts +23 -0
  230. package/dist/server/modules/worktree/worktree-sync-service.js +166 -0
  231. package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -0
  232. package/dist/server/routes/assistant.d.ts +3 -0
  233. package/dist/server/routes/assistant.js +15 -0
  234. package/dist/server/routes/assistant.js.map +1 -0
  235. package/dist/server/routes/debug-targets.d.ts +3 -0
  236. package/dist/server/routes/debug-targets.js +15 -0
  237. package/dist/server/routes/debug-targets.js.map +1 -0
  238. package/dist/server/routes/git.js +2 -0
  239. package/dist/server/routes/git.js.map +1 -1
  240. package/dist/server/routes/observability.d.ts +3 -0
  241. package/dist/server/routes/observability.js +7 -0
  242. package/dist/server/routes/observability.js.map +1 -0
  243. package/dist/server/routes/skills.d.ts +3 -0
  244. package/dist/server/routes/skills.js +7 -0
  245. package/dist/server/routes/skills.js.map +1 -0
  246. package/dist/server/routes/system.d.ts +3 -0
  247. package/dist/server/routes/system.js +9 -0
  248. package/dist/server/routes/system.js.map +1 -0
  249. package/dist/server/routes/workspaces.js +2 -0
  250. package/dist/server/routes/workspaces.js.map +1 -1
  251. package/dist/server/routes/worktrees.d.ts +3 -0
  252. package/dist/server/routes/worktrees.js +8 -0
  253. package/dist/server/routes/worktrees.js.map +1 -0
  254. package/dist/server/server/create-server.d.ts +46 -0
  255. package/dist/server/server/create-server.js +141 -12
  256. package/dist/server/server/create-server.js.map +1 -1
  257. package/dist/server/shared/utils/command-availability.d.ts +1 -0
  258. package/dist/server/shared/utils/command-availability.js +26 -3
  259. package/dist/server/shared/utils/command-availability.js.map +1 -1
  260. package/dist/server/shared/utils/secret-box.d.ts +2 -0
  261. package/dist/server/shared/utils/secret-box.js +29 -0
  262. package/dist/server/shared/utils/secret-box.js.map +1 -0
  263. package/dist/server/shared/utils/terminal-debug-log.js +5 -3
  264. package/dist/server/shared/utils/terminal-debug-log.js.map +1 -1
  265. package/dist/server/storage/repositories/ai-fallback-edit-repository.d.ts +11 -0
  266. package/dist/server/storage/repositories/ai-fallback-edit-repository.js +118 -0
  267. package/dist/server/storage/repositories/ai-fallback-edit-repository.js.map +1 -0
  268. package/dist/server/storage/repositories/debug-runtime-session-repository.d.ts +11 -0
  269. package/dist/server/storage/repositories/debug-runtime-session-repository.js +100 -0
  270. package/dist/server/storage/repositories/debug-runtime-session-repository.js.map +1 -0
  271. package/dist/server/storage/repositories/debug-service-repository.d.ts +9 -0
  272. package/dist/server/storage/repositories/debug-service-repository.js +99 -0
  273. package/dist/server/storage/repositories/debug-service-repository.js.map +1 -0
  274. package/dist/server/storage/repositories/debug-target-repository.d.ts +11 -0
  275. package/dist/server/storage/repositories/debug-target-repository.js +100 -0
  276. package/dist/server/storage/repositories/debug-target-repository.js.map +1 -0
  277. package/dist/server/storage/repositories/framework-analysis-result-repository.d.ts +9 -0
  278. package/dist/server/storage/repositories/framework-analysis-result-repository.js +98 -0
  279. package/dist/server/storage/repositories/framework-analysis-result-repository.js.map +1 -0
  280. package/dist/server/storage/repositories/git-remote-credential-repository.d.ts +9 -0
  281. package/dist/server/storage/repositories/git-remote-credential-repository.js +51 -0
  282. package/dist/server/storage/repositories/git-remote-credential-repository.js.map +1 -0
  283. package/dist/server/storage/repositories/instance-tailscale-repository.d.ts +10 -0
  284. package/dist/server/storage/repositories/instance-tailscale-repository.js +112 -0
  285. package/dist/server/storage/repositories/instance-tailscale-repository.js.map +1 -0
  286. package/dist/server/storage/repositories/managed-skill-repository.d.ts +11 -0
  287. package/dist/server/storage/repositories/managed-skill-repository.js +102 -0
  288. package/dist/server/storage/repositories/managed-skill-repository.js.map +1 -0
  289. package/dist/server/storage/repositories/port-lease-repository.d.ts +12 -0
  290. package/dist/server/storage/repositories/port-lease-repository.js +124 -0
  291. package/dist/server/storage/repositories/port-lease-repository.js.map +1 -0
  292. package/dist/server/storage/repositories/runtime-binding-repository.d.ts +10 -0
  293. package/dist/server/storage/repositories/runtime-binding-repository.js +89 -0
  294. package/dist/server/storage/repositories/runtime-binding-repository.js.map +1 -0
  295. package/dist/server/storage/repositories/skill-target-binding-repository.d.ts +10 -0
  296. package/dist/server/storage/repositories/skill-target-binding-repository.js +77 -0
  297. package/dist/server/storage/repositories/skill-target-binding-repository.js.map +1 -0
  298. package/dist/server/storage/repositories/terminal-command-template-repository.js +77 -4
  299. package/dist/server/storage/repositories/terminal-command-template-repository.js.map +1 -1
  300. package/dist/server/storage/repositories/terminal-instance-repository.js +89 -7
  301. package/dist/server/storage/repositories/terminal-instance-repository.js.map +1 -1
  302. package/dist/server/storage/repositories/workspace-navigation-state-repository.d.ts +9 -0
  303. package/dist/server/storage/repositories/workspace-navigation-state-repository.js +49 -0
  304. package/dist/server/storage/repositories/workspace-navigation-state-repository.js.map +1 -0
  305. package/dist/server/storage/repositories/workspace-repository.d.ts +7 -1
  306. package/dist/server/storage/repositories/workspace-repository.js +32 -8
  307. package/dist/server/storage/repositories/workspace-repository.js.map +1 -1
  308. package/dist/server/storage/repositories/workspace-worktree-repository.d.ts +13 -0
  309. package/dist/server/storage/repositories/workspace-worktree-repository.js +158 -0
  310. package/dist/server/storage/repositories/workspace-worktree-repository.js.map +1 -0
  311. package/dist/server/storage/sqlite/client.js +311 -0
  312. package/dist/server/storage/sqlite/client.js.map +1 -1
  313. package/dist/server/storage/sqlite/schema.sql +303 -0
  314. package/dist/server/types/domain.d.ts +303 -0
  315. package/dist/server/ws/workbench-ws-hub.js +33 -9
  316. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  317. package/dist/server/ws/ws-server.js +4 -4
  318. package/dist/server/ws/ws-server.js.map +1 -1
  319. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +18 -6
  320. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  321. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +14 -2
  322. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  323. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +25 -1
  324. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  325. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +6 -0
  326. package/package.json +1 -1
  327. package/dist/public/assets/index-1VIm8lVL.css +0 -1
  328. package/dist/public/assets/index-Dti93O2S.js +0 -109
@@ -0,0 +1,1644 @@
1
+ import fs from "node:fs";
2
+ import net from "node:net";
3
+ import path from "node:path";
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 { buildTemplateCommandLine, getShellEnterSequence } from "../terminal/terminal-shell.js";
8
+ import { createTaskManager } from "../tasks/task-manager.js";
9
+ import { HOST_TASK_TYPES } from "../tasks/task-types.js";
10
+ import { FRAMEWORK_COMPATIBILITY_MATRIX, FRAMEWORK_COMPATIBILITY_MATRIX_VERSION, getFrameworkCompatibilityItem } from "./framework-compatibility-matrix.js";
11
+ import { resolveLaunchPlan } from "./launch-adapter-registry.js";
12
+ export class DebugTargetService {
13
+ db;
14
+ workspaceService;
15
+ workspaceWorktreeRepository;
16
+ debugTargetRepository;
17
+ debugServiceRepository;
18
+ frameworkAnalysisResultRepository;
19
+ debugRuntimeSessionRepository;
20
+ portLeaseRepository;
21
+ runtimeBindingRepository;
22
+ aiFallbackEditRepository;
23
+ terminalCommandTemplateRepository;
24
+ terminalService;
25
+ terminalInstanceRepository;
26
+ taskManager;
27
+ constructor(db, workspaceService, workspaceWorktreeRepository, debugTargetRepository, debugServiceRepository, frameworkAnalysisResultRepository, debugRuntimeSessionRepository, portLeaseRepository, runtimeBindingRepository, aiFallbackEditRepository, terminalCommandTemplateRepository, terminalService, terminalInstanceRepository, taskManager = createTaskManager()) {
28
+ this.db = db;
29
+ this.workspaceService = workspaceService;
30
+ this.workspaceWorktreeRepository = workspaceWorktreeRepository;
31
+ this.debugTargetRepository = debugTargetRepository;
32
+ this.debugServiceRepository = debugServiceRepository;
33
+ this.frameworkAnalysisResultRepository = frameworkAnalysisResultRepository;
34
+ this.debugRuntimeSessionRepository = debugRuntimeSessionRepository;
35
+ this.portLeaseRepository = portLeaseRepository;
36
+ this.runtimeBindingRepository = runtimeBindingRepository;
37
+ this.aiFallbackEditRepository = aiFallbackEditRepository;
38
+ this.terminalCommandTemplateRepository = terminalCommandTemplateRepository;
39
+ this.terminalService = terminalService;
40
+ this.terminalInstanceRepository = terminalInstanceRepository;
41
+ this.taskManager = taskManager;
42
+ this.registerBackgroundTasks();
43
+ }
44
+ analyze(input) {
45
+ const workspace = this.workspaceService.getWorkspaceOrThrow(input.workspaceId);
46
+ const rootPath = this.resolveRootPath(workspace.path, input.rootPath);
47
+ const sourceMeta = this.resolveSourceMeta(workspace.id);
48
+ const timestamp = nowIso();
49
+ const commandHints = resolveAnalyzeCommandHints({
50
+ workspaceId: workspace.id,
51
+ rootPath,
52
+ explicitCommandHints: input.commandHints,
53
+ terminalCommandTemplateRepository: this.terminalCommandTemplateRepository
54
+ });
55
+ const discoveredServices = discoverServiceCandidates(rootPath, commandHints);
56
+ const analyzedServices = discoveredServices.map((candidate) => ({
57
+ candidate,
58
+ framework: pickFramework(collectFrameworkEvidence(candidate.cwd))
59
+ }));
60
+ const targetStackHint = analyzedServices[0]?.framework.primaryFramework ?? null;
61
+ const target = this.upsertTarget({
62
+ workspaceId: workspace.id,
63
+ rootPath,
64
+ displayName: path.basename(rootPath) || workspace.name,
65
+ stackHint: targetStackHint,
66
+ sourceType: sourceMeta.sourceType,
67
+ rootWorkspaceId: sourceMeta.rootWorkspaceId,
68
+ timestamp
69
+ });
70
+ const services = [];
71
+ const analyses = [];
72
+ for (const item of analyzedServices) {
73
+ const analysisId = createId();
74
+ const service = buildServiceRecord(target, item.candidate, item.framework, timestamp, analysisId);
75
+ const matrixItem = getFrameworkCompatibilityItem(item.framework.primaryFramework);
76
+ const analysis = {
77
+ id: analysisId,
78
+ targetId: target.id,
79
+ serviceId: service.id,
80
+ primaryFramework: item.framework.primaryFramework,
81
+ confidence: item.framework.confidence,
82
+ compatibilityLevel: matrixItem.compatibilityLevel,
83
+ recommendedInjectionMode: matrixItem.recommendedInjectionMode,
84
+ requiresServiceDiscoveryHandling: matrixItem.requiresServiceDiscoveryHandling,
85
+ requiresHmrHandling: matrixItem.requiresHmrHandling,
86
+ requiresCallbackHandling: matrixItem.requiresCallbackHandling,
87
+ aiFallbackPolicy: matrixItem.aiFallbackPolicy,
88
+ reasons: item.framework.reasons,
89
+ detectedFiles: item.framework.detectedFiles,
90
+ rawEvidence: item.framework.rawEvidence,
91
+ createdAt: timestamp
92
+ };
93
+ services.push(service);
94
+ analyses.push(analysis);
95
+ }
96
+ const persist = this.db.transaction(() => {
97
+ this.debugServiceRepository.deleteByTargetId(target.id);
98
+ this.frameworkAnalysisResultRepository.deleteByTargetId(target.id);
99
+ for (const service of services) {
100
+ this.debugServiceRepository.create(service);
101
+ }
102
+ for (const analysis of analyses) {
103
+ this.frameworkAnalysisResultRepository.create(analysis);
104
+ }
105
+ });
106
+ persist();
107
+ return {
108
+ target,
109
+ services,
110
+ analyses,
111
+ autoInjectionEligible: analyses.every((analysis) => analysis.compatibilityLevel === "supported" || analysis.compatibilityLevel === "conditional")
112
+ };
113
+ }
114
+ getFrameworkAnalysis(targetId) {
115
+ this.getTargetOrThrow(targetId);
116
+ return {
117
+ targetId,
118
+ items: this.frameworkAnalysisResultRepository.listByTargetId(targetId)
119
+ };
120
+ }
121
+ refreshFrameworkAnalysis(targetId) {
122
+ const target = this.getTargetOrThrow(targetId);
123
+ const services = this.debugServiceRepository.listByTargetId(targetId);
124
+ const commandHints = services.map((service) => [service.command, ...service.args].join(" "));
125
+ return this.analyze({
126
+ workspaceId: target.workspaceId,
127
+ rootPath: target.rootPath,
128
+ commandHints
129
+ });
130
+ }
131
+ getCompatibilityMatrix() {
132
+ return {
133
+ version: FRAMEWORK_COMPATIBILITY_MATRIX_VERSION,
134
+ items: FRAMEWORK_COMPATIBILITY_MATRIX
135
+ };
136
+ }
137
+ async createLaunchPlan(targetId) {
138
+ const target = this.getTargetOrThrow(targetId);
139
+ const services = this.debugServiceRepository.listByTargetId(targetId);
140
+ const analyses = this.frameworkAnalysisResultRepository.listByTargetId(targetId);
141
+ if (services.length === 0 || analyses.length === 0) {
142
+ throw new AppError({
143
+ statusCode: 409,
144
+ errorCode: "DEBUG_TARGET_ANALYSIS_REQUIRED",
145
+ detail: "当前调试目标还没有可用的框架分析结果,请先执行分析"
146
+ });
147
+ }
148
+ const analysisByServiceId = new Map(analyses
149
+ .filter((analysis) => analysis.serviceId)
150
+ .map((analysis) => [analysis.serviceId, analysis]));
151
+ const timestamp = nowIso();
152
+ const runtimeSession = this.debugRuntimeSessionRepository.create({
153
+ id: createId(),
154
+ targetId: target.id,
155
+ status: "PREPARING",
156
+ failureStage: null,
157
+ startedAt: null,
158
+ stoppedAt: null,
159
+ createdAt: timestamp,
160
+ updatedAt: timestamp
161
+ });
162
+ const planItems = [];
163
+ for (const service of services) {
164
+ const analysis = analysisByServiceId.get(service.id) ?? analyses[0] ?? null;
165
+ if (!analysis) {
166
+ throw new AppError({
167
+ statusCode: 409,
168
+ errorCode: "DEBUG_TARGET_ANALYSIS_REQUIRED",
169
+ detail: "调试服务缺少框架分析结果,无法生成启动计划"
170
+ });
171
+ }
172
+ const allocatedPort = await allocateManagedPort(service.role, this.portLeaseRepository);
173
+ const injectionPlan = resolveLaunchPlan({
174
+ targetRootPath: target.rootPath,
175
+ service,
176
+ analysis,
177
+ leasedPort: allocatedPort
178
+ });
179
+ const missingRequirements = resolveMissingRequirements(analysis);
180
+ let aiFallbackEditRecord = null;
181
+ const autoStartAllowed = isFrameworkEligible(analysis.compatibilityLevel)
182
+ && missingRequirements.length === 0
183
+ && injectionPlan.adapterKind !== "ai_fallback"
184
+ && injectionPlan.adapterKind !== null;
185
+ const portLeaseId = injectionPlan.leasedPort === null ? null : createId();
186
+ const runtimeBindingId = createId();
187
+ if (injectionPlan.aiFallback?.eligible) {
188
+ aiFallbackEditRecord = this.aiFallbackEditRepository.create({
189
+ id: createId(),
190
+ runtimeId: runtimeSession.id,
191
+ serviceId: service.id,
192
+ reason: injectionPlan.aiFallback.reason,
193
+ allowedFiles: injectionPlan.aiFallback.allowedFiles,
194
+ targetPort: injectionPlan.leasedPort ?? allocatedPort,
195
+ patchRef: null,
196
+ rollbackRef: null,
197
+ status: "PENDING",
198
+ createdAt: timestamp
199
+ });
200
+ }
201
+ if (portLeaseId && injectionPlan.leasedPort !== null) {
202
+ this.portLeaseRepository.create({
203
+ id: portLeaseId,
204
+ runtimeId: runtimeSession.id,
205
+ serviceId: service.id,
206
+ port: injectionPlan.leasedPort,
207
+ protocol: "tcp",
208
+ status: "LEASED",
209
+ leasedAt: timestamp,
210
+ expiresAt: null,
211
+ releasedAt: null
212
+ });
213
+ }
214
+ this.runtimeBindingRepository.create({
215
+ id: runtimeBindingId,
216
+ runtimeId: runtimeSession.id,
217
+ serviceId: service.id,
218
+ processInstanceId: null,
219
+ expectedPort: injectionPlan.expectedPort,
220
+ leasedPort: injectionPlan.leasedPort,
221
+ observedPort: null,
222
+ proxyPath: null,
223
+ status: "ALLOCATED",
224
+ updatedAt: timestamp
225
+ });
226
+ planItems.push({
227
+ serviceId: service.id,
228
+ role: service.role,
229
+ frameworkAnalysisId: analysis.id,
230
+ primaryFramework: analysis.primaryFramework ?? null,
231
+ compatibilityLevel: analysis.compatibilityLevel,
232
+ adapterKind: injectionPlan.adapterKind ?? null,
233
+ injectionMode: injectionPlan.injectionMode ?? null,
234
+ command: service.command,
235
+ args: injectionPlan.args,
236
+ envPatch: injectionPlan.envPatch,
237
+ expectedPort: injectionPlan.expectedPort,
238
+ leasedPort: injectionPlan.leasedPort,
239
+ artifactRef: injectionPlan.artifactRef,
240
+ runtimeBindingId,
241
+ portLeaseId,
242
+ requiresServiceDiscoveryHandling: analysis.requiresServiceDiscoveryHandling,
243
+ requiresHmrHandling: analysis.requiresHmrHandling,
244
+ requiresCallbackHandling: analysis.requiresCallbackHandling,
245
+ failureStage: injectionPlan.failureStage,
246
+ adapterAttempts: injectionPlan.adapterAttempts,
247
+ aiFallback: injectionPlan.aiFallback
248
+ ? {
249
+ ...injectionPlan.aiFallback,
250
+ editId: aiFallbackEditRecord?.id ?? null,
251
+ status: aiFallbackEditRecord?.status ?? injectionPlan.aiFallback.status
252
+ }
253
+ : null,
254
+ missingRequirements,
255
+ autoStartAllowed
256
+ });
257
+ }
258
+ return {
259
+ runtimeSession,
260
+ targetId: target.id,
261
+ autoStartAllowed: planItems.every((item) => item.autoStartAllowed),
262
+ services: planItems
263
+ };
264
+ }
265
+ async run(input) {
266
+ const launchPlan = await this.createLaunchPlan(input.targetId);
267
+ if (!launchPlan.autoStartAllowed) {
268
+ const aiFallbackItem = launchPlan.services.find((item) => item.aiFallback?.eligible);
269
+ if (aiFallbackItem) {
270
+ await this.failRuntimePlan(launchPlan.runtimeSession.id, aiFallbackItem.failureStage ?? "ai_fallback_required", "当前服务需要进入受限 AI 兜底流程,自动运行已阻止", 409, "DEBUG_TARGET_AI_FALLBACK_REQUIRED");
271
+ }
272
+ await this.failRuntimePlan(launchPlan.runtimeSession.id, launchPlan.services.find((item) => item.failureStage)?.failureStage ?? "launch_requirements", "当前启动计划缺少必要的服务发现、HMR 或 callback 处理,暂不允许自动启动", 409, "DEBUG_TARGET_RUN_NOT_ALLOWED");
273
+ }
274
+ const startedServices = [];
275
+ try {
276
+ for (const item of launchPlan.services) {
277
+ const service = this.getServiceOrThrow(item.serviceId, input.targetId);
278
+ const analysis = this.frameworkAnalysisResultRepository
279
+ .listByTargetId(input.targetId)
280
+ .find((candidate) => candidate.id === item.frameworkAnalysisId) ?? null;
281
+ const terminal = await this.terminalService.createTerminal({
282
+ workspaceId: this.getTargetOrThrow(input.targetId).workspaceId,
283
+ name: `${service.name} 运行`,
284
+ cwd: service.cwd,
285
+ shell: input.shell,
286
+ runtimeType: input.runtimeType,
287
+ createdByUserId: input.userId,
288
+ env: {
289
+ ...service.env,
290
+ ...item.envPatch
291
+ },
292
+ debugRuntimeSessionId: launchPlan.runtimeSession.id,
293
+ debugTargetId: input.targetId,
294
+ debugServiceId: service.id,
295
+ frameworkAnalysisId: analysis?.id ?? null,
296
+ launcherSourceType: "debug_service",
297
+ launchStage: "command_dispatched",
298
+ failureStage: null,
299
+ adapterKind: item.adapterKind ?? null,
300
+ envPatchSummary: item.envPatch,
301
+ artifactRef: item.artifactRef
302
+ });
303
+ const commandLine = buildTemplateCommandLine(buildEphemeralTemplate(service, item, input.runtimeType ?? terminal.runtimeType), terminal.shell);
304
+ await this.terminalService.writeInput(terminal.id, `${commandLine}${getShellEnterSequence(terminal.shell)}`);
305
+ this.runtimeBindingRepository.update({
306
+ id: item.runtimeBindingId,
307
+ runtimeId: launchPlan.runtimeSession.id,
308
+ serviceId: service.id,
309
+ processInstanceId: terminal.id,
310
+ expectedPort: item.expectedPort,
311
+ leasedPort: item.leasedPort,
312
+ observedPort: null,
313
+ proxyPath: null,
314
+ status: "ALLOCATED",
315
+ updatedAt: nowIso()
316
+ });
317
+ startedServices.push({
318
+ serviceId: service.id,
319
+ processInstanceId: terminal.id,
320
+ terminalId: terminal.id,
321
+ leasedPort: item.leasedPort,
322
+ runtimeBindingId: item.runtimeBindingId
323
+ });
324
+ }
325
+ }
326
+ catch (error) {
327
+ for (const service of startedServices) {
328
+ try {
329
+ await this.terminalService.closeTerminal(service.terminalId);
330
+ }
331
+ catch {
332
+ // 失败回滚阶段只做尽力而为,避免覆盖原始错误。
333
+ }
334
+ }
335
+ await this.failRuntimePlan(launchPlan.runtimeSession.id, "command_execution", error instanceof Error ? error.message : "调试目标启动失败", 500, "DEBUG_TARGET_RUN_FAILED");
336
+ }
337
+ const updatedRuntimeSession = this.debugRuntimeSessionRepository.update({
338
+ ...launchPlan.runtimeSession,
339
+ status: "RUNNING",
340
+ failureStage: null,
341
+ startedAt: nowIso(),
342
+ updatedAt: nowIso()
343
+ });
344
+ return {
345
+ runtimeSession: updatedRuntimeSession ?? launchPlan.runtimeSession,
346
+ services: startedServices
347
+ };
348
+ }
349
+ async handleTerminalExit(event) {
350
+ const runtimeId = event.terminal.debugRuntimeSessionId ?? null;
351
+ if (!runtimeId) {
352
+ return;
353
+ }
354
+ await this.reconcileRuntimeState(runtimeId, {
355
+ preferredFailureStage: event.terminal.status === "error" ? "process_exit" : null,
356
+ staleMissingBindingAsFailure: false
357
+ });
358
+ }
359
+ async getRuntimeDetail(runtimeId) {
360
+ const runtimeSession = this.debugRuntimeSessionRepository.findById(runtimeId);
361
+ if (!runtimeSession) {
362
+ throw new AppError({
363
+ statusCode: 404,
364
+ errorCode: "DEBUG_RUNTIME_NOT_FOUND",
365
+ detail: "调试运行时不存在"
366
+ });
367
+ }
368
+ const reconciled = await this.reconcileRuntimeState(runtimeId, {
369
+ preferredFailureStage: null,
370
+ staleMissingBindingAsFailure: true
371
+ });
372
+ const target = this.getTargetOrThrow(reconciled.targetId);
373
+ const services = this.debugServiceRepository.listByTargetId(target.id);
374
+ const analyses = this.frameworkAnalysisResultRepository.listByTargetId(target.id);
375
+ const bindings = this.runtimeBindingRepository.listByRuntimeId(runtimeId);
376
+ const leases = this.portLeaseRepository.listByRuntimeId(runtimeId);
377
+ const aiFallbackEdits = this.aiFallbackEditRepository.listByRuntimeId(runtimeId);
378
+ return {
379
+ runtimeSession: reconciled,
380
+ target,
381
+ services: services.map((service) => ({
382
+ service,
383
+ analysis: analyses.find((analysis) => analysis.serviceId === service.id) ?? null,
384
+ binding: bindings.find((binding) => binding.serviceId === service.id) ?? null,
385
+ portLease: leases.find((lease) => lease.serviceId === service.id) ?? null,
386
+ processInstance: resolveProcessInstance(bindings.find((binding) => binding.serviceId === service.id)?.processInstanceId ?? null, this.terminalInstanceRepository),
387
+ aiFallbackEdits: aiFallbackEdits.filter((edit) => edit.serviceId === service.id)
388
+ }))
389
+ };
390
+ }
391
+ async getLatestRuntimeDetail(targetId) {
392
+ this.getTargetOrThrow(targetId);
393
+ const latestRuntime = this.debugRuntimeSessionRepository.listByTargetId(targetId)[0] ?? null;
394
+ if (!latestRuntime) {
395
+ return null;
396
+ }
397
+ return await this.getRuntimeDetail(latestRuntime.id);
398
+ }
399
+ async getRecentRuntimeDetails(targetId, limit = 5) {
400
+ this.getTargetOrThrow(targetId);
401
+ const normalizedLimit = Number.isFinite(limit) ? Math.max(1, Math.min(10, Math.trunc(limit))) : 5;
402
+ const runtimes = this.debugRuntimeSessionRepository.listByTargetId(targetId).slice(0, normalizedLimit);
403
+ const items = [];
404
+ for (const runtime of runtimes) {
405
+ items.push(await this.getRuntimeDetail(runtime.id));
406
+ }
407
+ return {
408
+ targetId,
409
+ items
410
+ };
411
+ }
412
+ async runBackgroundRuntimeReconciliation(source = "debug_target.runtime_stale_reconciliation") {
413
+ const task = this.taskManager.enqueue(HOST_TASK_TYPES.debugRuntimeStaleReconciliation, {
414
+ key: "global",
415
+ source,
416
+ input: {
417
+ trigger: source.includes("scheduler") ? "scheduler" : "manual"
418
+ }
419
+ });
420
+ return await task.promise;
421
+ }
422
+ updateAiFallbackEdit(editId, action, refs = {}) {
423
+ const existing = this.aiFallbackEditRepository.findById(editId);
424
+ if (!existing) {
425
+ throw new AppError({
426
+ statusCode: 404,
427
+ errorCode: "AI_FALLBACK_EDIT_NOT_FOUND",
428
+ detail: "AI 兜底记录不存在"
429
+ });
430
+ }
431
+ const nextStatus = resolveAiFallbackNextStatus(action);
432
+ const updated = this.aiFallbackEditRepository.update({
433
+ ...existing,
434
+ patchRef: refs.patchRef ?? existing.patchRef ?? null,
435
+ rollbackRef: refs.rollbackRef ?? existing.rollbackRef ?? null,
436
+ status: nextStatus
437
+ });
438
+ return updated ?? existing;
439
+ }
440
+ getTargetOrThrow(targetId) {
441
+ const target = this.debugTargetRepository.findById(targetId);
442
+ if (!target) {
443
+ throw new AppError({
444
+ statusCode: 404,
445
+ errorCode: "DEBUG_TARGET_NOT_FOUND",
446
+ detail: "调试目标不存在"
447
+ });
448
+ }
449
+ return target;
450
+ }
451
+ getServiceOrThrow(serviceId, targetId) {
452
+ const service = this.debugServiceRepository
453
+ .listByTargetId(targetId)
454
+ .find((item) => item.id === serviceId);
455
+ if (!service) {
456
+ throw new AppError({
457
+ statusCode: 404,
458
+ errorCode: "DEBUG_SERVICE_NOT_FOUND",
459
+ detail: "调试服务不存在"
460
+ });
461
+ }
462
+ return service;
463
+ }
464
+ upsertTarget(input) {
465
+ const existing = this.debugTargetRepository.findByWorkspaceAndRootPath(input.workspaceId, input.rootPath);
466
+ if (existing) {
467
+ return this.debugTargetRepository.update({
468
+ ...existing,
469
+ displayName: input.displayName,
470
+ stackHint: input.stackHint,
471
+ sourceType: input.sourceType,
472
+ rootWorkspaceId: input.rootWorkspaceId,
473
+ updatedAt: input.timestamp
474
+ }) ?? existing;
475
+ }
476
+ return this.debugTargetRepository.create({
477
+ id: createId(),
478
+ workspaceId: input.workspaceId,
479
+ rootPath: input.rootPath,
480
+ displayName: input.displayName,
481
+ stackHint: input.stackHint,
482
+ sourceType: input.sourceType,
483
+ rootWorkspaceId: input.rootWorkspaceId,
484
+ createdAt: input.timestamp,
485
+ updatedAt: input.timestamp
486
+ });
487
+ }
488
+ resolveSourceMeta(workspaceId) {
489
+ const worktree = this.workspaceWorktreeRepository.findByWorkspaceId(workspaceId);
490
+ if (!worktree) {
491
+ return {
492
+ sourceType: "repo",
493
+ rootWorkspaceId: null
494
+ };
495
+ }
496
+ return {
497
+ sourceType: "worktree",
498
+ rootWorkspaceId: worktree.rootWorkspaceId
499
+ };
500
+ }
501
+ resolveRootPath(workspacePath, rootPath) {
502
+ const normalized = rootPath.trim();
503
+ if (!normalized) {
504
+ throw new AppError({
505
+ statusCode: 400,
506
+ errorCode: "INVALID_INPUT",
507
+ detail: "分析调试目标必须提供 rootPath",
508
+ field: "rootPath"
509
+ });
510
+ }
511
+ const resolved = path.resolve(normalized);
512
+ if (!fs.existsSync(resolved) || !fs.statSync(resolved).isDirectory()) {
513
+ throw new AppError({
514
+ statusCode: 400,
515
+ errorCode: "INVALID_INPUT",
516
+ detail: "rootPath 必须是现有目录",
517
+ field: "rootPath"
518
+ });
519
+ }
520
+ const workspaceRoot = withTrailingSeparator(path.resolve(workspacePath));
521
+ const candidateRoot = withTrailingSeparator(resolved);
522
+ if (candidateRoot !== workspaceRoot && !candidateRoot.startsWith(workspaceRoot)) {
523
+ throw new AppError({
524
+ statusCode: 400,
525
+ errorCode: "INVALID_INPUT",
526
+ detail: "rootPath 必须位于当前工作区路径内",
527
+ field: "rootPath"
528
+ });
529
+ }
530
+ return resolved;
531
+ }
532
+ registerBackgroundTasks() {
533
+ if (!this.taskManager.has(HOST_TASK_TYPES.debugRuntimeStaleReconciliation)) {
534
+ this.taskManager.register({
535
+ taskType: HOST_TASK_TYPES.debugRuntimeStaleReconciliation,
536
+ executionLane: "host_background",
537
+ timeoutMs: 10_000,
538
+ concurrency: 1,
539
+ run: async () => this.reconcileActiveRuntimeLeases()
540
+ });
541
+ }
542
+ }
543
+ async failRuntimePlan(runtimeId, failureStage, detail, statusCode, errorCode) {
544
+ const current = this.debugRuntimeSessionRepository.findById(runtimeId);
545
+ const now = nowIso();
546
+ if (current) {
547
+ this.debugRuntimeSessionRepository.update({
548
+ ...current,
549
+ status: "FAILED",
550
+ failureStage,
551
+ stoppedAt: now,
552
+ updatedAt: now
553
+ });
554
+ }
555
+ for (const lease of this.portLeaseRepository.listByRuntimeId(runtimeId)) {
556
+ this.portLeaseRepository.update({
557
+ ...lease,
558
+ status: "RELEASED",
559
+ releasedAt: now
560
+ });
561
+ }
562
+ for (const binding of this.runtimeBindingRepository.listByRuntimeId(runtimeId)) {
563
+ this.runtimeBindingRepository.update({
564
+ ...binding,
565
+ status: "FAILED",
566
+ updatedAt: now
567
+ });
568
+ }
569
+ throw new AppError({
570
+ statusCode,
571
+ errorCode,
572
+ detail
573
+ });
574
+ }
575
+ async reconcileActiveRuntimeLeases() {
576
+ const runtimeIds = this.collectReconciliationRuntimeIds();
577
+ let reconciledRuntimeCount = 0;
578
+ let staleLeaseCount = 0;
579
+ let releasedLeaseCount = 0;
580
+ let failedRuntimeCount = 0;
581
+ let stoppedRuntimeCount = 0;
582
+ for (const runtimeId of runtimeIds) {
583
+ const runtimeBefore = this.debugRuntimeSessionRepository.findById(runtimeId);
584
+ if (!runtimeBefore) {
585
+ continue;
586
+ }
587
+ const staleLeaseIdsBefore = new Set(this.portLeaseRepository
588
+ .listByRuntimeId(runtimeId)
589
+ .filter((lease) => lease.status === "STALE")
590
+ .map((lease) => lease.id));
591
+ const runtimeAfter = await this.reconcileRuntimeState(runtimeId, {
592
+ preferredFailureStage: null,
593
+ staleMissingBindingAsFailure: true
594
+ });
595
+ const staleLeases = this.portLeaseRepository
596
+ .listByRuntimeId(runtimeId)
597
+ .filter((lease) => lease.status === "STALE");
598
+ staleLeaseCount += staleLeases.filter((lease) => !staleLeaseIdsBefore.has(lease.id)).length;
599
+ releasedLeaseCount += this.releaseStaleLeases(staleLeases);
600
+ reconciledRuntimeCount += 1;
601
+ if (runtimeAfter.status === "FAILED") {
602
+ failedRuntimeCount += 1;
603
+ }
604
+ else if (runtimeAfter.status === "STOPPED") {
605
+ stoppedRuntimeCount += 1;
606
+ }
607
+ }
608
+ return {
609
+ scannedRuntimeCount: runtimeIds.length,
610
+ reconciledRuntimeCount,
611
+ staleLeaseCount,
612
+ releasedLeaseCount,
613
+ failedRuntimeCount,
614
+ stoppedRuntimeCount,
615
+ idle: runtimeIds.length === 0
616
+ };
617
+ }
618
+ async reconcileRuntimeState(runtimeId, options) {
619
+ const runtime = this.debugRuntimeSessionRepository.findById(runtimeId);
620
+ if (!runtime) {
621
+ throw new AppError({
622
+ statusCode: 404,
623
+ errorCode: "DEBUG_RUNTIME_NOT_FOUND",
624
+ detail: "调试运行时不存在"
625
+ });
626
+ }
627
+ const bindings = this.runtimeBindingRepository.listByRuntimeId(runtimeId);
628
+ const leases = this.portLeaseRepository.listByRuntimeId(runtimeId);
629
+ if (bindings.length === 0) {
630
+ return runtime;
631
+ }
632
+ let nextStatus = runtime.status;
633
+ let nextFailureStage = runtime.failureStage ?? null;
634
+ let nextStoppedAt = runtime.stoppedAt ?? null;
635
+ const now = nowIso();
636
+ let hasRunningProcess = false;
637
+ let hasErroredProcess = false;
638
+ let hasClosedProcess = false;
639
+ let hasMissingProcess = false;
640
+ for (const binding of bindings) {
641
+ if (!binding.processInstanceId && runtime.status === "PREPARING") {
642
+ continue;
643
+ }
644
+ const processInstance = resolveProcessInstance(binding.processInstanceId ?? null, this.terminalInstanceRepository);
645
+ const lease = leases.find((item) => item.serviceId === binding.serviceId) ?? null;
646
+ if (!processInstance) {
647
+ hasMissingProcess = true;
648
+ if (lease && (lease.status === "LEASED" || lease.status === "RELEASING")) {
649
+ this.portLeaseRepository.update({
650
+ ...lease,
651
+ status: "STALE",
652
+ releasedAt: lease.releasedAt ?? now
653
+ });
654
+ }
655
+ if (binding.status !== "FAILED" && binding.status !== "RELEASED") {
656
+ this.runtimeBindingRepository.update({
657
+ ...binding,
658
+ status: options.staleMissingBindingAsFailure ? "FAILED" : "RELEASED",
659
+ updatedAt: now
660
+ });
661
+ }
662
+ continue;
663
+ }
664
+ if (processInstance.status === "running") {
665
+ hasRunningProcess = true;
666
+ continue;
667
+ }
668
+ if (processInstance.status === "error") {
669
+ hasErroredProcess = true;
670
+ nextFailureStage = nextFailureStage ?? options.preferredFailureStage ?? "process_runtime_error";
671
+ if (lease && lease.status !== "RELEASED") {
672
+ this.portLeaseRepository.update({
673
+ ...lease,
674
+ status: "RELEASED",
675
+ releasedAt: lease.releasedAt ?? now
676
+ });
677
+ }
678
+ if (binding.status !== "FAILED") {
679
+ this.runtimeBindingRepository.update({
680
+ ...binding,
681
+ status: "FAILED",
682
+ updatedAt: now
683
+ });
684
+ }
685
+ continue;
686
+ }
687
+ if (processInstance.status === "closed") {
688
+ hasClosedProcess = true;
689
+ if (lease && lease.status !== "RELEASED") {
690
+ this.portLeaseRepository.update({
691
+ ...lease,
692
+ status: "RELEASED",
693
+ releasedAt: lease.releasedAt ?? now
694
+ });
695
+ }
696
+ if (binding.status !== "RELEASED") {
697
+ this.runtimeBindingRepository.update({
698
+ ...binding,
699
+ status: "RELEASED",
700
+ updatedAt: now
701
+ });
702
+ }
703
+ }
704
+ }
705
+ if (hasErroredProcess || (hasMissingProcess && options.staleMissingBindingAsFailure)) {
706
+ nextStatus = "FAILED";
707
+ nextFailureStage = nextFailureStage ?? (hasMissingProcess ? "stale_runtime_binding" : "process_runtime_error");
708
+ nextStoppedAt = now;
709
+ }
710
+ else if (hasRunningProcess) {
711
+ nextStatus = "RUNNING";
712
+ nextStoppedAt = null;
713
+ }
714
+ else if (hasClosedProcess || hasMissingProcess) {
715
+ nextStatus = "STOPPED";
716
+ nextFailureStage = null;
717
+ nextStoppedAt = now;
718
+ }
719
+ if (nextStatus !== runtime.status ||
720
+ nextFailureStage !== (runtime.failureStage ?? null) ||
721
+ nextStoppedAt !== (runtime.stoppedAt ?? null)) {
722
+ return this.debugRuntimeSessionRepository.update({
723
+ ...runtime,
724
+ status: nextStatus,
725
+ failureStage: nextFailureStage,
726
+ stoppedAt: nextStoppedAt,
727
+ updatedAt: now
728
+ }) ?? runtime;
729
+ }
730
+ return runtime;
731
+ }
732
+ collectReconciliationRuntimeIds() {
733
+ const runtimeIds = new Set();
734
+ for (const runtime of this.debugRuntimeSessionRepository.listByStatuses([
735
+ "PREPARING",
736
+ "RUNNING"
737
+ ])) {
738
+ runtimeIds.add(runtime.id);
739
+ }
740
+ for (const lease of this.portLeaseRepository.listByStatuses(["STALE"])) {
741
+ runtimeIds.add(lease.runtimeId);
742
+ }
743
+ return [...runtimeIds];
744
+ }
745
+ releaseStaleLeases(staleLeases) {
746
+ if (staleLeases.length === 0) {
747
+ return 0;
748
+ }
749
+ const now = nowIso();
750
+ for (const lease of staleLeases) {
751
+ this.portLeaseRepository.update({
752
+ ...lease,
753
+ status: "RELEASED",
754
+ releasedAt: lease.releasedAt ?? now
755
+ });
756
+ }
757
+ return staleLeases.length;
758
+ }
759
+ }
760
+ function discoverServiceCandidates(rootPath, commandHints) {
761
+ const rootPackageJson = readJsonFile(path.join(rootPath, "package.json"));
762
+ const rootScripts = extractPackageScripts(rootPackageJson);
763
+ const workspacePackages = discoverWorkspacePackages(rootPath, rootPackageJson);
764
+ const candidatesFromRootScripts = discoverWorkspaceServiceCandidatesFromRootScripts(rootPath, workspacePackages, rootScripts);
765
+ const candidatesFromWorkspacePackages = discoverWorkspaceServiceCandidatesFromPackages(rootPath, workspacePackages);
766
+ const candidatesFromAppsPythonServices = discoverAppsPythonServiceCandidates(rootPath);
767
+ const discoveredByCwd = new Map();
768
+ for (const candidate of [
769
+ ...candidatesFromRootScripts,
770
+ ...candidatesFromWorkspacePackages,
771
+ ...candidatesFromAppsPythonServices
772
+ ]) {
773
+ if (!discoveredByCwd.has(candidate.cwd)) {
774
+ discoveredByCwd.set(candidate.cwd, candidate);
775
+ }
776
+ }
777
+ if (discoveredByCwd.size > 0) {
778
+ return applyCommandHintFallback(sortDiscoveredServiceCandidates([...discoveredByCwd.values()]), commandHints);
779
+ }
780
+ return applyCommandHintFallback([
781
+ {
782
+ name: extractPackageName(rootPackageJson) ?? (path.basename(rootPath) || "service"),
783
+ cwd: rootPath,
784
+ commandHint: commandHints?.[0] ?? null,
785
+ roleHint: null
786
+ }
787
+ ], commandHints);
788
+ }
789
+ function discoverWorkspacePackages(rootPath, rootPackageJson) {
790
+ const packagePatterns = resolveWorkspacePackagePatterns(rootPath, rootPackageJson);
791
+ const packages = [];
792
+ const seenDirs = new Set();
793
+ for (const pattern of packagePatterns) {
794
+ for (const packageDir of expandWorkspacePattern(rootPath, pattern)) {
795
+ const normalizedDir = path.resolve(packageDir);
796
+ if (seenDirs.has(normalizedDir)) {
797
+ continue;
798
+ }
799
+ const packageJson = readJsonFile(path.join(normalizedDir, "package.json"));
800
+ if (!packageJson) {
801
+ continue;
802
+ }
803
+ seenDirs.add(normalizedDir);
804
+ packages.push({
805
+ name: extractPackageName(packageJson),
806
+ dir: normalizedDir,
807
+ scripts: extractPackageScripts(packageJson)
808
+ });
809
+ }
810
+ }
811
+ return packages.sort((left, right) => left.dir.localeCompare(right.dir));
812
+ }
813
+ function resolveWorkspacePackagePatterns(rootPath, rootPackageJson) {
814
+ const patterns = extractPackageJsonWorkspacePatterns(rootPackageJson);
815
+ if (patterns.length > 0) {
816
+ return patterns;
817
+ }
818
+ const pnpmWorkspacePath = path.join(rootPath, "pnpm-workspace.yaml");
819
+ if (!fs.existsSync(pnpmWorkspacePath)) {
820
+ return [];
821
+ }
822
+ try {
823
+ const content = fs.readFileSync(pnpmWorkspacePath, "utf8");
824
+ return content
825
+ .split(/\r?\n/u)
826
+ .map((line) => line.trim())
827
+ .filter((line) => line.startsWith("-"))
828
+ .map((line) => line.replace(/^-+\s*/, "").replace(/^['"]|['"]$/g, ""))
829
+ .filter(Boolean);
830
+ }
831
+ catch {
832
+ return [];
833
+ }
834
+ }
835
+ function extractPackageJsonWorkspacePatterns(rootPackageJson) {
836
+ if (!rootPackageJson) {
837
+ return [];
838
+ }
839
+ const rawWorkspaces = rootPackageJson.workspaces;
840
+ if (Array.isArray(rawWorkspaces)) {
841
+ return rawWorkspaces.filter((item) => typeof item === "string");
842
+ }
843
+ if (rawWorkspaces && typeof rawWorkspaces === "object" && !Array.isArray(rawWorkspaces)) {
844
+ const packages = rawWorkspaces.packages;
845
+ return Array.isArray(packages)
846
+ ? packages.filter((item) => typeof item === "string")
847
+ : [];
848
+ }
849
+ return [];
850
+ }
851
+ function expandWorkspacePattern(rootPath, pattern) {
852
+ const normalizedPattern = pattern.replace(/\\/g, "/");
853
+ if (normalizedPattern.endsWith("/*")) {
854
+ const baseDir = path.join(rootPath, normalizedPattern.slice(0, -2));
855
+ if (!fs.existsSync(baseDir) || !fs.statSync(baseDir).isDirectory()) {
856
+ return [];
857
+ }
858
+ try {
859
+ return fs.readdirSync(baseDir)
860
+ .map((entry) => path.join(baseDir, entry))
861
+ .filter((entryPath) => fs.existsSync(entryPath) && fs.statSync(entryPath).isDirectory());
862
+ }
863
+ catch {
864
+ return [];
865
+ }
866
+ }
867
+ const exactDir = path.join(rootPath, normalizedPattern);
868
+ return fs.existsSync(exactDir) && fs.statSync(exactDir).isDirectory() ? [exactDir] : [];
869
+ }
870
+ function discoverWorkspaceServiceCandidatesFromRootScripts(rootPath, workspacePackages, rootScripts) {
871
+ const discovered = [];
872
+ const seenDirs = new Set();
873
+ for (const [scriptName, scriptCommand] of Object.entries(rootScripts)) {
874
+ if (scriptName !== "dev" && !scriptName.startsWith("dev:")) {
875
+ continue;
876
+ }
877
+ const matchedPackage = resolveWorkspacePackageForRootScript(rootPath, workspacePackages, scriptName, scriptCommand);
878
+ if (!matchedPackage || seenDirs.has(matchedPackage.dir)) {
879
+ continue;
880
+ }
881
+ seenDirs.add(matchedPackage.dir);
882
+ discovered.push({
883
+ name: matchedPackage.name ?? (path.basename(matchedPackage.dir) || "service"),
884
+ cwd: matchedPackage.dir,
885
+ commandHint: resolveWorkspacePackageCommandHint(matchedPackage.scripts),
886
+ roleHint: inferServiceRoleHint(scriptName, matchedPackage)
887
+ });
888
+ }
889
+ return sortDiscoveredServiceCandidates(discovered);
890
+ }
891
+ function resolveWorkspacePackageForRootScript(rootPath, workspacePackages, scriptName, scriptCommand) {
892
+ const dirMatch = scriptCommand.match(/--dir(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/u);
893
+ const dirToken = dirMatch?.[1] ?? dirMatch?.[2] ?? dirMatch?.[3] ?? null;
894
+ if (dirToken) {
895
+ const resolvedDir = path.resolve(rootPath, dirToken);
896
+ const matched = workspacePackages.find((item) => item.dir === resolvedDir);
897
+ if (matched) {
898
+ return matched;
899
+ }
900
+ }
901
+ const filterMatch = scriptCommand.match(/--filter(?:=|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/u);
902
+ const filterToken = filterMatch?.[1] ?? filterMatch?.[2] ?? filterMatch?.[3] ?? null;
903
+ if (filterToken) {
904
+ const normalizedFilter = filterToken.trim();
905
+ const matched = workspacePackages.find((item) => item.name === normalizedFilter || path.basename(item.dir) === normalizedFilter);
906
+ if (matched) {
907
+ return matched;
908
+ }
909
+ }
910
+ const scriptAlias = scriptName.split(":")[1]?.trim() ?? "";
911
+ if (!scriptAlias) {
912
+ return null;
913
+ }
914
+ return workspacePackages.find((item) => item.name === scriptAlias || path.basename(item.dir) === scriptAlias) ?? null;
915
+ }
916
+ function discoverWorkspaceServiceCandidatesFromPackages(rootPath, workspacePackages) {
917
+ const discovered = workspacePackages
918
+ .filter((item) => hasRunnableDevScript(item.scripts))
919
+ .map((item) => ({
920
+ name: item.name ?? (path.basename(item.dir) || "service"),
921
+ cwd: item.dir,
922
+ commandHint: resolveWorkspacePackageCommandHint(item.scripts),
923
+ roleHint: inferServiceRoleHint(path.relative(rootPath, item.dir), item)
924
+ }));
925
+ return sortDiscoveredServiceCandidates(discovered);
926
+ }
927
+ function discoverAppsPythonServiceCandidates(rootPath) {
928
+ const appsDir = path.join(rootPath, "apps");
929
+ if (!fs.existsSync(appsDir) || !fs.statSync(appsDir).isDirectory()) {
930
+ return [];
931
+ }
932
+ let appEntries = [];
933
+ try {
934
+ appEntries = fs.readdirSync(appsDir);
935
+ }
936
+ catch {
937
+ return [];
938
+ }
939
+ const discovered = [];
940
+ for (const entry of appEntries) {
941
+ const serviceDir = path.join(appsDir, entry);
942
+ if (!fs.existsSync(serviceDir) || !fs.statSync(serviceDir).isDirectory()) {
943
+ continue;
944
+ }
945
+ const framework = pickFramework(collectFrameworkEvidence(serviceDir));
946
+ if (!isPythonFramework(framework.primaryFramework)) {
947
+ continue;
948
+ }
949
+ discovered.push({
950
+ name: path.basename(serviceDir) || "service",
951
+ cwd: serviceDir,
952
+ commandHint: null,
953
+ roleHint: "backend"
954
+ });
955
+ }
956
+ return sortDiscoveredServiceCandidates(discovered);
957
+ }
958
+ function hasRunnableDevScript(scripts) {
959
+ return Object.keys(scripts).some((name) => name === "dev" || name.startsWith("dev:"));
960
+ }
961
+ function resolveWorkspacePackageCommandHint(scripts) {
962
+ if (typeof scripts.dev === "string" && scripts.dev.trim().length > 0) {
963
+ return "pnpm dev";
964
+ }
965
+ const devScriptName = Object.keys(scripts)
966
+ .filter((name) => name.startsWith("dev:"))
967
+ .sort()[0] ?? null;
968
+ return devScriptName ? `pnpm run ${devScriptName}` : null;
969
+ }
970
+ function inferServiceRoleHint(signal, workspacePackage) {
971
+ const normalizedSignal = `${signal} ${workspacePackage.name ?? ""} ${path.basename(workspacePackage.dir)}`
972
+ .toLowerCase();
973
+ if (normalizedSignal.includes("frontend") || normalizedSignal.includes("user-app")
974
+ || normalizedSignal.includes("web") || normalizedSignal.includes("ui")) {
975
+ return "frontend";
976
+ }
977
+ if (normalizedSignal.includes("backend") || normalizedSignal.includes("server")
978
+ || normalizedSignal.includes("api") || normalizedSignal.includes("host")) {
979
+ return "backend";
980
+ }
981
+ if (normalizedSignal.includes("worker")) {
982
+ return "worker";
983
+ }
984
+ if (normalizedSignal.includes("mock")) {
985
+ return "mock";
986
+ }
987
+ return null;
988
+ }
989
+ function sortDiscoveredServiceCandidates(candidates) {
990
+ return [...candidates].sort((left, right) => {
991
+ const roleDelta = resolveDiscoveredServicePriority(left.roleHint) - resolveDiscoveredServicePriority(right.roleHint);
992
+ if (roleDelta !== 0) {
993
+ return roleDelta;
994
+ }
995
+ return left.cwd.localeCompare(right.cwd);
996
+ });
997
+ }
998
+ function resolveDiscoveredServicePriority(role) {
999
+ switch (role) {
1000
+ case "frontend":
1001
+ return 0;
1002
+ case "backend":
1003
+ return 1;
1004
+ case "worker":
1005
+ return 2;
1006
+ case "mock":
1007
+ return 3;
1008
+ default:
1009
+ return 4;
1010
+ }
1011
+ }
1012
+ function applyCommandHintFallback(candidates, commandHints) {
1013
+ if (!commandHints || commandHints.length === 0) {
1014
+ return candidates;
1015
+ }
1016
+ return candidates.map((candidate, index) => ({
1017
+ ...candidate,
1018
+ commandHint: candidate.commandHint ?? commandHints[index] ?? (candidates.length === 1 ? commandHints[0] ?? null : null)
1019
+ }));
1020
+ }
1021
+ function extractPackageName(packageJson) {
1022
+ const packageName = packageJson?.name;
1023
+ return typeof packageName === "string" && packageName.trim().length > 0 ? packageName.trim() : null;
1024
+ }
1025
+ function buildServiceRecord(target, candidate, framework, timestamp, frameworkAnalysisId) {
1026
+ const parsedCommand = parseCommandHint(candidate.commandHint ?? defaultCommandHintForFramework(framework.primaryFramework, candidate.cwd));
1027
+ return {
1028
+ id: createId(),
1029
+ targetId: target.id,
1030
+ role: candidate.roleHint ?? resolveServiceRole(framework.primaryFramework),
1031
+ name: candidate.name,
1032
+ cwd: candidate.cwd,
1033
+ command: parsedCommand.command,
1034
+ args: parsedCommand.args,
1035
+ env: {},
1036
+ defaultPortHint: framework.defaultPortHint,
1037
+ protocol: "http",
1038
+ healthPath: null,
1039
+ adapterKind: normalizeAdapterKind(framework.primaryFramework),
1040
+ frameworkAnalysisId,
1041
+ createdAt: timestamp,
1042
+ updatedAt: timestamp
1043
+ };
1044
+ }
1045
+ function parseCommandHint(commandHint) {
1046
+ const tokens = (commandHint.match(/"[^"]*"|'[^']*'|\S+/g) ?? [])
1047
+ .map((item) => item.replace(/^['"]|['"]$/g, ""))
1048
+ .filter(Boolean);
1049
+ if (tokens.length === 0) {
1050
+ return {
1051
+ command: "npm",
1052
+ args: ["run", "dev"]
1053
+ };
1054
+ }
1055
+ return {
1056
+ command: tokens[0] ?? "npm",
1057
+ args: tokens.slice(1)
1058
+ };
1059
+ }
1060
+ function defaultCommandHintForFramework(framework, rootPath) {
1061
+ switch (framework) {
1062
+ case "spring-boot":
1063
+ return "./mvnw spring-boot:run";
1064
+ case "uvicorn":
1065
+ return `uvicorn ${inferUvicornAppSpec(rootPath)} --reload`;
1066
+ case "flask":
1067
+ return "flask run";
1068
+ case "django":
1069
+ return "python manage.py runserver";
1070
+ case "rails":
1071
+ return "bin/rails server";
1072
+ case "aspnet-core":
1073
+ return "dotnet watch run";
1074
+ default:
1075
+ return "npm run dev";
1076
+ }
1077
+ }
1078
+ function resolveServiceRole(framework) {
1079
+ switch (framework) {
1080
+ case "vite":
1081
+ case "nextjs":
1082
+ case "cra":
1083
+ case "astro":
1084
+ case "nuxt":
1085
+ case "vue-cli":
1086
+ case "remix":
1087
+ return "frontend";
1088
+ case "spring-boot":
1089
+ case "uvicorn":
1090
+ case "flask":
1091
+ case "django":
1092
+ case "rails":
1093
+ case "aspnet-core":
1094
+ case "nestjs":
1095
+ case "express":
1096
+ case "koa":
1097
+ case "hono":
1098
+ case "node-custom":
1099
+ case "go-http":
1100
+ case "laravel":
1101
+ case "php-custom":
1102
+ return "backend";
1103
+ default:
1104
+ return "custom";
1105
+ }
1106
+ }
1107
+ function inferUvicornAppSpec(rootPath) {
1108
+ if (!rootPath) {
1109
+ return "main:app";
1110
+ }
1111
+ const candidates = [
1112
+ {
1113
+ relativePath: "app/main.py",
1114
+ moduleSpec: "app.main:app"
1115
+ },
1116
+ {
1117
+ relativePath: "main.py",
1118
+ moduleSpec: "main:app"
1119
+ },
1120
+ {
1121
+ relativePath: "src/main.py",
1122
+ moduleSpec: "src.main:app"
1123
+ },
1124
+ {
1125
+ relativePath: "app.py",
1126
+ moduleSpec: "app:app"
1127
+ }
1128
+ ];
1129
+ for (const candidate of candidates) {
1130
+ if (fs.existsSync(path.join(rootPath, candidate.relativePath))) {
1131
+ return candidate.moduleSpec;
1132
+ }
1133
+ }
1134
+ return "main:app";
1135
+ }
1136
+ function normalizeAdapterKind(framework) {
1137
+ const item = getFrameworkCompatibilityItem(framework);
1138
+ if (item.recommendedInjectionMode === "none") {
1139
+ return null;
1140
+ }
1141
+ return item.recommendedInjectionMode;
1142
+ }
1143
+ function withTrailingSeparator(input) {
1144
+ return input.endsWith(path.sep) ? input : `${input}${path.sep}`;
1145
+ }
1146
+ function collectFrameworkEvidence(rootPath) {
1147
+ const rawEvidence = {};
1148
+ const candidates = new Map();
1149
+ const packageJson = readJsonFile(path.join(rootPath, "package.json"));
1150
+ const packageDeps = extractPackageDependencies(packageJson);
1151
+ const packageScripts = extractPackageScripts(packageJson);
1152
+ if (packageJson) {
1153
+ rawEvidence.packageJson = {
1154
+ dependencies: Object.keys(packageDeps),
1155
+ scripts: Object.keys(packageScripts)
1156
+ };
1157
+ }
1158
+ const addCandidateSignal = (framework, score, reason, files, defaultPortHint) => {
1159
+ const candidate = candidates.get(framework) ?? {
1160
+ framework,
1161
+ score: 0,
1162
+ reasons: [],
1163
+ detectedFiles: [],
1164
+ defaultPortHint
1165
+ };
1166
+ candidate.score += score;
1167
+ candidate.defaultPortHint = candidate.defaultPortHint ?? defaultPortHint;
1168
+ if (!candidate.reasons.includes(reason)) {
1169
+ candidate.reasons.push(reason);
1170
+ }
1171
+ for (const file of files) {
1172
+ if (!candidate.detectedFiles.includes(file)) {
1173
+ candidate.detectedFiles.push(file);
1174
+ }
1175
+ }
1176
+ candidates.set(framework, candidate);
1177
+ };
1178
+ const firstExistingFile = (relativePaths) => {
1179
+ for (const relativePath of relativePaths) {
1180
+ if (fs.existsSync(path.join(rootPath, relativePath))) {
1181
+ return relativePath;
1182
+ }
1183
+ }
1184
+ return null;
1185
+ };
1186
+ const existingFiles = (relativePaths) => relativePaths.filter((relativePath) => fs.existsSync(path.join(rootPath, relativePath)));
1187
+ if (packageJson) {
1188
+ addCandidateSignal("node-custom", 1, "命中 package.json,存在通用 Node 项目特征", ["package.json"], 3000);
1189
+ }
1190
+ const viteConfig = firstExistingFile(["vite.config.ts", "vite.config.js", "vite.config.mjs", "vite.config.cjs"]);
1191
+ if (viteConfig) {
1192
+ addCandidateSignal("vite", 4, "命中 Vite 配置文件", [viteConfig, "package.json"], 5173);
1193
+ }
1194
+ if (packageDeps.vite) {
1195
+ addCandidateSignal("vite", 3, "package.json 含 vite 依赖", ["package.json"], 5173);
1196
+ }
1197
+ if (includesScriptCommand(packageScripts, "vite")) {
1198
+ addCandidateSignal("vite", 2, "package.json 的脚本命中了 vite 命令", ["package.json"], 5173);
1199
+ }
1200
+ const nextConfig = firstExistingFile(["next.config.js", "next.config.mjs", "next.config.ts"]);
1201
+ if (nextConfig) {
1202
+ addCandidateSignal("nextjs", 4, "命中 Next.js 配置文件", [nextConfig, "package.json"], 3000);
1203
+ }
1204
+ if (packageDeps.next) {
1205
+ addCandidateSignal("nextjs", 3, "package.json 含 next 依赖", ["package.json"], 3000);
1206
+ }
1207
+ if (includesScriptCommand(packageScripts, "next dev")) {
1208
+ addCandidateSignal("nextjs", 2, "package.json 的脚本命中了 next dev", ["package.json"], 3000);
1209
+ }
1210
+ if (packageDeps["react-scripts"]) {
1211
+ addCandidateSignal("cra", 3, "package.json 命中 react-scripts 依赖", ["package.json"], 3000);
1212
+ }
1213
+ if (includesScriptCommand(packageScripts, "react-scripts")) {
1214
+ addCandidateSignal("cra", 2, "package.json 的脚本命中了 react-scripts", ["package.json"], 3000);
1215
+ }
1216
+ const astroConfig = firstExistingFile(["astro.config.mjs", "astro.config.ts", "astro.config.js"]);
1217
+ if (astroConfig) {
1218
+ addCandidateSignal("astro", 4, "命中 Astro 配置文件", [astroConfig, "package.json"], 4321);
1219
+ }
1220
+ if (packageDeps.astro) {
1221
+ addCandidateSignal("astro", 3, "package.json 含 astro 依赖", ["package.json"], 4321);
1222
+ }
1223
+ const nuxtConfig = firstExistingFile(["nuxt.config.ts", "nuxt.config.js"]);
1224
+ if (nuxtConfig) {
1225
+ addCandidateSignal("nuxt", 4, "命中 Nuxt 配置文件", [nuxtConfig, "package.json"], 3000);
1226
+ }
1227
+ if (packageDeps.nuxt) {
1228
+ addCandidateSignal("nuxt", 3, "package.json 含 nuxt 依赖", ["package.json"], 3000);
1229
+ }
1230
+ if (fs.existsSync(path.join(rootPath, "vue.config.js"))) {
1231
+ addCandidateSignal("vue-cli", 4, "命中 Vue CLI 配置文件", ["vue.config.js", "package.json"], 8080);
1232
+ }
1233
+ if (packageDeps["@vue/cli-service"]) {
1234
+ addCandidateSignal("vue-cli", 3, "package.json 含 @vue/cli-service 依赖", ["package.json"], 8080);
1235
+ }
1236
+ const remixConfig = firstExistingFile(["remix.config.js", "remix.config.mjs", "remix.config.ts"]);
1237
+ if (remixConfig) {
1238
+ addCandidateSignal("remix", 4, "命中 Remix 配置文件", [remixConfig, "package.json"], 3000);
1239
+ }
1240
+ if (packageDeps["@remix-run/dev"] || packageDeps["@remix-run/react"]) {
1241
+ addCandidateSignal("remix", 3, "package.json 含 Remix 依赖", ["package.json"], 3000);
1242
+ }
1243
+ if (includesScriptCommand(packageScripts, "remix dev")) {
1244
+ addCandidateSignal("remix", 2, "package.json 的脚本命中了 remix dev", ["package.json"], 3000);
1245
+ }
1246
+ const electronFiles = existingFiles(["electron-builder.json", "electron.vite.config.ts", "electron.vite.config.js"]);
1247
+ if (electronFiles.length > 0) {
1248
+ addCandidateSignal("electron", 4, "命中 Electron 配置文件", electronFiles, 3000);
1249
+ }
1250
+ if (packageDeps.electron || packageDeps["electron-vite"]) {
1251
+ addCandidateSignal("electron", 3, "package.json 含 Electron 相关依赖", ["package.json"], 3000);
1252
+ }
1253
+ const tauriConfig = firstExistingFile(["src-tauri/tauri.conf.json", "src-tauri/tauri.conf.json5"]);
1254
+ if (tauriConfig) {
1255
+ addCandidateSignal("tauri", 4, "命中 Tauri 配置文件", [tauriConfig, "package.json"], 3000);
1256
+ }
1257
+ if (packageDeps["@tauri-apps/cli"]) {
1258
+ addCandidateSignal("tauri", 3, "package.json 含 @tauri-apps/cli 依赖", ["package.json"], 3000);
1259
+ }
1260
+ const pomPath = path.join(rootPath, "pom.xml");
1261
+ if (fs.existsSync(pomPath) && fileContains(pomPath, "spring-boot")) {
1262
+ addCandidateSignal("spring-boot", 5, "pom.xml 命中 spring-boot 关键字", ["pom.xml"], 8080);
1263
+ }
1264
+ const gradleFiles = existingFiles(["build.gradle", "build.gradle.kts"]);
1265
+ for (const gradleFile of gradleFiles) {
1266
+ if (fileContains(path.join(rootPath, gradleFile), "spring-boot")) {
1267
+ addCandidateSignal("spring-boot", 4, `${gradleFile} 命中 spring-boot 关键字`, [gradleFile], 8080);
1268
+ }
1269
+ }
1270
+ if (existingFiles(["application.properties", "application.yml", "application.yaml"]).length > 0) {
1271
+ addCandidateSignal("spring-boot", 1, "命中 Spring Boot 常见应用配置文件", existingFiles([
1272
+ "application.properties",
1273
+ "application.yml",
1274
+ "application.yaml"
1275
+ ]), 8080);
1276
+ }
1277
+ const requirementsExists = fs.existsSync(path.join(rootPath, "requirements.txt"));
1278
+ const pyprojectExists = fs.existsSync(path.join(rootPath, "pyproject.toml"));
1279
+ if ((requirementsExists && fileContains(path.join(rootPath, "requirements.txt"), "uvicorn"))
1280
+ || (pyprojectExists && fileContains(path.join(rootPath, "pyproject.toml"), "fastapi"))) {
1281
+ addCandidateSignal("uvicorn", 4, "Python 依赖中命中 uvicorn / fastapi", existingFiles([
1282
+ "requirements.txt",
1283
+ "pyproject.toml"
1284
+ ]), 8000);
1285
+ }
1286
+ if (requirementsExists && fileContains(path.join(rootPath, "requirements.txt"), "flask")) {
1287
+ addCandidateSignal("flask", 3, "requirements.txt 命中 flask", ["requirements.txt"], 5000);
1288
+ }
1289
+ if (fs.existsSync(path.join(rootPath, "app.py"))) {
1290
+ addCandidateSignal("flask", 2, "命中 Flask 常见入口 app.py", ["app.py"], 5000);
1291
+ }
1292
+ if (fs.existsSync(path.join(rootPath, "manage.py"))) {
1293
+ addCandidateSignal("django", 4, "命中 Django 入口 manage.py", ["manage.py"], 8000);
1294
+ }
1295
+ if (requirementsExists && fileContains(path.join(rootPath, "requirements.txt"), "django")) {
1296
+ addCandidateSignal("django", 3, "requirements.txt 命中 django", ["requirements.txt"], 8000);
1297
+ }
1298
+ if (fs.existsSync(path.join(rootPath, "settings.py"))) {
1299
+ addCandidateSignal("django", 1, "命中 Django 常见配置 settings.py", ["settings.py"], 8000);
1300
+ }
1301
+ if (fs.existsSync(path.join(rootPath, "Gemfile")) && fileContains(path.join(rootPath, "Gemfile"), "rails")) {
1302
+ addCandidateSignal("rails", 4, "Gemfile 命中 Rails", ["Gemfile"], 3000);
1303
+ }
1304
+ const csprojFiles = safeListFiles(rootPath).filter((filePath) => filePath.endsWith(".csproj"));
1305
+ if (csprojFiles.length > 0) {
1306
+ addCandidateSignal("aspnet-core", 4, "命中 .csproj 项目文件", csprojFiles.slice(0, 3).map((filePath) => path.basename(filePath)), 5000);
1307
+ }
1308
+ if (fs.existsSync(path.join(rootPath, "Program.cs"))) {
1309
+ addCandidateSignal("aspnet-core", 1, "命中 ASP.NET Core 常见入口 Program.cs", ["Program.cs"], 5000);
1310
+ }
1311
+ if (packageDeps["@nestjs/core"]) {
1312
+ addCandidateSignal("nestjs", 3, "package.json 命中 @nestjs/core 依赖", ["package.json"], 3000);
1313
+ }
1314
+ if (includesScriptCommand(packageScripts, "nest start")) {
1315
+ addCandidateSignal("nestjs", 2, "package.json 的脚本命中了 nest start", ["package.json"], 3000);
1316
+ }
1317
+ const nestEntry = firstExistingFile(["src/main.ts", "src/main.js"]);
1318
+ if (nestEntry && (packageDeps["@nestjs/core"] || includesScriptCommand(packageScripts, "nest start"))) {
1319
+ addCandidateSignal("nestjs", 1, "命中 NestJS 常见入口文件", [nestEntry], 3000);
1320
+ }
1321
+ if (packageDeps.express) {
1322
+ addCandidateSignal("express", 3, "package.json 命中 express 依赖", ["package.json"], 3000);
1323
+ }
1324
+ if (packageDeps.koa) {
1325
+ addCandidateSignal("koa", 3, "package.json 命中 koa 依赖", ["package.json"], 3000);
1326
+ }
1327
+ if (packageDeps.hono) {
1328
+ addCandidateSignal("hono", 3, "package.json 命中 hono 依赖", ["package.json"], 3000);
1329
+ }
1330
+ const nodeEntry = firstExistingFile(["server.ts", "server.js", "app.ts", "app.js", "main.ts", "main.js", "index.ts", "index.js"]);
1331
+ if (nodeEntry) {
1332
+ addCandidateSignal("node-custom", 1, "命中通用 Node 服务入口文件", [nodeEntry], 3000);
1333
+ }
1334
+ if (fs.existsSync(path.join(rootPath, "go.mod"))) {
1335
+ addCandidateSignal("go-http", 4, "命中 go.mod,第一阶段按 Go 自定义 HTTP 服务处理", ["go.mod"], 8080);
1336
+ }
1337
+ if (fs.existsSync(path.join(rootPath, "artisan"))) {
1338
+ addCandidateSignal("laravel", 4, "命中 Laravel 入口 artisan", ["artisan"], 8000);
1339
+ }
1340
+ if (fs.existsSync(path.join(rootPath, "composer.json"))
1341
+ && fileContains(path.join(rootPath, "composer.json"), "laravel")) {
1342
+ addCandidateSignal("laravel", 3, "composer.json 命中 laravel 依赖", ["composer.json"], 8000);
1343
+ }
1344
+ if (fs.existsSync(path.join(rootPath, "composer.json"))) {
1345
+ addCandidateSignal("php-custom", 1, "命中 composer.json,存在 PHP 项目特征", ["composer.json"], 8000);
1346
+ }
1347
+ rawEvidence.candidates = Array.from(candidates.values())
1348
+ .sort((left, right) => right.score - left.score)
1349
+ .map((candidate) => ({
1350
+ framework: candidate.framework,
1351
+ score: candidate.score,
1352
+ reasons: candidate.reasons,
1353
+ detectedFiles: candidate.detectedFiles
1354
+ }));
1355
+ const selected = selectFrameworkCandidate(candidates);
1356
+ if (!selected) {
1357
+ return buildDetection(null, "low", ["未识别到受支持框架的稳定特征"], packageJson ? ["package.json"] : [], rawEvidence, null);
1358
+ }
1359
+ rawEvidence.selectedCandidate = {
1360
+ framework: selected.framework,
1361
+ score: selected.score
1362
+ };
1363
+ return buildDetection(selected.framework, confidenceByCandidateScore(selected.score), selected.reasons, selected.detectedFiles, rawEvidence, selected.defaultPortHint);
1364
+ }
1365
+ function buildDetection(primaryFramework, confidence, reasons, detectedFiles, rawEvidence, defaultPortHint) {
1366
+ return {
1367
+ primaryFramework,
1368
+ confidence,
1369
+ reasons,
1370
+ detectedFiles,
1371
+ rawEvidence,
1372
+ defaultPortHint
1373
+ };
1374
+ }
1375
+ function confidenceByCandidateScore(score) {
1376
+ if (score >= 6) {
1377
+ return "high";
1378
+ }
1379
+ if (score >= 3) {
1380
+ return "medium";
1381
+ }
1382
+ return "low";
1383
+ }
1384
+ function readJsonFile(filePath) {
1385
+ if (!fs.existsSync(filePath)) {
1386
+ return null;
1387
+ }
1388
+ try {
1389
+ const content = fs.readFileSync(filePath, "utf8");
1390
+ const parsed = JSON.parse(content);
1391
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
1392
+ ? parsed
1393
+ : null;
1394
+ }
1395
+ catch {
1396
+ return null;
1397
+ }
1398
+ }
1399
+ function extractPackageDependencies(packageJson) {
1400
+ if (!packageJson) {
1401
+ return {};
1402
+ }
1403
+ const mergedEntries = [
1404
+ ...(objectEntries(packageJson.dependencies)),
1405
+ ...(objectEntries(packageJson.devDependencies))
1406
+ ];
1407
+ return Object.fromEntries(mergedEntries.filter((entry) => typeof entry[1] === "string"));
1408
+ }
1409
+ function extractPackageScripts(packageJson) {
1410
+ if (!packageJson) {
1411
+ return {};
1412
+ }
1413
+ return Object.fromEntries(objectEntries(packageJson.scripts).filter((entry) => typeof entry[1] === "string"));
1414
+ }
1415
+ function objectEntries(value) {
1416
+ return value && typeof value === "object" && !Array.isArray(value)
1417
+ ? Object.entries(value)
1418
+ : [];
1419
+ }
1420
+ function includesScriptCommand(scripts, needle) {
1421
+ return Object.values(scripts).some((script) => script.includes(needle));
1422
+ }
1423
+ function fileContains(filePath, needle) {
1424
+ if (!fs.existsSync(filePath)) {
1425
+ return false;
1426
+ }
1427
+ try {
1428
+ return fs.readFileSync(filePath, "utf8").includes(needle);
1429
+ }
1430
+ catch {
1431
+ return false;
1432
+ }
1433
+ }
1434
+ function safeListFiles(rootPath) {
1435
+ try {
1436
+ return fs.readdirSync(rootPath).map((name) => path.join(rootPath, name));
1437
+ }
1438
+ catch {
1439
+ return [];
1440
+ }
1441
+ }
1442
+ function pickFramework(result) {
1443
+ return result;
1444
+ }
1445
+ function isPythonFramework(framework) {
1446
+ return framework === "uvicorn" || framework === "flask" || framework === "django";
1447
+ }
1448
+ function resolveAnalyzeCommandHints(input) {
1449
+ const templateHints = collectTemplateCommandHints(input.terminalCommandTemplateRepository.listByWorkspace(input.workspaceId), input.rootPath);
1450
+ const explicitHints = (input.explicitCommandHints ?? []).map((item) => item.trim()).filter(Boolean);
1451
+ const mergedHints = [...templateHints, ...explicitHints];
1452
+ return mergedHints.length > 0 ? [...new Set(mergedHints)] : undefined;
1453
+ }
1454
+ function collectTemplateCommandHints(templates, rootPath) {
1455
+ return templates
1456
+ .filter((template) => isTemplateRelevantToRootPath(template, rootPath))
1457
+ .sort((left, right) => compareTemplatePriority(left, right, rootPath))
1458
+ .map((template) => buildTemplateCommandHint(template))
1459
+ .filter((item) => Boolean(item));
1460
+ }
1461
+ function isTemplateRelevantToRootPath(template, rootPath) {
1462
+ const templateCwd = path.resolve(template.cwd);
1463
+ const normalizedRootPath = path.resolve(rootPath);
1464
+ return isPathWithin(normalizedRootPath, templateCwd) || isPathWithin(templateCwd, normalizedRootPath);
1465
+ }
1466
+ function compareTemplatePriority(left, right, rootPath) {
1467
+ const backendDelta = Number(isLikelyBackendTemplate(right)) - Number(isLikelyBackendTemplate(left));
1468
+ if (backendDelta !== 0) {
1469
+ return backendDelta;
1470
+ }
1471
+ const sourceDelta = resolveTemplateSourcePriority(left) - resolveTemplateSourcePriority(right);
1472
+ if (sourceDelta !== 0) {
1473
+ return sourceDelta;
1474
+ }
1475
+ const distanceDelta = resolveTemplateDistance(left, rootPath) - resolveTemplateDistance(right, rootPath);
1476
+ if (distanceDelta !== 0) {
1477
+ return distanceDelta;
1478
+ }
1479
+ return left.cwd.localeCompare(right.cwd);
1480
+ }
1481
+ function resolveTemplateSourcePriority(template) {
1482
+ return template.sourceType === "debug_service" ? 1 : 0;
1483
+ }
1484
+ function resolveTemplateDistance(template, rootPath) {
1485
+ const templateCwd = path.resolve(template.cwd);
1486
+ const normalizedRootPath = path.resolve(rootPath);
1487
+ if (templateCwd === normalizedRootPath) {
1488
+ return 0;
1489
+ }
1490
+ if (isPathWithin(normalizedRootPath, templateCwd)) {
1491
+ return 1;
1492
+ }
1493
+ if (isPathWithin(templateCwd, normalizedRootPath)) {
1494
+ return 2;
1495
+ }
1496
+ return 3;
1497
+ }
1498
+ function buildTemplateCommandHint(template) {
1499
+ const command = template.command.trim();
1500
+ const args = template.args.map((item) => item.trim()).filter(Boolean);
1501
+ if (!command) {
1502
+ return null;
1503
+ }
1504
+ return [command, ...args].join(" ");
1505
+ }
1506
+ function isLikelyBackendTemplate(template) {
1507
+ const signal = [
1508
+ template.name,
1509
+ template.cwd,
1510
+ template.command,
1511
+ ...template.args
1512
+ ].join(" ").toLowerCase();
1513
+ return (signal.includes("backend")
1514
+ || signal.includes("server")
1515
+ || signal.includes("api")
1516
+ || signal.includes("host")
1517
+ || signal.includes("python")
1518
+ || signal.includes("uvicorn")
1519
+ || signal.includes("gunicorn")
1520
+ || signal.includes("flask")
1521
+ || signal.includes("django")
1522
+ || signal.includes("manage.py"));
1523
+ }
1524
+ function isPathWithin(parentPath, childPath) {
1525
+ const normalizedParent = withTrailingSeparator(path.resolve(parentPath));
1526
+ const normalizedChild = path.resolve(childPath);
1527
+ return normalizedChild === path.resolve(parentPath) || normalizedChild.startsWith(normalizedParent);
1528
+ }
1529
+ function selectFrameworkCandidate(candidates) {
1530
+ return Array.from(candidates.values())
1531
+ .sort((left, right) => {
1532
+ if (right.score !== left.score) {
1533
+ return right.score - left.score;
1534
+ }
1535
+ return left.framework.localeCompare(right.framework);
1536
+ })[0] ?? null;
1537
+ }
1538
+ function resolveMissingRequirements(analysis) {
1539
+ const missing = [];
1540
+ if (analysis.requiresServiceDiscoveryHandling) {
1541
+ missing.push("service_discovery");
1542
+ }
1543
+ if (analysis.requiresHmrHandling) {
1544
+ missing.push("hmr");
1545
+ }
1546
+ if (analysis.requiresCallbackHandling) {
1547
+ missing.push("callback");
1548
+ }
1549
+ return missing;
1550
+ }
1551
+ function isFrameworkEligible(level) {
1552
+ return level === "supported" || level === "conditional";
1553
+ }
1554
+ async function allocateManagedPort(role, portLeaseRepository) {
1555
+ const startPort = resolvePortRangeStart(role);
1556
+ for (let offset = 0; offset < 200; offset += 1) {
1557
+ const port = startPort + offset;
1558
+ if (portLeaseRepository.findActiveByPort(port, "tcp")) {
1559
+ continue;
1560
+ }
1561
+ if (!(await isPortAvailable(port))) {
1562
+ continue;
1563
+ }
1564
+ return port;
1565
+ }
1566
+ throw new AppError({
1567
+ statusCode: 409,
1568
+ errorCode: "PORT_LEASE_EXHAUSTED",
1569
+ detail: `当前服务角色没有可分配的空闲端口:${role}`
1570
+ });
1571
+ }
1572
+ function resolvePortRangeStart(role) {
1573
+ switch (role) {
1574
+ case "frontend":
1575
+ return 43000;
1576
+ case "backend":
1577
+ return 44000;
1578
+ case "worker":
1579
+ return 45000;
1580
+ case "mock":
1581
+ return 46000;
1582
+ default:
1583
+ return 47000;
1584
+ }
1585
+ }
1586
+ async function isPortAvailable(port) {
1587
+ return await new Promise((resolve) => {
1588
+ const server = net.createServer();
1589
+ server.once("error", () => {
1590
+ resolve(false);
1591
+ });
1592
+ server.once("listening", () => {
1593
+ server.close(() => {
1594
+ resolve(true);
1595
+ });
1596
+ });
1597
+ server.listen(port, "127.0.0.1");
1598
+ });
1599
+ }
1600
+ function buildEphemeralTemplate(service, planItem, runtimeType) {
1601
+ return {
1602
+ id: `ephemeral-${service.id}`,
1603
+ workspaceId: "",
1604
+ name: service.name,
1605
+ cwd: service.cwd,
1606
+ command: service.command,
1607
+ args: planItem.args,
1608
+ env: planItem.envPatch,
1609
+ port: planItem.leasedPort,
1610
+ proxyEnabled: false,
1611
+ proxySlug: null,
1612
+ runtimeType,
1613
+ sourceType: "debug_service",
1614
+ debugTargetId: null,
1615
+ debugServiceId: service.id,
1616
+ frameworkAnalysisId: planItem.frameworkAnalysisId,
1617
+ adapterKind: planItem.adapterKind,
1618
+ injectionMode: planItem.injectionMode,
1619
+ generatedArtifactRef: null,
1620
+ serviceDiscoveryMode: planItem.requiresServiceDiscoveryHandling ? "api_base_url" : "none",
1621
+ managedBySystem: true,
1622
+ createdAt: "",
1623
+ updatedAt: ""
1624
+ };
1625
+ }
1626
+ function resolveProcessInstance(processInstanceId, terminalInstanceRepository) {
1627
+ if (!processInstanceId) {
1628
+ return null;
1629
+ }
1630
+ return terminalInstanceRepository.findById(processInstanceId);
1631
+ }
1632
+ function resolveAiFallbackNextStatus(action) {
1633
+ switch (action) {
1634
+ case "apply":
1635
+ return "APPLIED";
1636
+ case "reject":
1637
+ return "REJECTED";
1638
+ case "rollback":
1639
+ return "ROLLED_BACK";
1640
+ default:
1641
+ return "REJECTED";
1642
+ }
1643
+ }
1644
+ //# sourceMappingURL=debug-target-service.js.map