@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.
- package/README.md +16 -0
- package/bin/codingns.mjs +1369 -10
- package/dist/public/assets/{TerminalPage-CgrfstRm.js → TerminalPage-6jHZV9Mh.js} +17 -17
- package/dist/public/assets/index-CSVhg7I8.js +123 -0
- package/dist/public/assets/index-Ce1VX19m.css +1 -0
- package/dist/public/index.html +2 -2
- package/dist/server/config/opencode-base-url-resolver.d.ts +7 -0
- package/dist/server/config/opencode-base-url-resolver.js +48 -11
- package/dist/server/config/opencode-base-url-resolver.js.map +1 -1
- package/dist/server/config/opencode-system-probe-helper-client.d.ts +4 -0
- package/dist/server/config/opencode-system-probe-helper-client.js +29 -0
- package/dist/server/config/opencode-system-probe-helper-client.js.map +1 -1
- package/dist/server/config/opencode-system-probe-helper-process.d.ts +12 -0
- package/dist/server/config/opencode-system-probe-helper-process.js +34 -7
- package/dist/server/config/opencode-system-probe-helper-process.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.d.ts +317 -0
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js +549 -1
- package/dist/server/modules/assistant-capability/assistant-capability-controller.js.map +1 -1
- package/dist/server/modules/assistant-capability/assistant-capability-service.d.ts +330 -2
- package/dist/server/modules/assistant-capability/assistant-capability-service.js +958 -3
- package/dist/server/modules/assistant-capability/assistant-capability-service.js.map +1 -1
- package/dist/server/modules/butler/assistant-automation-service.d.ts +110 -0
- package/dist/server/modules/butler/assistant-automation-service.js +786 -0
- package/dist/server/modules/butler/assistant-automation-service.js.map +1 -0
- package/dist/server/modules/butler/assistant-automation-trigger.d.ts +94 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js +400 -0
- package/dist/server/modules/butler/assistant-automation-trigger.js.map +1 -0
- package/dist/server/modules/butler/assistant-sandbox-service.d.ts +55 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js +266 -0
- package/dist/server/modules/butler/assistant-sandbox-service.js.map +1 -0
- package/dist/server/modules/butler/butler-action-context-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-action-context-service.js +8 -2
- package/dist/server/modules/butler/butler-action-context-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-session-service.d.ts +8 -1
- package/dist/server/modules/butler/butler-control-session-service.js +154 -40
- package/dist/server/modules/butler/butler-control-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-control-timer-scheduler.d.ts +32 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js +93 -0
- package/dist/server/modules/butler/butler-control-timer-scheduler.js.map +1 -0
- package/dist/server/modules/butler/butler-control-timer-service.d.ts +42 -0
- package/dist/server/modules/butler/butler-control-timer-service.js +132 -0
- package/dist/server/modules/butler/butler-control-timer-service.js.map +1 -0
- package/dist/server/modules/butler/butler-controller.d.ts +42 -2
- package/dist/server/modules/butler/butler-controller.js +79 -12
- package/dist/server/modules/butler/butler-controller.js.map +1 -1
- package/dist/server/modules/butler/butler-follow-up-service.d.ts +9 -1
- package/dist/server/modules/butler/butler-follow-up-service.js +273 -181
- package/dist/server/modules/butler/butler-follow-up-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.d.ts +4 -1
- package/dist/server/modules/butler/butler-inbox-analysis-service.js +18 -4
- package/dist/server/modules/butler/butler-inbox-analysis-service.js.map +1 -1
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.js +7 -6
- package/dist/server/modules/butler/butler-inbox-instruction-adapter.js.map +1 -1
- package/dist/server/modules/butler/butler-profile-service.js +2 -5
- package/dist/server/modules/butler/butler-profile-service.js.map +1 -1
- package/dist/server/modules/butler/butler-project-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-project-service.js +7 -1
- package/dist/server/modules/butler/butler-project-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-service.d.ts +3 -1
- package/dist/server/modules/butler/butler-session-service.js +12 -1
- package/dist/server/modules/butler/butler-session-service.js.map +1 -1
- package/dist/server/modules/butler/butler-session-summary-service.js +2 -1
- package/dist/server/modules/butler/butler-session-summary-service.js.map +1 -1
- package/dist/server/modules/butler/butler-workspace-context.d.ts +3 -0
- package/dist/server/modules/butler/butler-workspace-context.js +182 -51
- package/dist/server/modules/butler/butler-workspace-context.js.map +1 -1
- package/dist/server/modules/butler/patrol-execution-service.js +2 -1
- package/dist/server/modules/butler/patrol-execution-service.js.map +1 -1
- package/dist/server/modules/butler/provider-adapter-registry.d.ts +3 -0
- package/dist/server/modules/butler/provider-adapter-registry.js +18 -1
- package/dist/server/modules/butler/provider-adapter-registry.js.map +1 -1
- package/dist/server/modules/butler/verification-run-service.d.ts +9 -2
- package/dist/server/modules/butler/verification-run-service.js +188 -34
- package/dist/server/modules/butler/verification-run-service.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-controller.d.ts +13 -0
- package/dist/server/modules/debug-target/debug-target-controller.js +77 -2
- package/dist/server/modules/debug-target/debug-target-controller.js.map +1 -1
- package/dist/server/modules/debug-target/debug-target-service.d.ts +17 -3
- package/dist/server/modules/debug-target/debug-target-service.js +696 -98
- package/dist/server/modules/debug-target/debug-target-service.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-client.d.ts +3 -0
- package/dist/server/modules/git/git-command-helper-client.js +71 -29
- package/dist/server/modules/git/git-command-helper-client.js.map +1 -1
- package/dist/server/modules/git/git-command-helper-process.js +62 -9
- package/dist/server/modules/git/git-command-helper-process.js.map +1 -1
- package/dist/server/modules/git/git-command-runner.d.ts +1 -0
- package/dist/server/modules/git/git-command-runner.js +44 -1
- package/dist/server/modules/git/git-command-runner.js.map +1 -1
- package/dist/server/modules/git/git-controller.js +8 -7
- package/dist/server/modules/git/git-controller.js.map +1 -1
- package/dist/server/modules/git/git-read-service.d.ts +7 -7
- package/dist/server/modules/git/git-read-service.js +41 -24
- package/dist/server/modules/git/git-read-service.js.map +1 -1
- package/dist/server/modules/model-switch/cc-switch-adapter.js +6 -2
- package/dist/server/modules/model-switch/cc-switch-adapter.js.map +1 -1
- package/dist/server/modules/preferences/profile-service.d.ts +3 -1
- package/dist/server/modules/preferences/profile-service.js +74 -3
- package/dist/server/modules/preferences/profile-service.js.map +1 -1
- package/dist/server/modules/provider/codex-model-options.js +2 -3
- package/dist/server/modules/provider/codex-model-options.js.map +1 -1
- package/dist/server/modules/provider/opencode-model-options.js +2 -3
- package/dist/server/modules/provider/opencode-model-options.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-client.d.ts +14 -7
- package/dist/server/modules/provider/provider-discovery-helper-client.js +208 -46
- package/dist/server/modules/provider/provider-discovery-helper-client.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-helper-process.js +96 -47
- package/dist/server/modules/provider/provider-discovery-helper-process.js.map +1 -1
- package/dist/server/modules/provider/provider-discovery-runtime.d.ts +4 -0
- package/dist/server/modules/provider/provider-discovery-runtime.js +145 -0
- package/dist/server/modules/provider/provider-discovery-runtime.js.map +1 -0
- package/dist/server/modules/sessions/claude-runtime-helper-client.js +23 -1
- package/dist/server/modules/sessions/claude-runtime-helper-client.js.map +1 -1
- package/dist/server/modules/sessions/session-history-service.d.ts +12 -3
- package/dist/server/modules/sessions/session-history-service.js +465 -67
- package/dist/server/modules/sessions/session-history-service.js.map +1 -1
- package/dist/server/modules/sessions/session-live-runtime-service.d.ts +8 -0
- package/dist/server/modules/sessions/session-live-runtime-service.js +164 -34
- package/dist/server/modules/sessions/session-live-runtime-service.js.map +1 -1
- package/dist/server/modules/sessions/session-message-origin-utils.d.ts +12 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js +45 -0
- package/dist/server/modules/sessions/session-message-origin-utils.js.map +1 -0
- package/dist/server/modules/sessions/session-permission-request-service.js +167 -0
- package/dist/server/modules/sessions/session-permission-request-service.js.map +1 -1
- package/dist/server/modules/skills/builtin-skill-service.d.ts +12 -0
- package/dist/server/modules/skills/builtin-skill-service.js +49 -0
- package/dist/server/modules/skills/builtin-skill-service.js.map +1 -0
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/SKILL.md +82 -0
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/agents/openai.yaml +4 -0
- package/dist/server/modules/skills/builtin-skills/codingns-assistant/references/cli-workflow.md +136 -0
- package/dist/server/modules/skills/skill-manager-service.d.ts +7 -0
- package/dist/server/modules/skills/skill-manager-service.js +98 -0
- package/dist/server/modules/skills/skill-manager-service.js.map +1 -1
- package/dist/server/modules/tailscale/tailscale-helper-client.d.ts +1 -0
- package/dist/server/modules/tailscale/tailscale-helper-client.js +12 -0
- package/dist/server/modules/tailscale/tailscale-helper-client.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-client.d.ts +10 -2
- package/dist/server/modules/tasks/task-helper-client.js +152 -27
- package/dist/server/modules/tasks/task-helper-client.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-process-handlers.d.ts +10 -3
- package/dist/server/modules/tasks/task-helper-process-handlers.js +7 -5
- package/dist/server/modules/tasks/task-helper-process-handlers.js.map +1 -1
- package/dist/server/modules/tasks/task-helper-process.js +104 -3
- package/dist/server/modules/tasks/task-helper-process.js.map +1 -1
- package/dist/server/modules/tasks/task-lane-executors.js +2 -2
- package/dist/server/modules/tasks/task-lane-executors.js.map +1 -1
- package/dist/server/modules/tasks/task-types.d.ts +4 -0
- package/dist/server/modules/tasks/task-types.js +5 -1
- package/dist/server/modules/tasks/task-types.js.map +1 -1
- package/dist/server/modules/terminal/command-template-service.d.ts +11 -2
- package/dist/server/modules/terminal/command-template-service.js +91 -9
- package/dist/server/modules/terminal/command-template-service.js.map +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-client.js.map +1 -1
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js +160 -11
- package/dist/server/modules/terminal/runtime/terminal-log-writer-process.js.map +1 -1
- package/dist/server/modules/terminal/template-port-runtime.d.ts +1 -1
- package/dist/server/modules/terminal/template-port-runtime.js +87 -37
- package/dist/server/modules/terminal/template-port-runtime.js.map +1 -1
- package/dist/server/modules/terminal/terminal-controller.d.ts +3 -0
- package/dist/server/modules/terminal/terminal-controller.js +41 -0
- package/dist/server/modules/terminal/terminal-controller.js.map +1 -1
- package/dist/server/modules/terminal/terminal-service.d.ts +4 -0
- package/dist/server/modules/terminal/terminal-service.js +35 -1
- package/dist/server/modules/terminal/terminal-service.js.map +1 -1
- package/dist/server/modules/workbench/workbench-service.d.ts +3 -0
- package/dist/server/modules/workbench/workbench-service.js +7 -6
- package/dist/server/modules/workbench/workbench-service.js.map +1 -1
- package/dist/server/modules/workbench/workspace-file-watcher.d.ts +14 -6
- package/dist/server/modules/workbench/workspace-file-watcher.js +267 -57
- package/dist/server/modules/workbench/workspace-file-watcher.js.map +1 -1
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.d.ts +3 -0
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js +149 -41
- package/dist/server/modules/workbench/workspace-panel-snapshot-service.js.map +1 -1
- package/dist/server/modules/workspace/workspace-code-composition.d.ts +1 -0
- package/dist/server/modules/workspace/workspace-code-composition.js +183 -1
- package/dist/server/modules/workspace/workspace-code-composition.js.map +1 -1
- package/dist/server/modules/workspace/workspace-service.js +54 -17
- package/dist/server/modules/workspace/workspace-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-cleanup-service.d.ts +1 -1
- package/dist/server/modules/worktree/worktree-cleanup-service.js +22 -17
- package/dist/server/modules/worktree/worktree-cleanup-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-controller.js +6 -5
- package/dist/server/modules/worktree/worktree-controller.js.map +1 -1
- package/dist/server/modules/worktree/worktree-manager.d.ts +10 -2
- package/dist/server/modules/worktree/worktree-manager.js +35 -20
- package/dist/server/modules/worktree/worktree-manager.js.map +1 -1
- package/dist/server/modules/worktree/worktree-merge-service.d.ts +2 -2
- package/dist/server/modules/worktree/worktree-merge-service.js +34 -27
- package/dist/server/modules/worktree/worktree-merge-service.js.map +1 -1
- package/dist/server/modules/worktree/worktree-sync-service.d.ts +1 -1
- package/dist/server/modules/worktree/worktree-sync-service.js +5 -3
- package/dist/server/modules/worktree/worktree-sync-service.js.map +1 -1
- package/dist/server/routes/assistant.js +43 -0
- package/dist/server/routes/assistant.js.map +1 -1
- package/dist/server/routes/butler.js +5 -0
- package/dist/server/routes/butler.js.map +1 -1
- package/dist/server/server/create-server.d.ts +8 -0
- package/dist/server/server/create-server.js +51 -13
- package/dist/server/server/create-server.js.map +1 -1
- package/dist/server/shared/http/request-abort.d.ts +2 -0
- package/dist/server/shared/http/request-abort.js +38 -0
- package/dist/server/shared/http/request-abort.js.map +1 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.d.ts +12 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js +139 -0
- package/dist/server/storage/repositories/assistant-automation-run-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.d.ts +15 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js +173 -0
- package/dist/server/storage/repositories/assistant-automation-task-repository.js.map +1 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.d.ts +17 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js +164 -0
- package/dist/server/storage/repositories/assistant-sandbox-workspace-repository.js.map +1 -0
- package/dist/server/storage/repositories/butler-control-session-repository.js +27 -3
- package/dist/server/storage/repositories/butler-control-session-repository.js.map +1 -1
- package/dist/server/storage/repositories/butler-control-timer-repository.d.ts +15 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js +157 -0
- package/dist/server/storage/repositories/butler-control-timer-repository.js.map +1 -0
- package/dist/server/storage/repositories/user-preference-profile-repository.js +6 -3
- package/dist/server/storage/repositories/user-preference-profile-repository.js.map +1 -1
- package/dist/server/storage/sqlite/client.js +239 -2
- package/dist/server/storage/sqlite/client.js.map +1 -1
- package/dist/server/storage/sqlite/schema.sql +107 -1
- package/dist/server/types/domain.d.ts +89 -2
- package/dist/server/ws/workbench-ws-hub.d.ts +14 -7
- package/dist/server/ws/workbench-ws-hub.js +316 -158
- package/dist/server/ws/workbench-ws-hub.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.d.ts +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js +111 -3
- package/node_modules/@codingns/session-sync-core/dist/providers/claude-code.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.d.ts +6 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js +306 -31
- package/node_modules/@codingns/session-sync-core/dist/providers/codex.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.d.ts +5 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js +187 -26
- package/node_modules/@codingns/session-sync-core/dist/providers/gemini.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.d.ts +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js +98 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/kimi.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.d.ts +2 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js +71 -8
- package/node_modules/@codingns/session-sync-core/dist/providers/opencode.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js +4 -1
- package/node_modules/@codingns/session-sync-core/dist/providers/utils.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js +44 -0
- package/node_modules/@codingns/session-sync-core/dist/runtime/claude-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js +9 -3
- package/node_modules/@codingns/session-sync-core/dist/runtime/codex-runtime.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/runtime/types.d.ts +1 -0
- package/node_modules/@codingns/session-sync-core/dist/services.js +17 -8
- package/node_modules/@codingns/session-sync-core/dist/services.js.map +1 -1
- package/node_modules/@codingns/session-sync-core/dist/types.d.ts +4 -0
- package/package.json +1 -1
- package/scripts/postinstall.mjs +33 -0
- package/dist/public/assets/index-Cek6u0b9.css +0 -1
- 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 =
|
|
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
|
|
266
|
-
const
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
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
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
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
|
|
346
|
-
|
|
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
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1557
|
-
|
|
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
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
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":
|