micro-models-agent 2.10.0 → 2.12.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/CHANGELOG.md +33 -0
- package/dist/assets/app.js +22 -4
- package/dist/assets/index.html +29 -0
- package/dist/assets/js/dom.js +2 -1
- package/dist/assets/js/feed.js +38 -26
- package/dist/assets/js/format.js +1 -1
- package/dist/assets/js/rail.js +114 -0
- package/dist/assets/js/settings-controls.js +2 -2
- package/dist/assets/js/settings.js +246 -51
- package/dist/assets/js/store.js +3 -0
- package/dist/assets/js/tabs.js +104 -2
- package/dist/assets/styles.css +215 -66
- package/dist/cli/cache-line.js +1 -0
- package/dist/cli/command-suggest.js +5 -4
- package/dist/cli/commands.js +231 -122
- package/dist/cli/completer.js +132 -31
- package/dist/cli/index.js +1 -0
- package/dist/cli/json-payload.js +4 -4
- package/dist/cli/main.js +166 -61
- package/dist/cli/onboarding-commands.js +126 -0
- package/dist/cli/plugin-commands.js +2 -1
- package/dist/cli/relaunch.js +16 -13
- package/dist/cli/repl-commands-lsp.js +75 -0
- package/dist/cli/repl-commands-skills.js +105 -0
- package/dist/cli/repl-commands.js +589 -266
- package/dist/cli/repl.js +436 -177
- package/dist/cli/run-result.js +5 -6
- package/dist/cli/security-commands.js +37 -28
- package/dist/cli/setup-order.js +4 -55
- package/dist/cli/setup-prompt.js +3 -2
- package/dist/cli/setup.js +27 -107
- package/dist/cli/web-command.js +98 -0
- package/dist/config/budget.js +5 -5
- package/dist/config/config.js +103 -96
- package/dist/config/defaults.js +38 -8
- package/dist/config/domains.js +24 -27
- package/dist/config/env.js +46 -0
- package/dist/config/experts.js +2 -0
- package/dist/config/index.js +1 -0
- package/dist/config/paths.js +56 -0
- package/dist/config/redact.js +44 -0
- package/dist/config/registry-extra.js +334 -0
- package/dist/config/registry.js +584 -0
- package/dist/config/security.js +35 -36
- package/dist/config/timeouts.js +74 -0
- package/dist/config/utils.js +16 -15
- package/dist/core/agent/artifact-progress.js +59 -0
- package/dist/core/agent/artifact-resolve.js +79 -0
- package/dist/core/agent/audit-gate.js +7 -3
- package/dist/core/agent/compaction.js +2 -2
- package/dist/core/agent/constants.js +33 -4
- package/dist/core/agent/context-log.js +454 -0
- package/dist/core/agent/context-renderer.js +5 -0
- package/dist/core/agent/critic-gate.js +132 -0
- package/dist/core/agent/hallucination-gate.js +148 -38
- package/dist/core/agent/loop-state.js +15 -0
- package/dist/core/agent/prefix-monitor.js +1 -1
- package/dist/core/agent/reasoning-resolver.js +10 -1
- package/dist/core/agent/token-tracker.js +7 -4
- package/dist/core/agent/tool-batch.js +93 -18
- package/dist/core/agent/tool-images.js +18 -0
- package/dist/core/agent/tool-output.js +65 -11
- package/dist/core/agent/tool-pairing.js +60 -0
- package/dist/core/agent-moe.js +25 -24
- package/dist/core/agent.js +429 -108
- package/dist/core/bootstrap/agent-phase.js +66 -0
- package/dist/core/bootstrap/config-phase.js +86 -0
- package/dist/core/bootstrap/modules-phase.js +269 -0
- package/dist/core/bootstrap/provider-phase.js +183 -0
- package/dist/core/bootstrap/session-phase.js +204 -0
- package/dist/core/bootstrap/state.js +94 -0
- package/dist/core/bootstrap/workspace-phase.js +115 -0
- package/dist/core/bootstrap.js +92 -660
- package/dist/core/crash-handler.js +17 -7
- package/dist/core/environment.js +37 -12
- package/dist/core/host-plan.js +43 -0
- package/dist/core/host.js +23 -0
- package/dist/core/index.js +1 -0
- package/dist/core/prompt-builder.js +17 -1
- package/dist/core/prompt-overflow.js +42 -43
- package/dist/core/session-logger.js +122 -30
- package/dist/core/system-prompt.js +117 -0
- package/dist/core/version.js +6 -4
- package/dist/core/web-host.js +34 -0
- package/dist/core/workspace.js +19 -15
- package/dist/i18n/en.json +106 -21
- package/dist/i18n/index.js +37 -3
- package/dist/i18n/ru.json +109 -21
- package/dist/index.js +7 -3
- package/dist/llm/image-utils.js +55 -64
- package/dist/llm/llm-errors.js +9 -0
- package/dist/llm/model-loader.js +10 -32
- package/dist/llm/models-dev.js +121 -0
- package/dist/llm/openai-compat.js +146 -73
- package/dist/llm/orchestrator.js +33 -11
- package/dist/llm/provider-budget.js +2 -2
- package/dist/llm/provider-context.js +233 -0
- package/dist/llm/provider.js +1 -1
- package/dist/llm/response.js +5 -0
- package/dist/llm/token-counter.js +27 -11
- package/dist/logger/app-logger.js +48 -22
- package/dist/logger/file-log.js +28 -19
- package/dist/logger/index.js +1 -0
- package/dist/main.js +24285 -22447
- package/dist/migration/backup.js +14 -3
- package/dist/migration/detect.js +12 -3
- package/dist/migration/index.js +1 -0
- package/dist/modules/artifacts/store.js +9 -8
- package/dist/modules/browser/actions.js +6 -0
- package/dist/modules/browser/bridge-client.js +14 -12
- package/dist/modules/browser/bridge-path.js +4 -4
- package/dist/modules/browser/console-buffer.js +37 -0
- package/dist/modules/browser/cookie-store.js +5 -0
- package/dist/modules/browser/driver-types.js +1 -0
- package/dist/modules/browser/driver.js +19 -11
- package/dist/modules/browser/index.js +3 -1
- package/dist/modules/browser/module.js +2 -1
- package/dist/modules/browser/session.js +10 -30
- package/dist/modules/browser/snapshot.js +19 -13
- package/dist/modules/browser/types.js +1 -0
- package/dist/modules/certification/cli.js +28 -20
- package/dist/modules/certification/fact-checker.js +9 -1
- package/dist/modules/certification/loader.js +9 -2
- package/dist/modules/certification/manifest.js +83 -21
- package/dist/modules/certification/runner.js +23 -13
- package/dist/modules/certification/scenarios.js +20 -20
- package/dist/modules/certification/syntax-scenarios.js +1 -2
- package/dist/modules/context/chunk-query.js +11 -9
- package/dist/modules/context/fact-extractor.js +37 -39
- package/dist/modules/context/index.js +1 -0
- package/dist/modules/context/manager.js +106 -80
- package/dist/modules/critic/client.js +176 -0
- package/dist/modules/critic/evidence.js +81 -0
- package/dist/modules/critic/provider.js +45 -0
- package/dist/modules/critic/tools.js +78 -0
- package/dist/modules/critic/types.js +1 -0
- package/dist/modules/execution/audit-runners.js +28 -34
- package/dist/modules/execution/auditor.js +53 -49
- package/dist/modules/execution/execution-plugin.js +142 -102
- package/dist/modules/execution/index.js +1 -1
- package/dist/modules/execution/input-from.js +37 -13
- package/dist/modules/execution/module.js +368 -191
- package/dist/modules/execution/moe-executor.js +53 -26
- package/dist/modules/execution/plan-actions.js +320 -202
- package/dist/modules/execution/plan-checks.js +35 -0
- package/dist/modules/execution/plan-coverage.js +36 -33
- package/dist/modules/execution/plan-steps.js +11 -3
- package/dist/modules/execution/plan-store.js +83 -17
- package/dist/modules/execution/plan-tool.js +89 -35
- package/dist/modules/execution/plan-validator.js +30 -10
- package/dist/modules/execution/planner.js +22 -13
- package/dist/modules/execution/stuck-detector.js +196 -93
- package/dist/modules/execution/tracker.js +22 -8
- package/dist/modules/execution/verifier.js +69 -48
- package/dist/modules/execution/windows-commands.js +12 -12
- package/dist/modules/hallucination/artifact-claim.js +93 -0
- package/dist/modules/hallucination/confidence.js +13 -11
- package/dist/modules/hallucination/consistency.js +10 -2
- package/dist/modules/hallucination/detector.js +10 -5
- package/dist/modules/hallucination/factual.js +43 -26
- package/dist/modules/hallucination/index.js +1 -0
- package/dist/modules/hallucination/js-identifiers.js +44 -45
- package/dist/modules/hallucination/llm-judge.js +17 -12
- package/dist/modules/hallucination/success-claim.js +59 -0
- package/dist/modules/indexer/cache.js +52 -4
- package/dist/modules/indexer/index.js +1 -0
- package/dist/modules/indexer/map-command.js +2 -4
- package/dist/modules/indexer/map-select.js +16 -16
- package/dist/modules/indexer/module.js +47 -12
- package/dist/modules/indexer/project-profile.js +8 -7
- package/dist/modules/indexer/symbols.js +15 -15
- package/dist/modules/indexer/walker.js +159 -29
- package/dist/modules/lsp/check-tool.js +7 -5
- package/dist/modules/lsp/client.js +39 -39
- package/dist/modules/lsp/command.js +17 -24
- package/dist/modules/lsp/config.js +12 -11
- package/dist/modules/lsp/index.js +1 -0
- package/dist/modules/lsp/module.js +48 -46
- package/dist/modules/lsp/probe.js +13 -14
- package/dist/modules/lsp/project-root.js +14 -17
- package/dist/modules/lsp/startup-check.js +18 -16
- package/dist/modules/mcp/client.js +59 -62
- package/dist/modules/mcp/index.js +1 -0
- package/dist/modules/mcp/module.js +66 -37
- package/dist/modules/mcp/registry.js +5 -0
- package/dist/modules/memory/index.js +1 -0
- package/dist/modules/memory/module.js +12 -8
- package/dist/modules/memory/search.js +4 -1
- package/dist/modules/memory/store.js +40 -2
- package/dist/modules/onboarding/init.js +47 -0
- package/dist/modules/onboarding/meet.js +30 -0
- package/dist/modules/onboarding/mode.js +30 -0
- package/dist/modules/onboarding/prompts.js +43 -0
- package/dist/modules/pipelines/engine.js +10 -0
- package/dist/modules/pipelines/index.js +1 -0
- package/dist/modules/pipelines/parser.js +4 -1
- package/dist/modules/pipelines/template.js +4 -0
- package/dist/modules/plugins/builtin/lint-on-write.js +80 -67
- package/dist/modules/plugins/builtin/notify.js +2 -0
- package/dist/modules/plugins/index.js +1 -0
- package/dist/modules/plugins/loader.js +18 -3
- package/dist/modules/plugins/manager.js +37 -17
- package/dist/modules/pricing/index.js +10 -11
- package/dist/modules/pricing/prices.js +12 -12
- package/dist/modules/processes/index.js +1 -0
- package/dist/modules/processes/kill-tree.js +27 -31
- package/dist/modules/processes/registry.js +44 -25
- package/dist/modules/processes/runner.js +4 -4
- package/dist/modules/providers/capabilities.js +28 -0
- package/dist/modules/providers/create.js +3 -4
- package/dist/modules/providers/factory.js +7 -7
- package/dist/modules/providers/fallback.js +12 -12
- package/dist/modules/providers/health.js +4 -4
- package/dist/modules/providers/manager.js +50 -19
- package/dist/modules/providers/naming.js +15 -0
- package/dist/modules/providers/preflight.js +71 -0
- package/dist/modules/providers/presets.js +39 -8
- package/dist/modules/providers/registry.js +7 -2
- package/dist/modules/reasoning/policy.js +8 -6
- package/dist/modules/reasoning/probe.js +30 -25
- package/dist/modules/security/audit-log.js +19 -36
- package/dist/modules/security/audit-notifier.js +35 -75
- package/dist/modules/security/command-validator.js +99 -51
- package/dist/modules/security/content-scanner.js +8 -8
- package/dist/modules/security/data-sanitizer.js +9 -16
- package/dist/modules/security/encryption.js +32 -52
- package/dist/modules/security/index.js +2 -1
- package/dist/modules/security/network-validator.js +120 -43
- package/dist/modules/security/path-validator.js +61 -46
- package/dist/modules/security/protected-paths.js +73 -0
- package/dist/modules/security/rate-limiter.js +17 -49
- package/dist/modules/security/security-policies.js +25 -57
- package/dist/modules/security/session-encryption.js +32 -71
- package/dist/modules/security/session-isolation.js +15 -28
- package/dist/modules/session/auto-name.js +63 -0
- package/dist/modules/session/export.js +98 -0
- package/dist/modules/session/index.js +1 -0
- package/dist/modules/session/manager.js +79 -28
- package/dist/modules/session/module.js +4 -3
- package/dist/modules/session/store.js +57 -20
- package/dist/modules/setup/answers.js +35 -0
- package/dist/modules/setup/apply.js +79 -0
- package/dist/modules/setup/discovery.js +72 -0
- package/dist/modules/skills/index.js +1 -0
- package/dist/modules/skills/loader.js +9 -1
- package/dist/modules/skills/module.js +11 -0
- package/dist/modules/updater/changelog-reader.js +15 -16
- package/dist/modules/updater/checker.js +9 -4
- package/dist/modules/updater/dev-detect.js +7 -6
- package/dist/modules/updater/index.js +1 -0
- package/dist/modules/updater/module.js +17 -15
- package/dist/modules/user-profile/compressor.js +2 -0
- package/dist/modules/user-profile/index.js +1 -0
- package/dist/modules/user-profile/profile.js +10 -1
- package/dist/modules/webui/answer-api.js +29 -0
- package/dist/modules/webui/assets.js +50 -0
- package/dist/modules/webui/body.js +21 -0
- package/dist/modules/webui/commands-settings.js +209 -0
- package/dist/modules/webui/commands-shared.js +60 -0
- package/dist/modules/webui/commands.js +174 -0
- package/dist/modules/webui/config-api.js +134 -0
- package/dist/modules/webui/file-api.js +202 -0
- package/dist/modules/webui/forward.js +159 -0
- package/dist/modules/webui/history-api.js +112 -0
- package/dist/modules/webui/host-state.js +60 -0
- package/dist/modules/webui/i18n-api.js +9 -0
- package/dist/modules/webui/limits.js +6 -0
- package/dist/modules/webui/log-forward.js +20 -0
- package/dist/modules/webui/memory-commands.js +64 -0
- package/dist/modules/webui/model-cache.js +18 -0
- package/dist/modules/webui/module.js +148 -0
- package/dist/modules/webui/persist.js +24 -0
- package/dist/modules/webui/prompt-io.js +97 -0
- package/dist/modules/webui/providers-api.js +337 -0
- package/dist/modules/webui/providers-view.js +56 -0
- package/dist/modules/webui/reasoning-view.js +15 -0
- package/dist/modules/webui/run-agent.js +81 -0
- package/dist/modules/webui/server.js +284 -0
- package/dist/modules/webui/sessions-api.js +178 -0
- package/dist/modules/webui/settings-api.js +561 -0
- package/dist/modules/webui/setup-api.js +201 -0
- package/dist/modules/webui/state.js +74 -0
- package/dist/modules/webui/util.js +261 -0
- package/dist/output/bus.js +7 -2
- package/dist/output/channel.js +80 -28
- package/dist/output/format.js +2 -1
- package/dist/output/index.js +1 -0
- package/dist/output/json-sink.js +5 -3
- package/dist/output/machine.js +4 -3
- package/dist/output/session-sink.js +4 -4
- package/dist/tools/approve.js +4 -0
- package/dist/tools/attach-image.js +5 -4
- package/dist/tools/auto-fixer.js +26 -44
- package/dist/tools/auto-tags.js +83 -0
- package/dist/tools/bash.js +193 -116
- package/dist/tools/browser.js +76 -31
- package/dist/tools/chunk-query.js +14 -6
- package/dist/tools/create-dir.js +9 -5
- package/dist/tools/delete-file.js +9 -5
- package/dist/tools/download-file.js +32 -7
- package/dist/tools/edit-file.js +52 -14
- package/dist/tools/enable-tools.js +6 -6
- package/dist/tools/executor.js +132 -27
- package/dist/tools/file-info.js +3 -1
- package/dist/tools/filter-tools.js +3 -3
- package/dist/tools/glob-tool.js +4 -1
- package/dist/tools/grep-tool.js +13 -10
- package/dist/tools/hidden-tools-block.js +6 -6
- package/dist/tools/index.js +16 -8
- package/dist/tools/list-dir.js +4 -2
- package/dist/tools/load-skill.js +7 -0
- package/dist/tools/mcp-call.js +15 -4
- package/dist/tools/meet-done.js +14 -0
- package/dist/tools/move-file.js +14 -7
- package/dist/tools/path-utils.js +29 -29
- package/dist/tools/pipeline-run.js +29 -7
- package/dist/tools/preview.js +1 -1
- package/dist/tools/process-ids.js +31 -0
- package/dist/tools/process-kill.js +9 -16
- package/dist/tools/process-list.js +1 -0
- package/dist/tools/process-log.js +11 -16
- package/dist/tools/process-wait.js +63 -0
- package/dist/tools/prompt-io.js +4 -3
- package/dist/tools/question.js +172 -25
- package/dist/tools/read-file.js +55 -34
- package/dist/tools/recall.js +14 -8
- package/dist/tools/registry.js +6 -5
- package/dist/tools/remember.js +8 -4
- package/dist/tools/scope-request.js +11 -6
- package/dist/tools/search-history.js +5 -2
- package/dist/tools/session-info.js +2 -0
- package/dist/tools/set-thinking.js +8 -4
- package/dist/tools/subagent.js +59 -39
- package/dist/tools/syntax-validator.js +292 -90
- package/dist/tools/user-input.js +22 -18
- package/dist/tools/web-browse.js +9 -3
- package/dist/tools/web-fetch.js +12 -23
- package/dist/tools/web-search.js +7 -5
- package/dist/tools/write-file.js +45 -15
- package/dist/ui/action-log.js +13 -0
- package/dist/ui/box.js +4 -3
- package/dist/ui/colors.js +2 -0
- package/dist/ui/diff.js +6 -5
- package/dist/ui/index.js +1 -0
- package/dist/ui/line-editor.js +230 -122
- package/dist/ui/line-math.js +10 -2
- package/dist/ui/md-formatter.js +57 -37
- package/dist/ui/output.js +8 -3
- package/dist/ui/plan-view.js +21 -10
- package/dist/ui/renderer.js +393 -67
- package/dist/ui/spinner.js +16 -5
- package/dist/ui/table.js +15 -17
- package/dist/ui/theme.js +143 -0
- package/dist/utils/child-run.js +20 -0
- package/dist/utils/error.js +1 -1
- package/dist/utils/index.js +2 -0
- package/dist/utils/retry.js +2 -2
- package/dist/utils/truncate.js +1 -1
- package/package.json +1 -1
|
@@ -1,19 +1,7 @@
|
|
|
1
|
+
// Корень LSP-workspace по маркерам и поиск локального tsc вверх по node_modules.
|
|
1
2
|
import { existsSync } from "fs";
|
|
2
3
|
import { dirname, join, relative, isAbsolute } from "path";
|
|
3
4
|
import { platform } from "os";
|
|
4
|
-
/**
|
|
5
|
-
* Resolve the LSP workspace root for an edited file.
|
|
6
|
-
*
|
|
7
|
-
* The workspace root must be the nearest project of the *edited file*, not the
|
|
8
|
-
* agent baseDir — tsserver resolves `typescript` relative to the workspace
|
|
9
|
-
* root, so for a file in `bicycle-shop/src/x.ts` under baseDir `mma_tests` the
|
|
10
|
-
* root must be `bicycle-shop` (observed: "Could not find a valid TypeScript
|
|
11
|
-
* installation" on every write/edit because baseDir had no node_modules).
|
|
12
|
-
*
|
|
13
|
-
* Which files mark a project root is language-specific and is declared in the
|
|
14
|
-
* LSP server config (`LspServerConfig.workspaceMarkers`) — this function takes
|
|
15
|
-
* the markers as a parameter and performs no language detection itself.
|
|
16
|
-
*/
|
|
17
5
|
/**
|
|
18
6
|
* Находит локальный компилятор TypeScript, поднимаясь от `startDir` вверх по
|
|
19
7
|
* `node_modules`. Возвращает готовую команду или null, если tsc не установлен.
|
|
@@ -47,16 +35,25 @@ export function resolveTscCommand(startDir, maxLevels = 8) {
|
|
|
47
35
|
}
|
|
48
36
|
return null;
|
|
49
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Корень LSP-workspace для редактируемого файла.
|
|
40
|
+
* Корень — ближайший проект *редактируемого файла*, не baseDir агента:
|
|
41
|
+
* tsserver резолвит `typescript` от корня workspace (наблюдалось: "Could not
|
|
42
|
+
* find a valid TypeScript installation" на каждый write/edit, когда у baseDir
|
|
43
|
+
* не было node_modules). Какие файлы маркируют корень — языко-специфично
|
|
44
|
+
* (`LspServerConfig.workspaceMarkers`); функция маркеры принимает параметром
|
|
45
|
+
* и детектом языка не занимается.
|
|
46
|
+
*/
|
|
50
47
|
export function findProjectRoot(filePath, baseDir, markers) {
|
|
51
48
|
if (!markers || markers.length === 0)
|
|
52
49
|
return baseDir;
|
|
53
50
|
let dir = dirname(filePath);
|
|
54
51
|
const root = baseDir.replace(/[\\/]+$/, "");
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
52
|
+
// Обход клампим к baseDir: вызывающие с самим baseDir как filePath (пробы)
|
|
53
|
+
// стартуют от dirname(baseDir) — ВЫШЕ песочницы — и подобрали бы маркер
|
|
54
|
+
// родителя (напр. package.json рядом).
|
|
58
55
|
const rel = relative(root, dir);
|
|
59
|
-
if ((rel
|
|
56
|
+
if ((rel?.startsWith("..")) || isAbsolute(rel))
|
|
60
57
|
dir = root;
|
|
61
58
|
while (true) {
|
|
62
59
|
if (markers.some((m) => existsSync(join(dir, m)))) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Стартовый health-check проекта: предсуществующие ошибки в промпт до начала работы.
|
|
1
2
|
import { existsSync } from "fs";
|
|
2
3
|
import { join } from "path";
|
|
3
4
|
import { spawn } from "child_process";
|
|
@@ -8,27 +9,27 @@ import { getServerForFile } from "./config";
|
|
|
8
9
|
import { findProjectRoot, resolveTscCommand } from "./project-root";
|
|
9
10
|
import { estimateTokens } from "../../llm/token-counter";
|
|
10
11
|
import { errMsg } from "../../utils";
|
|
11
|
-
|
|
12
|
+
import { LSP_STARTUP_CHECK_TIMEOUT_MS as STARTUP_CHECK_TIMEOUT_MS } from "../../config/timeouts";
|
|
13
|
+
/** Стартовый чек смотрит не больше этого числа файлов через LSP. */
|
|
12
14
|
const STARTUP_CHECK_FILE_CAP = 5;
|
|
13
|
-
/**
|
|
14
|
-
const STARTUP_CHECK_TIMEOUT_MS = 60_000;
|
|
15
|
-
/** Cap of tsc error lines surfaced in the prompt block. */
|
|
15
|
+
/** Кап строк ошибок tsc в промпт-блоке. */
|
|
16
16
|
const STARTUP_CHECK_ERROR_CAP = 20;
|
|
17
17
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* Сканирует проект на старте сессии на уже существующие ошибки, чтобы модель
|
|
19
|
+
* знала baseline ДО начала работы. Закрывает прерванные сессии: недоломанный
|
|
20
|
+
* прошлым запуском код всплывает сразу, а не находится (или пропускается)
|
|
21
|
+
* реактивно.
|
|
22
22
|
*
|
|
23
|
-
*
|
|
24
|
-
* -
|
|
25
|
-
*
|
|
26
|
-
* -
|
|
27
|
-
* (
|
|
23
|
+
* Стратегия:
|
|
24
|
+
* - есть tsconfig.json → один `tsc --noEmit --skipLibCheck` (один процесс на
|
|
25
|
+
* весь проект);
|
|
26
|
+
* - иначе → ограниченные LSP-диагностики до STARTUP_CHECK_FILE_CAP файлов
|
|
27
|
+
* (CSS/JSON/чистый-JS проекты без TS).
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* Всегда fail-open: любая ошибка/таймаут/чистый результат → null, чек не
|
|
30
|
+
* может замедлить или сломать старт сессии.
|
|
31
31
|
*/
|
|
32
|
+
/** Запустить стартовый health-check; нечего показать — null. */
|
|
32
33
|
export async function runStartupHealthCheck(config, baseDir, deps = {}) {
|
|
33
34
|
if (!config.enabled)
|
|
34
35
|
return null;
|
|
@@ -81,7 +82,7 @@ async function runCheck(config, baseDir, deps) {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
catch (e) {
|
|
84
|
-
//
|
|
85
|
+
// Упавший LSP на файле → лог и пропуск (fail-open).
|
|
85
86
|
deps.logger?.debug(`Startup LSP check failed for ${file}: ${errMsg(e)}`);
|
|
86
87
|
}
|
|
87
88
|
}
|
|
@@ -124,6 +125,7 @@ async function runTscDefault(projectRoot, timeoutMs) {
|
|
|
124
125
|
});
|
|
125
126
|
});
|
|
126
127
|
}
|
|
128
|
+
/** Распарсить "error TS" строки из вывода tsc (дедуп). */
|
|
127
129
|
export function parseTscErrors(output) {
|
|
128
130
|
const lines = [];
|
|
129
131
|
for (const raw of output.split("\n")) {
|
|
@@ -1,27 +1,32 @@
|
|
|
1
|
+
// MCP-клиент: JSON-RPC поверх stdio/sse/http, listTools/callTool, таймауты.
|
|
1
2
|
import { spawn } from "child_process";
|
|
2
3
|
import { defaultOutputBus } from "../../output";
|
|
3
|
-
|
|
4
|
-
const HTTP_TIMEOUT_MS = 10_000;
|
|
4
|
+
import { MCP_HTTP_TIMEOUT_MS as HTTP_TIMEOUT_MS } from "../../config/timeouts";
|
|
5
5
|
/**
|
|
6
|
-
* Accept
|
|
7
|
-
*
|
|
6
|
+
* Accept-заголовок для MCP Streamable HTTP: сервер отвечает 406, если клиент
|
|
7
|
+
* не принимает оба media type.
|
|
8
8
|
*/
|
|
9
9
|
const MCP_ACCEPT_HEADER = "application/json, text/event-stream";
|
|
10
|
+
/** MCP-клиент: коннект по транспорту, listTools, callTool. */
|
|
10
11
|
export class MCPClient {
|
|
11
12
|
serverName;
|
|
12
13
|
config;
|
|
13
14
|
process = null;
|
|
14
15
|
_connected = false;
|
|
15
16
|
abortController = null;
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
/** In-flight SSE-вызовы по JSON-RPC id (один слот мешал конкурентные
|
|
18
|
+
* вызовы и матчил ответы по любому id — теперь слот на вызов). */
|
|
19
|
+
ssePending = new Map();
|
|
20
|
+
sseSeq = 0;
|
|
18
21
|
constructor(config) {
|
|
19
22
|
this.config = config;
|
|
20
23
|
this.serverName = config.name;
|
|
21
24
|
}
|
|
25
|
+
/** Подключён ли клиент. */
|
|
22
26
|
isConnected() {
|
|
23
27
|
return this._connected;
|
|
24
28
|
}
|
|
29
|
+
/** Подключиться по транспорту из конфига (http/sse/stdio). */
|
|
25
30
|
async connect() {
|
|
26
31
|
if (this._connected)
|
|
27
32
|
return;
|
|
@@ -56,7 +61,7 @@ export class MCPClient {
|
|
|
56
61
|
this._connected = true;
|
|
57
62
|
const reader = response.body.getReader();
|
|
58
63
|
const decoder = new TextDecoder();
|
|
59
|
-
|
|
64
|
+
const buffer = "";
|
|
60
65
|
this.readSSE(reader, decoder, buffer);
|
|
61
66
|
}
|
|
62
67
|
catch (err) {
|
|
@@ -102,31 +107,30 @@ export class MCPClient {
|
|
|
102
107
|
handleSSEMessage(data) {
|
|
103
108
|
try {
|
|
104
109
|
const msg = JSON.parse(data);
|
|
105
|
-
if (
|
|
106
|
-
this.
|
|
107
|
-
if (
|
|
108
|
-
this.
|
|
110
|
+
if (msg.id !== undefined) {
|
|
111
|
+
const pending = this.ssePending.get(msg.id);
|
|
112
|
+
if (pending) {
|
|
113
|
+
this.ssePending.delete(msg.id);
|
|
114
|
+
clearTimeout(pending.timer);
|
|
115
|
+
if (msg.error)
|
|
116
|
+
pending.reject(new Error(msg.error.message));
|
|
117
|
+
else
|
|
118
|
+
pending.resolve(msg.result);
|
|
109
119
|
}
|
|
110
|
-
else {
|
|
111
|
-
this.pendingResolve(msg.result);
|
|
112
|
-
}
|
|
113
|
-
this.pendingResolve = null;
|
|
114
|
-
this.pendingReject = null;
|
|
115
120
|
}
|
|
116
121
|
}
|
|
117
122
|
catch (e) {
|
|
118
|
-
//
|
|
119
|
-
//
|
|
123
|
+
// Хартбиты/комменты — не JSON: ожидаемый шум, но логируем, чтобы
|
|
124
|
+
// реально битый стрим был виден в диагностике.
|
|
120
125
|
defaultOutputBus.log("error", "mcp", `[MCPClient] Unparseable SSE message: ${String(data).slice(0, 120)} ${e instanceof Error ? e.message : String(e)}`, { persist: true });
|
|
121
126
|
}
|
|
122
127
|
}
|
|
123
128
|
handleSSEError(err) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
this.pendingResolve = null;
|
|
128
|
-
this.pendingReject = null;
|
|
129
|
+
for (const [, pending] of this.ssePending) {
|
|
130
|
+
clearTimeout(pending.timer);
|
|
131
|
+
pending.reject(err);
|
|
129
132
|
}
|
|
133
|
+
this.ssePending.clear();
|
|
130
134
|
}
|
|
131
135
|
connectStdio() {
|
|
132
136
|
return new Promise((resolve, reject) => {
|
|
@@ -152,15 +156,15 @@ export class MCPClient {
|
|
|
152
156
|
child.on("spawn", () => {
|
|
153
157
|
this._connected = true;
|
|
154
158
|
this.process = child;
|
|
155
|
-
// MCP
|
|
156
|
-
// tools/list
|
|
159
|
+
// MCP-handshake: настоящие серверы требуют `initialize` перед
|
|
160
|
+
// tools/list и tools/call.
|
|
157
161
|
this.stdioRequest("initialize", {
|
|
158
162
|
protocolVersion: "2024-11-05",
|
|
159
163
|
capabilities: {},
|
|
160
164
|
clientInfo: { name: "mma", version: "1.0.0" },
|
|
161
165
|
}, 10000)
|
|
162
166
|
.then(() => {
|
|
163
|
-
child.stdin?.write(JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })
|
|
167
|
+
child.stdin?.write(`${JSON.stringify({ jsonrpc: "2.0", method: "notifications/initialized" })}\n`);
|
|
164
168
|
})
|
|
165
169
|
.then(() => {
|
|
166
170
|
_resolved = true;
|
|
@@ -176,16 +180,18 @@ export class MCPClient {
|
|
|
176
180
|
});
|
|
177
181
|
});
|
|
178
182
|
}
|
|
179
|
-
/**
|
|
183
|
+
/** Неразобранный хвост stdio-ответов (частичная строка). */
|
|
180
184
|
stdioBuffer = "";
|
|
185
|
+
/** Монотонный id stdio-запросов (Date.now() коллизил на вызовах в одну мс). */
|
|
186
|
+
stdioRequestSeq = 0;
|
|
181
187
|
/**
|
|
182
|
-
* JSON-RPC
|
|
183
|
-
*
|
|
184
|
-
*
|
|
188
|
+
* JSON-RPC запрос поверх stdio: таймаут, обработка exit и построчный парсинг
|
|
189
|
+
* с учётом буфера (нотификации и чужие ответы между запросом и ответом
|
|
190
|
+
* матчинг больше не ломают).
|
|
185
191
|
*/
|
|
186
192
|
stdioRequest(method, params, timeoutMs) {
|
|
187
193
|
const proc = this.process;
|
|
188
|
-
if (!proc
|
|
194
|
+
if (!proc?.stdout || !proc.stdin) {
|
|
189
195
|
return Promise.reject(new Error("Not connected to MCP server"));
|
|
190
196
|
}
|
|
191
197
|
const stdout = proc.stdout;
|
|
@@ -193,7 +199,7 @@ export class MCPClient {
|
|
|
193
199
|
return new Promise((resolve, reject) => {
|
|
194
200
|
const request = {
|
|
195
201
|
jsonrpc: "2.0",
|
|
196
|
-
id:
|
|
202
|
+
id: ++this.stdioRequestSeq,
|
|
197
203
|
method,
|
|
198
204
|
...(params !== undefined ? { params } : {}),
|
|
199
205
|
};
|
|
@@ -225,7 +231,7 @@ export class MCPClient {
|
|
|
225
231
|
continue;
|
|
226
232
|
}
|
|
227
233
|
if (msg.id !== request.id)
|
|
228
|
-
continue; //
|
|
234
|
+
continue; // нотификация или чужой ответ
|
|
229
235
|
finish(() => {
|
|
230
236
|
if (msg.error) {
|
|
231
237
|
reject(new Error(msg.error.message));
|
|
@@ -245,9 +251,10 @@ export class MCPClient {
|
|
|
245
251
|
timer = setTimeout(() => {
|
|
246
252
|
finish(() => reject(new Error(`MCP ${method} timeout`)));
|
|
247
253
|
}, timeout);
|
|
248
|
-
proc.stdin.write(JSON.stringify(request)
|
|
254
|
+
proc.stdin.write(`${JSON.stringify(request)}\n`);
|
|
249
255
|
});
|
|
250
256
|
}
|
|
257
|
+
/** Список тулов сервера (автоконнект; транспорт выбирается из конфига). */
|
|
251
258
|
async listTools() {
|
|
252
259
|
if (!this._connected) {
|
|
253
260
|
await this.connect();
|
|
@@ -319,6 +326,7 @@ export class MCPClient {
|
|
|
319
326
|
const result = await this.stdioRequest("tools/list");
|
|
320
327
|
return result?.tools || [];
|
|
321
328
|
}
|
|
329
|
+
/** Отключиться: http — флаг, sse — abort, stdio — kill процесса. */
|
|
322
330
|
async disconnect() {
|
|
323
331
|
if (this.config.transport === "http") {
|
|
324
332
|
this._connected = false;
|
|
@@ -334,6 +342,7 @@ export class MCPClient {
|
|
|
334
342
|
}
|
|
335
343
|
this._connected = false;
|
|
336
344
|
}
|
|
345
|
+
/** Вызвать тул (без коннекта — ошибка; транспорт из конфига). */
|
|
337
346
|
async callTool(toolName, args) {
|
|
338
347
|
if (!this._connected) {
|
|
339
348
|
throw new Error("Not connected to MCP server");
|
|
@@ -376,12 +385,11 @@ export class MCPClient {
|
|
|
376
385
|
}
|
|
377
386
|
return result.result || result;
|
|
378
387
|
}
|
|
379
|
-
/** Timer of the in-flight SSE request — cleared when the reply arrives via the stream. */
|
|
380
|
-
sseTimer;
|
|
381
388
|
async callToolSSE(toolName, args) {
|
|
389
|
+
const id = ++this.sseSeq;
|
|
382
390
|
const request = {
|
|
383
391
|
jsonrpc: "2.0",
|
|
384
|
-
id
|
|
392
|
+
id,
|
|
385
393
|
method: "tools/call",
|
|
386
394
|
params: {
|
|
387
395
|
name: toolName,
|
|
@@ -389,14 +397,17 @@ export class MCPClient {
|
|
|
389
397
|
},
|
|
390
398
|
};
|
|
391
399
|
return new Promise((resolve, reject) => {
|
|
392
|
-
|
|
393
|
-
|
|
400
|
+
const settle = (fn) => {
|
|
401
|
+
const pending = this.ssePending.get(id);
|
|
402
|
+
if (!pending)
|
|
403
|
+
return;
|
|
404
|
+
this.ssePending.delete(id);
|
|
405
|
+
clearTimeout(pending.timer);
|
|
406
|
+
fn();
|
|
407
|
+
};
|
|
394
408
|
const timeout = this.config.timeout || 30000;
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.pendingReject = null;
|
|
398
|
-
reject(new Error("MCP call timeout"));
|
|
399
|
-
}, timeout);
|
|
409
|
+
const timer = setTimeout(() => settle(() => reject(new Error("MCP call timeout"))), timeout);
|
|
410
|
+
this.ssePending.set(id, { resolve, reject, timer });
|
|
400
411
|
fetch(this.config.url, {
|
|
401
412
|
method: "POST",
|
|
402
413
|
headers: {
|
|
@@ -408,34 +419,20 @@ export class MCPClient {
|
|
|
408
419
|
signal: this.abortController?.signal,
|
|
409
420
|
})
|
|
410
421
|
.then(async (response) => {
|
|
411
|
-
this.clearSseTimer();
|
|
412
422
|
if (!response.ok) {
|
|
413
423
|
const text = await response.text().catch(() => "");
|
|
414
|
-
reject(new Error(`MCP POST failed: ${response.status} ${text}`));
|
|
424
|
+
settle(() => reject(new Error(`MCP POST failed: ${response.status} ${text}`)));
|
|
415
425
|
return;
|
|
416
426
|
}
|
|
417
427
|
const result = await response.json();
|
|
418
|
-
|
|
419
|
-
reject(new Error(result.error.message));
|
|
420
|
-
}
|
|
421
|
-
else {
|
|
422
|
-
resolve(result.result || result);
|
|
423
|
-
}
|
|
428
|
+
settle(() => result.error ? reject(new Error(result.error.message)) : resolve(result.result || result));
|
|
424
429
|
})
|
|
425
430
|
.catch((err) => {
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
reject(err);
|
|
429
|
-
}
|
|
431
|
+
if (err.name !== "AbortError")
|
|
432
|
+
settle(() => reject(err));
|
|
430
433
|
});
|
|
431
434
|
});
|
|
432
435
|
}
|
|
433
|
-
clearSseTimer() {
|
|
434
|
-
if (this.sseTimer !== undefined) {
|
|
435
|
-
clearTimeout(this.sseTimer);
|
|
436
|
-
this.sseTimer = undefined;
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
436
|
callToolStdio(toolName, args) {
|
|
440
437
|
if (!this.process) {
|
|
441
438
|
throw new Error("Not connected to MCP server");
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { MCPClient } from "./client";
|
|
2
2
|
import { MCPRegistry } from "./registry";
|
|
3
3
|
import { Logger } from "../../logger/app-logger";
|
|
4
|
+
import { MCP_DISCOVERY_TIMEOUT_MS as DISCOVERY_TIMEOUT_MS } from "../../config/timeouts";
|
|
4
5
|
const logger = new Logger("warn", "mcp");
|
|
6
|
+
/** Безопасное имя тула: mcp__server__tool. */
|
|
5
7
|
function sanitizeToolName(server, tool) {
|
|
6
8
|
const safeServer = server.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
7
9
|
const safeTool = tool.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
8
10
|
return `mcp__${safeServer}__${safeTool}`;
|
|
9
11
|
}
|
|
12
|
+
/** Привести input-схему MCP к форме дефиниций тулов. */
|
|
10
13
|
function convertInputSchema(schema) {
|
|
11
14
|
if (schema && typeof schema === "object" && "properties" in schema) {
|
|
12
15
|
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
@@ -18,26 +21,23 @@ function convertInputSchema(schema) {
|
|
|
18
21
|
}
|
|
19
22
|
return { type: "object", properties: {} };
|
|
20
23
|
}
|
|
21
|
-
/**
|
|
22
|
-
const DISCOVERY_TIMEOUT_MS = 5_000;
|
|
24
|
+
/** Модуль MCP: конфиги при init, коннект лениво, тул-дефиниции из найденного. */
|
|
23
25
|
export class MCPModule {
|
|
24
26
|
config;
|
|
25
27
|
name = "mcp";
|
|
26
28
|
connections = new Map();
|
|
27
|
-
/**
|
|
29
|
+
/** Конфиги серверов с init, ещё без коннекта. */
|
|
28
30
|
serverConfigs = new Map();
|
|
29
31
|
discovered = [];
|
|
30
32
|
initialized = false;
|
|
31
|
-
|
|
32
|
-
/** Servers that failed initial discovery — will not be retried. */
|
|
33
|
+
/** Серверы с упавшим первичным discovery — не ретраятся. */
|
|
33
34
|
failedServers = new Set();
|
|
34
35
|
constructor(config) {
|
|
35
36
|
this.config = config;
|
|
36
37
|
}
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* offline-safe.
|
|
39
|
+
* Зарегистрировать конфиги MCP-серверов БЕЗ коннекта. Коннект и discovery
|
|
40
|
+
* тулов — лениво, на первом вызове: старт быстрый и офлайн-безопасный.
|
|
41
41
|
*/
|
|
42
42
|
async initialize() {
|
|
43
43
|
if (this.initialized)
|
|
@@ -61,30 +61,36 @@ export class MCPModule {
|
|
|
61
61
|
};
|
|
62
62
|
registry.register(serverConfig);
|
|
63
63
|
}
|
|
64
|
-
//
|
|
64
|
+
// Конфиги — для ленивого коннекта, сетевых вызовов тут нет.
|
|
65
65
|
for (const serverName of registry.list()) {
|
|
66
66
|
this.serverConfigs.set(serverName, registry.get(serverName));
|
|
67
67
|
}
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
|
|
68
|
+
// Discovery ДО снапшота тул-дефиниций вызывающим. Раньше был
|
|
69
|
+
// fire-and-forget, и `collectToolDefinitions()` при bootstrap отрабатывал
|
|
70
|
+
// раньше, чем серверы успевали подключиться, — настоящие MCP-тулы не
|
|
71
|
+
// регистрировались. Discovery ограничен (таймаут на сервер, серверы
|
|
72
|
+
// параллельно) и best-effort: падения оставляют ленивый плейсхолдер "connect".
|
|
73
|
+
await this.discoverAllTools();
|
|
71
74
|
}
|
|
72
75
|
/**
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
+
* Подключиться и найти тулы всех зарегистрированных серверов параллельно,
|
|
77
|
+
* каждый ограничен DISCOVERY_TIMEOUT_MS. Отменённая по таймауту попытка:
|
|
78
|
+
* её позднее завершение не должно регистрировать сервер, уже помеченный упавшим.
|
|
76
79
|
*/
|
|
77
80
|
async discoverAllTools() {
|
|
78
|
-
|
|
81
|
+
await Promise.all([...this.serverConfigs].map(async ([serverName, serverConfig]) => {
|
|
79
82
|
if (this.failedServers.has(serverName))
|
|
80
|
-
|
|
83
|
+
return;
|
|
81
84
|
const client = new MCPClient(serverConfig);
|
|
85
|
+
let timedOut = false;
|
|
86
|
+
let timer;
|
|
82
87
|
try {
|
|
83
|
-
// Race discovery against timeout — never let a dead server block startup.
|
|
84
88
|
await Promise.race([
|
|
85
89
|
(async () => {
|
|
86
90
|
await client.connect();
|
|
87
91
|
const tools = await client.listTools();
|
|
92
|
+
if (timedOut)
|
|
93
|
+
return; // проиграл гонку — состояние не мутировать
|
|
88
94
|
this.connections.set(serverName, client);
|
|
89
95
|
for (const tool of tools) {
|
|
90
96
|
this.discovered.push({
|
|
@@ -95,28 +101,35 @@ export class MCPModule {
|
|
|
95
101
|
});
|
|
96
102
|
}
|
|
97
103
|
})(),
|
|
98
|
-
new Promise((_, reject) =>
|
|
104
|
+
new Promise((_, reject) => {
|
|
105
|
+
timer = setTimeout(() => reject(new Error("discovery timeout")), DISCOVERY_TIMEOUT_MS);
|
|
106
|
+
}),
|
|
99
107
|
]);
|
|
100
108
|
}
|
|
101
109
|
catch (e) {
|
|
110
|
+
timedOut = true;
|
|
102
111
|
this.failedServers.add(serverName);
|
|
103
|
-
|
|
112
|
+
logger.warn(`MCP server "${serverName}" init failed: ${e.message}`);
|
|
104
113
|
await client.disconnect().catch((err) => {
|
|
105
114
|
logger.warn(`cleanup disconnect for "${serverName}" failed`, { error: String(err) });
|
|
106
115
|
});
|
|
107
116
|
}
|
|
108
|
-
|
|
117
|
+
finally {
|
|
118
|
+
if (timer)
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
109
122
|
}
|
|
110
123
|
/**
|
|
111
|
-
*
|
|
112
|
-
*
|
|
124
|
+
* Гарантировать коннект конкретного сервера и discovery его тулов.
|
|
125
|
+
* Лениво вызывается на первом обращении к тулу сервера.
|
|
113
126
|
*/
|
|
114
127
|
async ensureConnected(serverName) {
|
|
115
|
-
//
|
|
128
|
+
// Уже подключён?
|
|
116
129
|
const existing = this.connections.get(serverName);
|
|
117
130
|
if (existing?.isConnected())
|
|
118
131
|
return existing;
|
|
119
|
-
//
|
|
132
|
+
// Упал при init — не ретраить.
|
|
120
133
|
if (this.failedServers.has(serverName))
|
|
121
134
|
return null;
|
|
122
135
|
const serverConfig = this.serverConfigs.get(serverName);
|
|
@@ -127,7 +140,7 @@ export class MCPModule {
|
|
|
127
140
|
await client.connect();
|
|
128
141
|
const tools = await client.listTools();
|
|
129
142
|
this.connections.set(serverName, client);
|
|
130
|
-
//
|
|
143
|
+
// Добавить новонайденные тулы (если ещё нет).
|
|
131
144
|
for (const tool of tools) {
|
|
132
145
|
const alreadyDiscovered = this.discovered.some((d) => d.serverName === serverName && d.toolName === tool.name);
|
|
133
146
|
if (!alreadyDiscovered) {
|
|
@@ -169,7 +182,7 @@ export class MCPModule {
|
|
|
169
182
|
}
|
|
170
183
|
}
|
|
171
184
|
else {
|
|
172
|
-
lines.push(` (not yet connected — use
|
|
185
|
+
lines.push(` (not yet connected — use ${sanitizeToolName(serverName, "connect")} to discover tools)`);
|
|
173
186
|
}
|
|
174
187
|
}
|
|
175
188
|
lines.push("---");
|
|
@@ -181,13 +194,30 @@ export class MCPModule {
|
|
|
181
194
|
};
|
|
182
195
|
}
|
|
183
196
|
getToolDefinitions() {
|
|
184
|
-
//
|
|
185
|
-
//
|
|
197
|
+
// Дефиниции из найденных тулов И из зарегистрированных, но ещё не
|
|
198
|
+
// найденных серверов (модель видит все настроенные MCP-тулы, даже если
|
|
199
|
+
// discovery при старте упал).
|
|
186
200
|
const defs = [];
|
|
187
|
-
//
|
|
201
|
+
// Санитизация может схлопнуть разные имена в один id (`a.b`/`a-b`,
|
|
202
|
+
// `x.y`/`x-y`); реестр на дубле бросал, что роняло bootstrap.
|
|
203
|
+
// Разруливаем числовым суффиксом до регистрации.
|
|
204
|
+
const usedNames = new Set();
|
|
205
|
+
const uniqueName = (base) => {
|
|
206
|
+
if (!usedNames.has(base)) {
|
|
207
|
+
usedNames.add(base);
|
|
208
|
+
return base;
|
|
209
|
+
}
|
|
210
|
+
let i = 2;
|
|
211
|
+
while (usedNames.has(`${base}_${i}`))
|
|
212
|
+
i++;
|
|
213
|
+
const name = `${base}_${i}`;
|
|
214
|
+
usedNames.add(name);
|
|
215
|
+
return name;
|
|
216
|
+
};
|
|
217
|
+
// Уже найденные тулы (подключены при старте).
|
|
188
218
|
for (const d of this.discovered) {
|
|
189
219
|
defs.push({
|
|
190
|
-
name: sanitizeToolName(d.serverName, d.toolName),
|
|
220
|
+
name: uniqueName(sanitizeToolName(d.serverName, d.toolName)),
|
|
191
221
|
description: `${d.description} [MCP server: ${d.serverName}]`,
|
|
192
222
|
parameters: convertInputSchema(d.inputSchema),
|
|
193
223
|
tags: ["code", "research"],
|
|
@@ -212,17 +242,16 @@ export class MCPModule {
|
|
|
212
242
|
},
|
|
213
243
|
});
|
|
214
244
|
}
|
|
215
|
-
//
|
|
216
|
-
// that will connect lazily on first call.
|
|
245
|
+
// Ещё не найденные серверы — плейсхолдеры, коннект лениво на первом вызове.
|
|
217
246
|
for (const [serverName] of this.serverConfigs) {
|
|
218
247
|
if (this.failedServers.has(serverName))
|
|
219
248
|
continue;
|
|
220
249
|
const alreadyDiscovered = this.discovered.some((d) => d.serverName === serverName);
|
|
221
250
|
if (alreadyDiscovered)
|
|
222
251
|
continue;
|
|
223
|
-
//
|
|
252
|
+
// Один тул "connect", чтобы модель могла триггернуть discovery.
|
|
224
253
|
defs.push({
|
|
225
|
-
name: sanitizeToolName(serverName, "connect"),
|
|
254
|
+
name: uniqueName(sanitizeToolName(serverName, "connect")),
|
|
226
255
|
description: `Connect to MCP server "${serverName}" and discover its tools`,
|
|
227
256
|
parameters: { type: "object", properties: {} },
|
|
228
257
|
tags: ["code", "research"],
|
|
@@ -255,8 +284,8 @@ export class MCPModule {
|
|
|
255
284
|
await client.disconnect();
|
|
256
285
|
}
|
|
257
286
|
catch (e) {
|
|
258
|
-
// Best-effort cleanup
|
|
259
|
-
//
|
|
287
|
+
// Best-effort cleanup на конце сессии: упавший disconnect не
|
|
288
|
+
// должен ронять shutdown, но обязан остаться в диагностике.
|
|
260
289
|
logger.warn(`disconnect from "${serverName}" failed`, { error: String(e?.message ?? e) });
|
|
261
290
|
}
|
|
262
291
|
}
|
|
@@ -1,14 +1,19 @@
|
|
|
1
|
+
/** Реестр конфигов MCP-серверов по именам. */
|
|
1
2
|
export class MCPRegistry {
|
|
2
3
|
servers = new Map();
|
|
4
|
+
/** Зарегистрировать конфиг сервера. */
|
|
3
5
|
register(config) {
|
|
4
6
|
this.servers.set(config.name, config);
|
|
5
7
|
}
|
|
8
|
+
/** Конфиг по имени (undefined — нет). */
|
|
6
9
|
get(name) {
|
|
7
10
|
return this.servers.get(name);
|
|
8
11
|
}
|
|
12
|
+
/** Имена серверов по алфавиту. */
|
|
9
13
|
list() {
|
|
10
14
|
return Array.from(this.servers.keys()).sort();
|
|
11
15
|
}
|
|
16
|
+
/** Удалить сервер. */
|
|
12
17
|
remove(name) {
|
|
13
18
|
this.servers.delete(name);
|
|
14
19
|
}
|