@jingyi0605/codingns 0.3.5 → 0.4.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 (255) hide show
  1. package/README.md +16 -0
  2. package/bin/codingns.mjs +1369 -10
  3. package/dist/public/assets/{TerminalPage-CgrfstRm.js → TerminalPage-6jHZV9Mh.js} +17 -17
  4. package/dist/public/assets/index-CSVhg7I8.js +123 -0
  5. package/dist/public/assets/index-Ce1VX19m.css +1 -0
  6. package/dist/public/index.html +2 -2
  7. package/dist/server/config/opencode-base-url-resolver.d.ts +7 -0
  8. package/dist/server/config/opencode-base-url-resolver.js +48 -11
  9. package/dist/server/config/opencode-base-url-resolver.js.map +1 -1
  10. package/dist/server/config/opencode-system-probe-helper-client.d.ts +4 -0
  11. package/dist/server/config/opencode-system-probe-helper-client.js +29 -0
  12. package/dist/server/config/opencode-system-probe-helper-client.js.map +1 -1
  13. package/dist/server/config/opencode-system-probe-helper-process.d.ts +12 -0
  14. package/dist/server/config/opencode-system-probe-helper-process.js +34 -7
  15. package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
  16. package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +317 -0
  17. package/dist/server/modules/assistant-capability/assistant-capability-controller.js +549 -1
  18. package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
  19. package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +330 -2
  20. package/dist/server/modules/assistant-capability/assistant-capability-service.js +958 -3
  21. package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
  22. package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
  23. package/dist/server/modules/butler/assistant-automation-service.js +786 -0
  24. package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
  25. package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
  26. package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
  27. package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
  28. package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
  29. package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
  30. package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
  31. package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
  32. package/dist/server/modules/butler/butler-action-context-service.js +8 -2
  33. package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
  34. package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
  35. package/dist/server/modules/butler/butler-control-session-service.js +154 -40
  36. package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
  37. package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
  38. package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
  39. package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
  40. package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
  41. package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
  42. package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
  43. package/dist/server/modules/butler/butler-controller.d.ts +42 -2
  44. package/dist/server/modules/butler/butler-controller.js +79 -12
  45. package/dist/server/modules/butler/butler-controller.js.map +1 -1
  46. package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
  47. package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
  48. package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
  49. package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
  50. package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
  51. package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
  52. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +7 -6
  53. package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -1
  54. package/dist/server/modules/butler/butler-profile-service.js +2 -5
  55. package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
  56. package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
  57. package/dist/server/modules/butler/butler-project-service.js +7 -1
  58. package/dist/server/modules/butler/butler-project-service.js.map +1 -1
  59. package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
  60. package/dist/server/modules/butler/butler-session-service.js +12 -1
  61. package/dist/server/modules/butler/butler-session-service.js.map +1 -1
  62. package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
  63. package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
  64. package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
  65. package/dist/server/modules/butler/butler-workspace-context.js +182 -51
  66. package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
  67. package/dist/server/modules/butler/patrol-execution-service.js +2 -1
  68. package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
  69. package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
  70. package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
  71. package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
  72. package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
  73. package/dist/server/modules/butler/verification-run-service.js +188 -34
  74. package/dist/server/modules/butler/verification-run-service.js.map +1 -1
  75. package/dist/server/modules/debug-target/debug-target-controller.d.ts +13 -0
  76. package/dist/server/modules/debug-target/debug-target-controller.js +77 -2
  77. package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
  78. package/dist/server/modules/debug-target/debug-target-service.d.ts +17 -3
  79. package/dist/server/modules/debug-target/debug-target-service.js +696 -98
  80. package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
  81. package/dist/server/modules/git/git-command-helper-client.d.ts +3 -0
  82. package/dist/server/modules/git/git-command-helper-client.js +71 -29
  83. package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
  84. package/dist/server/modules/git/git-command-helper-process.js +62 -9
  85. package/dist/server/modules/git/git-command-helper-process.js.map +1 -1
  86. package/dist/server/modules/git/git-command-runner.d.ts +1 -0
  87. package/dist/server/modules/git/git-command-runner.js +44 -1
  88. package/dist/server/modules/git/git-command-runner.js.map +1 -1
  89. package/dist/server/modules/git/git-controller.js +8 -7
  90. package/dist/server/modules/git/git-controller.js.map +1 -1
  91. package/dist/server/modules/git/git-read-service.d.ts +7 -7
  92. package/dist/server/modules/git/git-read-service.js +41 -24
  93. package/dist/server/modules/git/git-read-service.js.map +1 -1
  94. package/dist/server/modules/model-switch/cc-switch-adapter.js +6 -2
  95. package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
  96. package/dist/server/modules/preferences/profile-service.d.ts +3 -1
  97. package/dist/server/modules/preferences/profile-service.js +74 -3
  98. package/dist/server/modules/preferences/profile-service.js.map +1 -1
  99. package/dist/server/modules/provider/codex-model-options.js +2 -3
  100. package/dist/server/modules/provider/codex-model-options.js.map +1 -1
  101. package/dist/server/modules/provider/opencode-model-options.js +2 -3
  102. package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
  103. package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +14 -7
  104. package/dist/server/modules/provider/provider-discovery-helper-client.js +208 -46
  105. package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
  106. package/dist/server/modules/provider/provider-discovery-helper-process.js +96 -47
  107. package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
  108. package/dist/server/modules/provider/provider-discovery-runtime.d.ts +4 -0
  109. package/dist/server/modules/provider/provider-discovery-runtime.js +145 -0
  110. package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -0
  111. package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
  112. package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
  113. package/dist/server/modules/sessions/session-history-service.d.ts +12 -3
  114. package/dist/server/modules/sessions/session-history-service.js +465 -67
  115. package/dist/server/modules/sessions/session-history-service.js.map +1 -1
  116. package/dist/server/modules/sessions/session-live-runtime-service.d.ts +8 -0
  117. package/dist/server/modules/sessions/session-live-runtime-service.js +164 -34
  118. package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
  119. package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
  120. package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
  121. package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
  122. package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
  123. package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
  124. package/dist/server/modules/skills/builtin-skill-service.d.ts +12 -0
  125. package/dist/server/modules/skills/builtin-skill-service.js +49 -0
  126. package/dist/server/modules/skills/builtin-skill-service.js.map +1 -0
  127. package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +82 -0
  128. package/dist/server/modules/skills/builtin-skills/codingns-assistant/agents/openai.yaml +4 -0
  129. package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +136 -0
  130. package/dist/server/modules/skills/skill-manager-service.d.ts +7 -0
  131. package/dist/server/modules/skills/skill-manager-service.js +98 -0
  132. package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
  133. package/dist/server/modules/tailscale/tailscale-helper-client.d.ts +1 -0
  134. package/dist/server/modules/tailscale/tailscale-helper-client.js +12 -0
  135. package/dist/server/modules/tailscale/tailscale-helper-client.js.map +1 -1
  136. package/dist/server/modules/tasks/task-helper-client.d.ts +10 -2
  137. package/dist/server/modules/tasks/task-helper-client.js +152 -27
  138. package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
  139. package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +10 -3
  140. package/dist/server/modules/tasks/task-helper-process-handlers.js +7 -5
  141. package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -1
  142. package/dist/server/modules/tasks/task-helper-process.js +104 -3
  143. package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
  144. package/dist/server/modules/tasks/task-lane-executors.js +2 -2
  145. package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
  146. package/dist/server/modules/tasks/task-types.d.ts +4 -0
  147. package/dist/server/modules/tasks/task-types.js +5 -1
  148. package/dist/server/modules/tasks/task-types.js.map +1 -1
  149. package/dist/server/modules/terminal/command-template-service.d.ts +11 -2
  150. package/dist/server/modules/terminal/command-template-service.js +91 -9
  151. package/dist/server/modules/terminal/command-template-service.js.map +1 -1
  152. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +1 -1
  153. package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -1
  154. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +160 -11
  155. package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -1
  156. package/dist/server/modules/terminal/template-port-runtime.d.ts +1 -1
  157. package/dist/server/modules/terminal/template-port-runtime.js +87 -37
  158. package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
  159. package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
  160. package/dist/server/modules/terminal/terminal-controller.js +41 -0
  161. package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
  162. package/dist/server/modules/terminal/terminal-service.d.ts +4 -0
  163. package/dist/server/modules/terminal/terminal-service.js +35 -1
  164. package/dist/server/modules/terminal/terminal-service.js.map +1 -1
  165. package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
  166. package/dist/server/modules/workbench/workbench-service.js +7 -6
  167. package/dist/server/modules/workbench/workbench-service.js.map +1 -1
  168. package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
  169. package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
  170. package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
  171. package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +3 -0
  172. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +149 -41
  173. package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
  174. package/dist/server/modules/workspace/workspace-code-composition.d.ts +1 -0
  175. package/dist/server/modules/workspace/workspace-code-composition.js +183 -1
  176. package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -1
  177. package/dist/server/modules/workspace/workspace-service.js +54 -17
  178. package/dist/server/modules/workspace/workspace-service.js.map +1 -1
  179. package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +1 -1
  180. package/dist/server/modules/worktree/worktree-cleanup-service.js +22 -17
  181. package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -1
  182. package/dist/server/modules/worktree/worktree-controller.js +6 -5
  183. package/dist/server/modules/worktree/worktree-controller.js.map +1 -1
  184. package/dist/server/modules/worktree/worktree-manager.d.ts +10 -2
  185. package/dist/server/modules/worktree/worktree-manager.js +35 -20
  186. package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
  187. package/dist/server/modules/worktree/worktree-merge-service.d.ts +2 -2
  188. package/dist/server/modules/worktree/worktree-merge-service.js +34 -27
  189. package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -1
  190. package/dist/server/modules/worktree/worktree-sync-service.d.ts +1 -1
  191. package/dist/server/modules/worktree/worktree-sync-service.js +5 -3
  192. package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -1
  193. package/dist/server/routes/assistant.js +43 -0
  194. package/dist/server/routes/assistant.js.map +1 -1
  195. package/dist/server/routes/butler.js +5 -0
  196. package/dist/server/routes/butler.js.map +1 -1
  197. package/dist/server/server/create-server.d.ts +8 -0
  198. package/dist/server/server/create-server.js +51 -13
  199. package/dist/server/server/create-server.js.map +1 -1
  200. package/dist/server/shared/http/request-abort.d.ts +2 -0
  201. package/dist/server/shared/http/request-abort.js +38 -0
  202. package/dist/server/shared/http/request-abort.js.map +1 -0
  203. package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
  204. package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
  205. package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
  206. package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
  207. package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
  208. package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
  209. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
  210. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
  211. package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
  212. package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
  213. package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
  214. package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
  215. package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
  216. package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
  217. package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
  218. package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
  219. package/dist/server/storage/sqlite/client.js +239 -2
  220. package/dist/server/storage/sqlite/client.js.map +1 -1
  221. package/dist/server/storage/sqlite/schema.sql +107 -1
  222. package/dist/server/types/domain.d.ts +89 -2
  223. package/dist/server/ws/workbench-ws-hub.d.ts +14 -7
  224. package/dist/server/ws/workbench-ws-hub.js +316 -158
  225. package/dist/server/ws/workbench-ws-hub.js.map +1 -1
  226. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
  227. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
  228. package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
  229. package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
  230. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
  231. package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
  232. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
  233. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
  234. package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
  235. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
  236. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
  237. package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
  238. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
  239. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
  240. package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
  241. package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
  242. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
  243. package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
  244. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
  245. package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
  246. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
  247. package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
  248. package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
  249. package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
  250. package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
  251. package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
  252. package/package.json +1 -1
  253. package/scripts/postinstall.mjs +33 -0
  254. package/dist/public/assets/index-Cek6u0b9.css +0 -1
  255. package/dist/public/assets/index-THHY79si.js +0 -122
@@ -4,7 +4,9 @@ import path from "node:path";
4
4
  import { AppError } from "../../shared/errors/app-error.js";
5
5
  import { createId } from "../../shared/utils/id.js";
6
6
  import { nowIso } from "../../shared/utils/time.js";
7
+ import { DEFAULT_DEBUG_PORT_POOLS } from "../preferences/profile-service.js";
7
8
  import { buildTemplateCommandLine, getShellEnterSequence } from "../terminal/terminal-shell.js";
9
+ import { discoverTemplateRuntimeStatuses } from "../terminal/template-port-runtime.js";
8
10
  import { createTaskManager } from "../tasks/task-manager.js";
9
11
  import { HOST_TASK_TYPES } from "../tasks/task-types.js";
10
12
  import { FRAMEWORK_COMPATIBILITY_MATRIX, FRAMEWORK_COMPATIBILITY_MATRIX_VERSION, getFrameworkCompatibilityItem } from "./framework-compatibility-matrix.js";
@@ -21,10 +23,11 @@ export class DebugTargetService {
21
23
  runtimeBindingRepository;
22
24
  aiFallbackEditRepository;
23
25
  terminalCommandTemplateRepository;
26
+ preferenceProfileService;
24
27
  terminalService;
25
28
  terminalInstanceRepository;
26
29
  taskManager;
27
- constructor(db, workspaceService, workspaceWorktreeRepository, debugTargetRepository, debugServiceRepository, frameworkAnalysisResultRepository, debugRuntimeSessionRepository, portLeaseRepository, runtimeBindingRepository, aiFallbackEditRepository, terminalCommandTemplateRepository, terminalService, terminalInstanceRepository, taskManager = createTaskManager()) {
30
+ constructor(db, workspaceService, workspaceWorktreeRepository, debugTargetRepository, debugServiceRepository, frameworkAnalysisResultRepository, debugRuntimeSessionRepository, portLeaseRepository, runtimeBindingRepository, aiFallbackEditRepository, terminalCommandTemplateRepository, preferenceProfileService, terminalService, terminalInstanceRepository, taskManager = createTaskManager()) {
28
31
  this.db = db;
29
32
  this.workspaceService = workspaceService;
30
33
  this.workspaceWorktreeRepository = workspaceWorktreeRepository;
@@ -36,6 +39,7 @@ export class DebugTargetService {
36
39
  this.runtimeBindingRepository = runtimeBindingRepository;
37
40
  this.aiFallbackEditRepository = aiFallbackEditRepository;
38
41
  this.terminalCommandTemplateRepository = terminalCommandTemplateRepository;
42
+ this.preferenceProfileService = preferenceProfileService;
39
43
  this.terminalService = terminalService;
40
44
  this.terminalInstanceRepository = terminalInstanceRepository;
41
45
  this.taskManager = taskManager;
@@ -134,7 +138,94 @@ export class DebugTargetService {
134
138
  items: FRAMEWORK_COMPATIBILITY_MATRIX
135
139
  };
136
140
  }
137
- async createLaunchPlan(targetId) {
141
+ async createLaunchPlan(targetId, portRequests = [], userId = null) {
142
+ return await this.createRegisteredTemplatePreviewPlan(targetId, portRequests, userId);
143
+ }
144
+ async run(input) {
145
+ const launchPlan = await this.createManagedRuntimeLaunchPlan(input.targetId, input.portRequests ?? [], input.userId);
146
+ if (!launchPlan.autoStartAllowed) {
147
+ const aiFallbackItem = launchPlan.services.find((item) => item.aiFallback?.eligible);
148
+ if (aiFallbackItem) {
149
+ await this.failRuntimePlan(launchPlan.runtimeSession.id, aiFallbackItem.failureStage ?? "ai_fallback_required", "当前服务需要进入受限 AI 兜底流程,自动运行已阻止", 409, "DEBUG_TARGET_AI_FALLBACK_REQUIRED");
150
+ }
151
+ await this.failRuntimePlan(launchPlan.runtimeSession.id, launchPlan.services.find((item) => item.failureStage)?.failureStage ?? "launch_requirements", "当前启动计划缺少必要的服务发现、HMR 或 callback 处理,暂不允许自动启动", 409, "DEBUG_TARGET_RUN_NOT_ALLOWED");
152
+ }
153
+ const startedServices = [];
154
+ try {
155
+ for (const item of launchPlan.services) {
156
+ const service = this.getServiceOrThrow(item.serviceId, input.targetId);
157
+ const analysis = this.frameworkAnalysisResultRepository
158
+ .listByTargetId(input.targetId)
159
+ .find((candidate) => candidate.id === item.frameworkAnalysisId) ?? null;
160
+ const terminal = await this.terminalService.createTerminal({
161
+ workspaceId: this.getTargetOrThrow(input.targetId).workspaceId,
162
+ name: `${service.name} 运行`,
163
+ cwd: service.cwd,
164
+ shell: input.shell,
165
+ runtimeType: input.runtimeType,
166
+ createdByUserId: input.userId,
167
+ env: {
168
+ ...service.env,
169
+ ...item.envPatch
170
+ },
171
+ debugRuntimeSessionId: launchPlan.runtimeSession.id,
172
+ debugTargetId: input.targetId,
173
+ debugServiceId: service.id,
174
+ frameworkAnalysisId: analysis?.id ?? null,
175
+ launcherSourceType: "debug_service",
176
+ launchStage: "command_dispatched",
177
+ failureStage: null,
178
+ adapterKind: item.adapterKind ?? null,
179
+ envPatchSummary: item.envPatch,
180
+ artifactRef: item.artifactRef
181
+ });
182
+ const commandLine = buildTemplateCommandLine(buildEphemeralTemplate(service, item, input.runtimeType ?? terminal.runtimeType), terminal.shell);
183
+ await this.terminalService.writeInput(terminal.id, `${commandLine}${getShellEnterSequence(terminal.shell)}`);
184
+ this.runtimeBindingRepository.update({
185
+ id: item.runtimeBindingId,
186
+ runtimeId: launchPlan.runtimeSession.id,
187
+ serviceId: service.id,
188
+ processInstanceId: terminal.id,
189
+ expectedPort: item.expectedPort,
190
+ leasedPort: item.leasedPort,
191
+ observedPort: null,
192
+ proxyPath: null,
193
+ status: "ALLOCATED",
194
+ updatedAt: nowIso()
195
+ });
196
+ startedServices.push({
197
+ serviceId: service.id,
198
+ processInstanceId: terminal.id,
199
+ terminalId: terminal.id,
200
+ leasedPort: item.leasedPort,
201
+ runtimeBindingId: item.runtimeBindingId
202
+ });
203
+ }
204
+ }
205
+ catch (error) {
206
+ for (const service of startedServices) {
207
+ try {
208
+ await this.terminalService.closeTerminal(service.terminalId);
209
+ }
210
+ catch {
211
+ // 失败回滚阶段只做尽力而为,避免覆盖原始错误。
212
+ }
213
+ }
214
+ await this.failRuntimePlan(launchPlan.runtimeSession.id, "command_execution", error instanceof Error ? error.message : "调试目标启动失败", 500, "DEBUG_TARGET_RUN_FAILED");
215
+ }
216
+ const updatedRuntimeSession = this.debugRuntimeSessionRepository.update({
217
+ ...launchPlan.runtimeSession,
218
+ status: "RUNNING",
219
+ failureStage: null,
220
+ startedAt: nowIso(),
221
+ updatedAt: nowIso()
222
+ });
223
+ return {
224
+ runtimeSession: updatedRuntimeSession ?? launchPlan.runtimeSession,
225
+ services: startedServices
226
+ };
227
+ }
228
+ async createManagedRuntimeLaunchPlan(targetId, portRequests, userId) {
138
229
  const target = this.getTargetOrThrow(targetId);
139
230
  const services = this.debugServiceRepository.listByTargetId(targetId);
140
231
  const analyses = this.frameworkAnalysisResultRepository.listByTargetId(targetId);
@@ -160,6 +251,13 @@ export class DebugTargetService {
160
251
  updatedAt: timestamp
161
252
  });
162
253
  const planItems = [];
254
+ const requestedPorts = await resolveRequestedPorts({
255
+ targetRootPath: target.rootPath,
256
+ services,
257
+ portRequests,
258
+ portLeaseRepository: this.portLeaseRepository
259
+ });
260
+ const portPoolConfig = this.resolvePortPoolConfig(userId);
163
261
  for (const service of services) {
164
262
  const analysis = analysisByServiceId.get(service.id) ?? analyses[0] ?? null;
165
263
  if (!analysis) {
@@ -169,7 +267,8 @@ export class DebugTargetService {
169
267
  detail: "调试服务缺少框架分析结果,无法生成启动计划"
170
268
  });
171
269
  }
172
- const allocatedPort = await allocateManagedPort(service.role, this.portLeaseRepository);
270
+ const allocatedPort = requestedPorts.get(service.id)
271
+ ?? await allocateManagedPort(service.role, this.portLeaseRepository, portPoolConfig);
173
272
  const injectionPlan = resolveLaunchPlan({
174
273
  targetRootPath: target.rootPath,
175
274
  service,
@@ -262,90 +361,60 @@ export class DebugTargetService {
262
361
  services: planItems
263
362
  };
264
363
  }
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
- }
364
+ async createRegisteredTemplatePreviewPlan(targetId, portRequests, userId) {
365
+ const target = this.getTargetOrThrow(targetId);
366
+ const analyses = this.frameworkAnalysisResultRepository.listByTargetId(targetId);
367
+ if (analyses.length === 0) {
368
+ throw new AppError({
369
+ statusCode: 409,
370
+ errorCode: "DEBUG_TARGET_ANALYSIS_REQUIRED",
371
+ detail: "当前调试目标还没有可用的框架分析结果,请先执行分析"
372
+ });
325
373
  }
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");
374
+ const timestamp = nowIso();
375
+ const runtimeSession = buildPreviewRuntimeSession(target.id, timestamp);
376
+ const templates = listRegisteredTemplatesForTarget(this.terminalCommandTemplateRepository.listByWorkspace(target.workspaceId), target.rootPath);
377
+ if (templates.length === 0) {
378
+ return await this.createManagedRuntimeLaunchPlan(targetId, portRequests, userId);
336
379
  }
337
- const updatedRuntimeSession = this.debugRuntimeSessionRepository.update({
338
- ...launchPlan.runtimeSession,
339
- status: "RUNNING",
340
- failureStage: null,
341
- startedAt: nowIso(),
342
- updatedAt: nowIso()
380
+ const services = this.debugServiceRepository.listByTargetId(targetId);
381
+ const runtimeStatuses = await discoverTemplateRuntimeStatuses(templates
382
+ .filter((template) => template.port !== null)
383
+ .map((template) => ({
384
+ templateId: template.id,
385
+ port: template.port
386
+ })));
387
+ const runtimeStatusByTemplateId = new Map(runtimeStatuses.map((item) => [item.templateId, item]));
388
+ const portPoolConfig = this.resolvePortPoolConfig(userId);
389
+ const requestedPorts = await resolveTemplateRequestedPorts({
390
+ targetRootPath: target.rootPath,
391
+ templates,
392
+ portRequests,
393
+ portLeaseRepository: this.portLeaseRepository
394
+ });
395
+ const templatePlans = await buildRegisteredTemplatePlanItems({
396
+ target,
397
+ templates,
398
+ services,
399
+ analyses,
400
+ runtimeStatusByTemplateId,
401
+ requestedPorts,
402
+ portPoolConfig,
403
+ portLeaseRepository: this.portLeaseRepository
343
404
  });
344
405
  return {
345
- runtimeSession: updatedRuntimeSession ?? launchPlan.runtimeSession,
346
- services: startedServices
406
+ runtimeSession,
407
+ targetId: target.id,
408
+ autoStartAllowed: templatePlans.length > 0 && templatePlans.every((item) => item.autoStartAllowed),
409
+ services: templatePlans
347
410
  };
348
411
  }
412
+ resolvePortPoolConfig(userId) {
413
+ if (!userId) {
414
+ return clonePortPoolConfig(DEFAULT_DEBUG_PORT_POOLS);
415
+ }
416
+ return clonePortPoolConfig(this.preferenceProfileService.getProfile(userId).debugPortPools);
417
+ }
349
418
  async handleTerminalExit(event) {
350
419
  const runtimeId = event.terminal.debugRuntimeSessionId ?? null;
351
420
  if (!runtimeId) {
@@ -1551,10 +1620,447 @@ function resolveMissingRequirements(analysis) {
1551
1620
  function isFrameworkEligible(level) {
1552
1621
  return level === "supported" || level === "conditional";
1553
1622
  }
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;
1623
+ async function buildRegisteredTemplatePlanItems(input) {
1624
+ const matchedContexts = input.templates.map((template) => matchTemplateAnalysisContext(template, input.services, input.analyses));
1625
+ const configuredPortCount = new Map();
1626
+ const plannedPortByTemplateId = new Map();
1627
+ const usedPorts = new Set();
1628
+ for (const template of input.templates) {
1629
+ if (template.port !== null) {
1630
+ configuredPortCount.set(template.port, (configuredPortCount.get(template.port) ?? 0) + 1);
1631
+ }
1632
+ }
1633
+ for (const context of matchedContexts) {
1634
+ const requestedPort = input.requestedPorts.get(context.template.id);
1635
+ if (requestedPort !== undefined) {
1636
+ plannedPortByTemplateId.set(context.template.id, requestedPort);
1637
+ usedPorts.add(requestedPort);
1638
+ continue;
1639
+ }
1640
+ if (context.template.port === null) {
1641
+ plannedPortByTemplateId.set(context.template.id, null);
1642
+ continue;
1643
+ }
1644
+ const runtimeStatus = input.runtimeStatusByTemplateId.get(context.template.id) ?? null;
1645
+ const needsOrchestration = input.target.sourceType === "worktree"
1646
+ || (configuredPortCount.get(context.template.port) ?? 0) > 1
1647
+ || runtimeStatus?.occupied === true
1648
+ || input.portLeaseRepository.findActiveByPort(context.template.port, "tcp") !== null
1649
+ || usedPorts.has(context.template.port);
1650
+ if (!needsOrchestration) {
1651
+ plannedPortByTemplateId.set(context.template.id, context.template.port);
1652
+ usedPorts.add(context.template.port);
1653
+ continue;
1654
+ }
1655
+ const allocatedPort = await allocateManagedPort(context.role, input.portLeaseRepository, input.portPoolConfig, usedPorts);
1656
+ plannedPortByTemplateId.set(context.template.id, allocatedPort);
1657
+ usedPorts.add(allocatedPort);
1658
+ }
1659
+ const backendEndpoint = resolveRegisteredTemplateBackendEndpoint(matchedContexts, plannedPortByTemplateId);
1660
+ return matchedContexts.map((context) => {
1661
+ const plannedPort = plannedPortByTemplateId.get(context.template.id) ?? null;
1662
+ const runtimeBindingId = createId();
1663
+ const analysis = context.analysis;
1664
+ if (!analysis) {
1665
+ return {
1666
+ serviceId: context.template.id,
1667
+ role: context.role,
1668
+ frameworkAnalysisId: null,
1669
+ primaryFramework: null,
1670
+ compatibilityLevel: "unknown",
1671
+ adapterKind: context.template.adapterKind ?? null,
1672
+ injectionMode: context.template.injectionMode ?? null,
1673
+ command: context.template.command,
1674
+ args: context.template.args,
1675
+ envPatch: {},
1676
+ expectedPort: context.template.port,
1677
+ leasedPort: plannedPort,
1678
+ artifactRef: context.template.generatedArtifactRef ?? null,
1679
+ runtimeBindingId,
1680
+ portLeaseId: null,
1681
+ requiresServiceDiscoveryHandling: false,
1682
+ requiresHmrHandling: false,
1683
+ requiresCallbackHandling: false,
1684
+ failureStage: "framework_analysis_missing",
1685
+ adapterAttempts: [],
1686
+ aiFallback: null,
1687
+ missingRequirements: ["analysis"],
1688
+ autoStartAllowed: false
1689
+ };
1690
+ }
1691
+ if (plannedPort === null) {
1692
+ return {
1693
+ serviceId: context.template.id,
1694
+ role: context.role,
1695
+ frameworkAnalysisId: analysis.id,
1696
+ primaryFramework: analysis.primaryFramework ?? null,
1697
+ compatibilityLevel: analysis.compatibilityLevel,
1698
+ adapterKind: context.template.adapterKind ?? null,
1699
+ injectionMode: context.template.injectionMode ?? null,
1700
+ command: context.template.command,
1701
+ args: context.template.args,
1702
+ envPatch: {},
1703
+ expectedPort: context.template.port,
1704
+ leasedPort: null,
1705
+ artifactRef: context.template.generatedArtifactRef ?? null,
1706
+ runtimeBindingId,
1707
+ portLeaseId: null,
1708
+ requiresServiceDiscoveryHandling: analysis.requiresServiceDiscoveryHandling,
1709
+ requiresHmrHandling: analysis.requiresHmrHandling,
1710
+ requiresCallbackHandling: analysis.requiresCallbackHandling,
1711
+ failureStage: "port_unconfigured",
1712
+ adapterAttempts: [],
1713
+ aiFallback: null,
1714
+ missingRequirements: ["port"],
1715
+ autoStartAllowed: false
1716
+ };
1717
+ }
1718
+ const injectionPlan = resolveLaunchPlan({
1719
+ targetRootPath: input.target.rootPath,
1720
+ service: buildTemplateBackedDebugService(context.template, context.role, analysis.id),
1721
+ analysis,
1722
+ leasedPort: plannedPort
1723
+ });
1724
+ const serviceDiscoveryEnvPatch = buildServiceDiscoveryEnvPatch({
1725
+ role: context.role,
1726
+ analysis,
1727
+ backendEndpoint
1728
+ });
1729
+ const missingRequirements = resolveRegisteredTemplateMissingRequirements(analysis, {
1730
+ role: context.role,
1731
+ backendEndpoint
1732
+ });
1733
+ const envPatch = {
1734
+ ...injectionPlan.envPatch,
1735
+ ...serviceDiscoveryEnvPatch
1736
+ };
1737
+ const autoStartAllowed = isFrameworkEligible(analysis.compatibilityLevel)
1738
+ && missingRequirements.length === 0
1739
+ && injectionPlan.adapterKind !== "ai_fallback"
1740
+ && injectionPlan.adapterKind !== null;
1741
+ return {
1742
+ serviceId: context.template.id,
1743
+ role: context.role,
1744
+ frameworkAnalysisId: analysis.id,
1745
+ primaryFramework: analysis.primaryFramework ?? null,
1746
+ compatibilityLevel: analysis.compatibilityLevel,
1747
+ adapterKind: injectionPlan.adapterKind ?? null,
1748
+ injectionMode: injectionPlan.injectionMode ?? null,
1749
+ command: context.template.command,
1750
+ args: injectionPlan.args,
1751
+ envPatch,
1752
+ expectedPort: context.template.port,
1753
+ leasedPort: plannedPort,
1754
+ artifactRef: injectionPlan.artifactRef ?? context.template.generatedArtifactRef ?? null,
1755
+ runtimeBindingId,
1756
+ portLeaseId: null,
1757
+ requiresServiceDiscoveryHandling: analysis.requiresServiceDiscoveryHandling,
1758
+ requiresHmrHandling: analysis.requiresHmrHandling,
1759
+ requiresCallbackHandling: analysis.requiresCallbackHandling,
1760
+ failureStage: injectionPlan.failureStage,
1761
+ adapterAttempts: injectionPlan.adapterAttempts,
1762
+ aiFallback: injectionPlan.aiFallback,
1763
+ missingRequirements,
1764
+ autoStartAllowed
1765
+ };
1766
+ });
1767
+ }
1768
+ function buildPreviewRuntimeSession(targetId, timestamp) {
1769
+ return {
1770
+ id: `preview-${createId()}`,
1771
+ targetId,
1772
+ status: "PREPARING",
1773
+ failureStage: null,
1774
+ startedAt: null,
1775
+ stoppedAt: null,
1776
+ createdAt: timestamp,
1777
+ updatedAt: timestamp
1778
+ };
1779
+ }
1780
+ function listRegisteredTemplatesForTarget(templates, rootPath) {
1781
+ return templates
1782
+ .filter((template) => template.managedBySystem !== true)
1783
+ .filter((template) => isTemplateRelevantToRootPath(template, rootPath))
1784
+ .sort((left, right) => compareTemplatePriority(left, right, rootPath));
1785
+ }
1786
+ function matchTemplateAnalysisContext(template, services, analyses) {
1787
+ const analysisByServiceId = new Map(analyses
1788
+ .filter((analysis) => analysis.serviceId)
1789
+ .map((analysis) => [analysis.serviceId, analysis]));
1790
+ const normalizedTemplateCwd = path.resolve(template.cwd);
1791
+ const templateRole = inferTemplateRole(template);
1792
+ const matchedService = [...services]
1793
+ .sort((left, right) => compareServiceMatchPriority(left, right, normalizedTemplateCwd, templateRole))
1794
+ .find((service) => {
1795
+ const serviceCwd = path.resolve(service.cwd);
1796
+ return serviceCwd === normalizedTemplateCwd
1797
+ || isPathWithin(normalizedTemplateCwd, serviceCwd)
1798
+ || isPathWithin(serviceCwd, normalizedTemplateCwd);
1799
+ }) ?? null;
1800
+ const analysis = matchedService ? analysisByServiceId.get(matchedService.id) ?? null : null;
1801
+ return {
1802
+ template,
1803
+ role: matchedService?.role ?? templateRole,
1804
+ service: matchedService,
1805
+ analysis
1806
+ };
1807
+ }
1808
+ function compareServiceMatchPriority(left, right, templateCwd, templateRole) {
1809
+ const leftExact = Number(path.resolve(left.cwd) === templateCwd);
1810
+ const rightExact = Number(path.resolve(right.cwd) === templateCwd);
1811
+ if (rightExact !== leftExact) {
1812
+ return rightExact - leftExact;
1813
+ }
1814
+ const leftRoleMatch = Number(left.role === templateRole);
1815
+ const rightRoleMatch = Number(right.role === templateRole);
1816
+ if (rightRoleMatch !== leftRoleMatch) {
1817
+ return rightRoleMatch - leftRoleMatch;
1818
+ }
1819
+ const leftDistance = resolveRelativePathDistance(templateCwd, left.cwd);
1820
+ const rightDistance = resolveRelativePathDistance(templateCwd, right.cwd);
1821
+ if (leftDistance !== rightDistance) {
1822
+ return leftDistance - rightDistance;
1823
+ }
1824
+ return left.name.localeCompare(right.name);
1825
+ }
1826
+ function resolveRelativePathDistance(leftPath, rightPath) {
1827
+ const normalizedLeft = withTrailingSeparator(path.resolve(leftPath));
1828
+ const normalizedRight = withTrailingSeparator(path.resolve(rightPath));
1829
+ if (normalizedLeft === normalizedRight) {
1830
+ return 0;
1831
+ }
1832
+ if (normalizedLeft.startsWith(normalizedRight) || normalizedRight.startsWith(normalizedLeft)) {
1833
+ return 1;
1834
+ }
1835
+ return 2;
1836
+ }
1837
+ function inferTemplateRole(template) {
1838
+ const signal = [
1839
+ template.name,
1840
+ template.cwd,
1841
+ template.command,
1842
+ ...template.args
1843
+ ].join(" ").toLowerCase();
1844
+ if (signal.includes("frontend")
1845
+ || signal.includes("web")
1846
+ || signal.includes("ui")
1847
+ || signal.includes("vite")
1848
+ || signal.includes("next")
1849
+ || signal.includes("astro")
1850
+ || signal.includes("nuxt")
1851
+ || signal.includes("vue")
1852
+ || signal.includes("remix")) {
1853
+ return "frontend";
1854
+ }
1855
+ if (signal.includes("worker")) {
1856
+ return "worker";
1857
+ }
1858
+ if (signal.includes("mock")) {
1859
+ return "mock";
1860
+ }
1861
+ if (signal.includes("backend")
1862
+ || signal.includes("server")
1863
+ || signal.includes("api")
1864
+ || signal.includes("host")
1865
+ || signal.includes("express")
1866
+ || signal.includes("nestjs")
1867
+ || signal.includes("koa")
1868
+ || signal.includes("hono")
1869
+ || signal.includes("uvicorn")
1870
+ || signal.includes("flask")
1871
+ || signal.includes("django")
1872
+ || signal.includes("spring")
1873
+ || signal.includes("rails")
1874
+ || signal.includes("dotnet")) {
1875
+ return "backend";
1876
+ }
1877
+ return "custom";
1878
+ }
1879
+ function buildTemplateBackedDebugService(template, role, frameworkAnalysisId) {
1880
+ return {
1881
+ id: template.id,
1882
+ targetId: template.debugTargetId ?? "",
1883
+ role,
1884
+ name: template.name,
1885
+ cwd: template.cwd,
1886
+ command: template.command,
1887
+ args: template.args,
1888
+ env: template.env,
1889
+ defaultPortHint: template.port,
1890
+ protocol: "http",
1891
+ healthPath: null,
1892
+ adapterKind: template.adapterKind ?? null,
1893
+ frameworkAnalysisId,
1894
+ createdAt: template.createdAt,
1895
+ updatedAt: template.updatedAt
1896
+ };
1897
+ }
1898
+ function resolveRegisteredTemplateBackendEndpoint(contexts, plannedPortByTemplateId) {
1899
+ const candidate = contexts.find((context) => context.role === "backend" && plannedPortByTemplateId.get(context.template.id) !== null);
1900
+ if (!candidate) {
1901
+ return null;
1902
+ }
1903
+ return `http://127.0.0.1:${plannedPortByTemplateId.get(candidate.template.id)}`;
1904
+ }
1905
+ function buildServiceDiscoveryEnvPatch(input) {
1906
+ if (input.role === "backend" || !input.analysis.requiresServiceDiscoveryHandling || !input.backendEndpoint) {
1907
+ return {};
1908
+ }
1909
+ switch (input.analysis.primaryFramework) {
1910
+ case "vite":
1911
+ return {
1912
+ VITE_API_BASE_URL: input.backendEndpoint,
1913
+ API_BASE_URL: input.backendEndpoint
1914
+ };
1915
+ case "nextjs":
1916
+ return {
1917
+ NEXT_PUBLIC_API_BASE_URL: input.backendEndpoint,
1918
+ API_BASE_URL: input.backendEndpoint
1919
+ };
1920
+ case "cra":
1921
+ return {
1922
+ REACT_APP_API_BASE_URL: input.backendEndpoint,
1923
+ API_BASE_URL: input.backendEndpoint
1924
+ };
1925
+ case "astro":
1926
+ return {
1927
+ PUBLIC_API_BASE_URL: input.backendEndpoint,
1928
+ API_BASE_URL: input.backendEndpoint
1929
+ };
1930
+ case "nuxt":
1931
+ return {
1932
+ NUXT_PUBLIC_API_BASE_URL: input.backendEndpoint,
1933
+ API_BASE_URL: input.backendEndpoint
1934
+ };
1935
+ case "vue-cli":
1936
+ return {
1937
+ VUE_APP_API_BASE_URL: input.backendEndpoint,
1938
+ API_BASE_URL: input.backendEndpoint
1939
+ };
1940
+ case "remix":
1941
+ return {
1942
+ API_BASE_URL: input.backendEndpoint,
1943
+ PUBLIC_API_BASE_URL: input.backendEndpoint
1944
+ };
1945
+ default:
1946
+ return {
1947
+ API_BASE_URL: input.backendEndpoint,
1948
+ BACKEND_BASE_URL: input.backendEndpoint
1949
+ };
1950
+ }
1951
+ }
1952
+ function resolveRegisteredTemplateMissingRequirements(analysis, input) {
1953
+ const missing = [];
1954
+ if (analysis.requiresServiceDiscoveryHandling
1955
+ && input.role !== "backend"
1956
+ && !input.backendEndpoint) {
1957
+ missing.push("service_discovery");
1958
+ }
1959
+ if (analysis.requiresCallbackHandling) {
1960
+ missing.push("callback");
1961
+ }
1962
+ return missing;
1963
+ }
1964
+ async function resolveTemplateRequestedPorts(input) {
1965
+ const requestedPorts = new Map();
1966
+ const usedPorts = new Map();
1967
+ for (const request of input.portRequests) {
1968
+ validatePortRequestShape(request);
1969
+ const matches = input.templates.filter((template) => matchesTemplatePortRequest(template, input.targetRootPath, request));
1970
+ if (matches.length === 0) {
1971
+ throw new AppError({
1972
+ statusCode: 400,
1973
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_UNMATCHED",
1974
+ detail: `显式端口请求没有匹配到任何启动项:${describePortRequest(request)}`,
1975
+ field: "portRequests"
1976
+ });
1977
+ }
1978
+ if (matches.length > 1) {
1979
+ throw new AppError({
1980
+ statusCode: 409,
1981
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_AMBIGUOUS",
1982
+ detail: `显式端口请求匹配到了多个启动项:${describePortRequest(request)}`,
1983
+ field: "portRequests"
1984
+ });
1985
+ }
1986
+ const matchedTemplate = matches[0];
1987
+ const previousPort = requestedPorts.get(matchedTemplate.id);
1988
+ if (previousPort !== undefined && previousPort !== request.port) {
1989
+ throw new AppError({
1990
+ statusCode: 409,
1991
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_CONFLICT",
1992
+ detail: `同一个启动项收到了多个不同的显式端口请求:${matchedTemplate.name}`,
1993
+ field: "portRequests"
1994
+ });
1995
+ }
1996
+ const occupiedBy = usedPorts.get(request.port);
1997
+ if (occupiedBy && occupiedBy !== matchedTemplate.id) {
1998
+ throw new AppError({
1999
+ statusCode: 409,
2000
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_CONFLICT",
2001
+ detail: `多个启动项请求了同一个端口:${request.port}`,
2002
+ field: "portRequests"
2003
+ });
2004
+ }
2005
+ await ensureRequestedPortAvailable(request.port, inferTemplateRole(matchedTemplate), input.portLeaseRepository);
2006
+ requestedPorts.set(matchedTemplate.id, request.port);
2007
+ usedPorts.set(request.port, matchedTemplate.id);
2008
+ }
2009
+ return requestedPorts;
2010
+ }
2011
+ async function resolveRequestedPorts(input) {
2012
+ const requestedPorts = new Map();
2013
+ const usedPorts = new Map();
2014
+ for (const request of input.portRequests) {
2015
+ validatePortRequestShape(request);
2016
+ const matches = input.services.filter((service) => matchesPortRequest(service, input.targetRootPath, request));
2017
+ if (matches.length === 0) {
2018
+ throw new AppError({
2019
+ statusCode: 400,
2020
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_UNMATCHED",
2021
+ detail: `显式端口请求没有匹配到任何服务:${describePortRequest(request)}`,
2022
+ field: "portRequests"
2023
+ });
2024
+ }
2025
+ if (matches.length > 1) {
2026
+ throw new AppError({
2027
+ statusCode: 409,
2028
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_AMBIGUOUS",
2029
+ detail: `显式端口请求匹配到了多个服务:${describePortRequest(request)}`,
2030
+ field: "portRequests"
2031
+ });
2032
+ }
2033
+ const matchedService = matches[0];
2034
+ const previousPort = requestedPorts.get(matchedService.id);
2035
+ if (previousPort !== undefined && previousPort !== request.port) {
2036
+ throw new AppError({
2037
+ statusCode: 409,
2038
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_CONFLICT",
2039
+ detail: `同一个服务收到了多个不同的显式端口请求:${matchedService.name}`,
2040
+ field: "portRequests"
2041
+ });
2042
+ }
2043
+ const occupiedBy = usedPorts.get(request.port);
2044
+ if (occupiedBy && occupiedBy !== matchedService.id) {
2045
+ throw new AppError({
2046
+ statusCode: 409,
2047
+ errorCode: "DEBUG_TARGET_PORT_REQUEST_CONFLICT",
2048
+ detail: `多个服务请求了同一个端口:${request.port}`,
2049
+ field: "portRequests"
2050
+ });
2051
+ }
2052
+ await ensureRequestedPortAvailable(request.port, matchedService.role, input.portLeaseRepository);
2053
+ requestedPorts.set(matchedService.id, request.port);
2054
+ usedPorts.set(request.port, matchedService.id);
2055
+ }
2056
+ return requestedPorts;
2057
+ }
2058
+ async function allocateManagedPort(role, portLeaseRepository, portPoolConfig, usedPorts = new Set()) {
2059
+ const range = portPoolConfig;
2060
+ for (let port = range.start; port <= range.end; port += 1) {
2061
+ if (usedPorts.has(port)) {
2062
+ continue;
2063
+ }
1558
2064
  if (portLeaseRepository.findActiveByPort(port, "tcp")) {
1559
2065
  continue;
1560
2066
  }
@@ -1566,21 +2072,25 @@ async function allocateManagedPort(role, portLeaseRepository) {
1566
2072
  throw new AppError({
1567
2073
  statusCode: 409,
1568
2074
  errorCode: "PORT_LEASE_EXHAUSTED",
1569
- detail: `当前服务角色没有可分配的空闲端口:${role}`
2075
+ detail: `当前服务角色没有可分配的空闲端口:${role}(范围 ${range.start}-${range.end})`
1570
2076
  });
1571
2077
  }
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;
2078
+ async function ensureRequestedPortAvailable(port, role, portLeaseRepository) {
2079
+ if (portLeaseRepository.findActiveByPort(port, "tcp")) {
2080
+ throw new AppError({
2081
+ statusCode: 409,
2082
+ errorCode: "DEBUG_TARGET_PORT_NOT_AVAILABLE",
2083
+ detail: `显式请求的端口已被平台中的其他运行时占用:${port}`,
2084
+ field: "portRequests"
2085
+ });
2086
+ }
2087
+ if (!(await isPortAvailable(port))) {
2088
+ throw new AppError({
2089
+ statusCode: 409,
2090
+ errorCode: "DEBUG_TARGET_PORT_NOT_AVAILABLE",
2091
+ detail: `显式请求的端口当前不可用:${port}(服务角色:${role})`,
2092
+ field: "portRequests"
2093
+ });
1584
2094
  }
1585
2095
  }
1586
2096
  async function isPortAvailable(port) {
@@ -1629,6 +2139,94 @@ function resolveProcessInstance(processInstanceId, terminalInstanceRepository) {
1629
2139
  }
1630
2140
  return terminalInstanceRepository.findById(processInstanceId);
1631
2141
  }
2142
+ function validatePortRequestShape(request) {
2143
+ if (!Number.isInteger(request.port) || request.port < 1 || request.port > 65535) {
2144
+ throw new AppError({
2145
+ statusCode: 400,
2146
+ errorCode: "INVALID_INPUT",
2147
+ detail: `显式端口请求非法,port 必须是 1 到 65535 之间的整数:${request.port}`,
2148
+ field: "portRequests"
2149
+ });
2150
+ }
2151
+ const hasSelector = Boolean(request.serviceId?.trim()
2152
+ || request.role?.trim()
2153
+ || request.cwd?.trim()
2154
+ || request.name?.trim()
2155
+ || request.command?.trim());
2156
+ if (!hasSelector) {
2157
+ throw new AppError({
2158
+ statusCode: 400,
2159
+ errorCode: "INVALID_INPUT",
2160
+ detail: "显式端口请求至少要提供 serviceId、role、cwd、name、command 中的一个选择条件",
2161
+ field: "portRequests"
2162
+ });
2163
+ }
2164
+ }
2165
+ function matchesPortRequest(service, targetRootPath, request) {
2166
+ if (request.serviceId?.trim() && request.serviceId.trim() !== service.id) {
2167
+ return false;
2168
+ }
2169
+ if (request.role?.trim() && request.role.trim() !== service.role) {
2170
+ return false;
2171
+ }
2172
+ if (request.name?.trim() && request.name.trim() !== service.name) {
2173
+ return false;
2174
+ }
2175
+ if (request.command?.trim() && request.command.trim() !== service.command) {
2176
+ return false;
2177
+ }
2178
+ if (request.cwd?.trim()) {
2179
+ const selectorPath = normalizePortRequestCwd(request.cwd, targetRootPath);
2180
+ const servicePath = path.resolve(service.cwd);
2181
+ if (selectorPath !== servicePath) {
2182
+ return false;
2183
+ }
2184
+ }
2185
+ return true;
2186
+ }
2187
+ function matchesTemplatePortRequest(template, targetRootPath, request) {
2188
+ if (request.serviceId?.trim() && request.serviceId.trim() !== template.id) {
2189
+ return false;
2190
+ }
2191
+ if (request.role?.trim() && request.role.trim() !== inferTemplateRole(template)) {
2192
+ return false;
2193
+ }
2194
+ if (request.name?.trim() && request.name.trim() !== template.name) {
2195
+ return false;
2196
+ }
2197
+ if (request.command?.trim() && request.command.trim() !== template.command) {
2198
+ return false;
2199
+ }
2200
+ if (request.cwd?.trim()) {
2201
+ const selectorPath = normalizePortRequestCwd(request.cwd, targetRootPath);
2202
+ const templatePath = path.resolve(template.cwd);
2203
+ if (selectorPath !== templatePath) {
2204
+ return false;
2205
+ }
2206
+ }
2207
+ return true;
2208
+ }
2209
+ function clonePortPoolConfig(config) {
2210
+ return { ...config };
2211
+ }
2212
+ function normalizePortRequestCwd(value, targetRootPath) {
2213
+ const trimmed = value?.trim() || "";
2214
+ if (!trimmed) {
2215
+ return "";
2216
+ }
2217
+ return path.resolve(path.isAbsolute(trimmed) ? trimmed : path.join(targetRootPath, trimmed));
2218
+ }
2219
+ function describePortRequest(request) {
2220
+ const parts = [
2221
+ request.serviceId?.trim() ? `serviceId=${request.serviceId.trim()}` : null,
2222
+ request.role?.trim() ? `role=${request.role.trim()}` : null,
2223
+ request.cwd?.trim() ? `cwd=${request.cwd.trim()}` : null,
2224
+ request.name?.trim() ? `name=${request.name.trim()}` : null,
2225
+ request.command?.trim() ? `command=${request.command.trim()}` : null,
2226
+ `port=${request.port}`
2227
+ ].filter((item) => Boolean(item));
2228
+ return parts.join(", ");
2229
+ }
1632
2230
  function resolveAiFallbackNextStatus(action) {
1633
2231
  switch (action) {
1634
2232
  case "apply":