berry-agent 0.1.0-alpha.1
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/LICENSE +21 -0
- package/README.md +96 -0
- package/dist/.api-emit.stamp +1 -0
- package/dist/.build-meta.json +6 -0
- package/dist/agent/events.js +1 -0
- package/dist/agent/index.js +2 -0
- package/dist/agent/loop.js +139 -0
- package/dist/agent/queue.js +119 -0
- package/dist/agent/stream.js +62 -0
- package/dist/agent/tools-batch.js +221 -0
- package/dist/agent/types.js +8 -0
- package/dist/api/berry-agent-llm.d.ts +18 -0
- package/dist/api/berry-agent.d.ts +9 -0
- package/dist/api/surface.json +1632 -0
- package/dist/api/tsconfig.paths.json +12 -0
- package/dist/api/typebox-compile.d.ts +2 -0
- package/dist/api/typebox-value.d.ts +2 -0
- package/dist/api/typebox.d.ts +2 -0
- package/dist/browser/cdp.js +256 -0
- package/dist/browser/codes.js +31 -0
- package/dist/browser/discover.js +107 -0
- package/dist/browser/engine.js +169 -0
- package/dist/browser/index.js +17 -0
- package/dist/browser/install.js +342 -0
- package/dist/browser/page.js +321 -0
- package/dist/browser/service.js +158 -0
- package/dist/browser/tools.js +198 -0
- package/dist/browser/types.js +81 -0
- package/dist/channels/ask-queue.js +82 -0
- package/dist/channels/commands.js +129 -0
- package/dist/channels/engine/cell.js +143 -0
- package/dist/channels/engine/diff.js +143 -0
- package/dist/channels/engine/engine.js +416 -0
- package/dist/channels/engine/index.js +8 -0
- package/dist/channels/engine/input-keys.js +86 -0
- package/dist/channels/engine/input.js +596 -0
- package/dist/channels/engine/memory-io.js +71 -0
- package/dist/channels/engine/process-io.js +38 -0
- package/dist/channels/engine/types.js +17 -0
- package/dist/channels/engine/width.js +122 -0
- package/dist/channels/index.js +15 -0
- package/dist/channels/registry.js +56 -0
- package/dist/channels/sdk/admit.js +29 -0
- package/dist/channels/sdk/backend.js +102 -0
- package/dist/channels/sdk/cursor.js +56 -0
- package/dist/channels/sdk/index.js +20 -0
- package/dist/channels/sdk/jsonl.js +208 -0
- package/dist/channels/sdk/protocol.js +24 -0
- package/dist/channels/sdk/schema.js +91 -0
- package/dist/channels/sdk/wire-core.js +446 -0
- package/dist/channels/service.js +169 -0
- package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
- package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
- package/dist/channels/tui/autocomplete/popup.js +148 -0
- package/dist/channels/tui/autocomplete/provider.js +7 -0
- package/dist/channels/tui/autocomplete/token.js +68 -0
- package/dist/channels/tui/backend/ansi-rows.js +211 -0
- package/dist/channels/tui/backend/main-screen.js +207 -0
- package/dist/channels/tui/backend/osc.js +91 -0
- package/dist/channels/tui/backend/transcript.js +230 -0
- package/dist/channels/tui/backend/tui-backend.js +915 -0
- package/dist/channels/tui/editor/editor-model.js +543 -0
- package/dist/channels/tui/editor/editor-view.js +140 -0
- package/dist/channels/tui/editor/editor.js +275 -0
- package/dist/channels/tui/editor/undo-stack.js +35 -0
- package/dist/channels/tui/editor/visual-lines.js +126 -0
- package/dist/channels/tui/editor/word-nav.js +106 -0
- package/dist/channels/tui/history/history-viewer.js +403 -0
- package/dist/channels/tui/index.js +3 -0
- package/dist/channels/tui/layout.js +167 -0
- package/dist/channels/tui/markdown/blocks.js +111 -0
- package/dist/channels/tui/markdown/inline.js +75 -0
- package/dist/channels/tui/markdown/markdown.js +182 -0
- package/dist/channels/tui/memory/memory-viewer.js +540 -0
- package/dist/channels/tui/overlay/alt-screen.js +105 -0
- package/dist/channels/tui/overlay/overlay.js +57 -0
- package/dist/channels/tui/overlay/select-confirm.js +156 -0
- package/dist/channels/tui/panels/todo-panel.js +54 -0
- package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
- package/dist/channels/tui/scroll/scroll-view.js +254 -0
- package/dist/channels/tui/status/status-line.js +76 -0
- package/dist/channels/tui/text.js +38 -0
- package/dist/channels/tui/theme.js +41 -0
- package/dist/channels/types.js +11 -0
- package/dist/channels/ui-core.js +209 -0
- package/dist/checkpoint/capture.js +45 -0
- package/dist/checkpoint/codes.js +33 -0
- package/dist/checkpoint/command.js +82 -0
- package/dist/checkpoint/gate.js +48 -0
- package/dist/checkpoint/index.js +20 -0
- package/dist/checkpoint/restore.js +172 -0
- package/dist/checkpoint/store.js +247 -0
- package/dist/checkpoint/types.js +4 -0
- package/dist/checkpoint/walk.js +146 -0
- package/dist/compaction/ccr-tools.js +112 -0
- package/dist/compaction/ccr.js +84 -0
- package/dist/compaction/codes.js +21 -0
- package/dist/compaction/index.js +18 -0
- package/dist/compaction/policy.js +200 -0
- package/dist/compaction/service.js +422 -0
- package/dist/compaction/slots.js +121 -0
- package/dist/compaction/types.js +10 -0
- package/dist/context/codes.js +40 -0
- package/dist/context/events.js +160 -0
- package/dist/context/index.js +13 -0
- package/dist/context/logger.js +136 -0
- package/dist/context/scope.js +182 -0
- package/dist/context/workspace.js +104 -0
- package/dist/contracts/agent-events.d.ts +73 -0
- package/dist/contracts/agent-events.js +16 -0
- package/dist/contracts/api.d.ts +249 -0
- package/dist/contracts/api.js +299 -0
- package/dist/contracts/approval.d.ts +37 -0
- package/dist/contracts/approval.js +9 -0
- package/dist/contracts/env-ref.d.ts +41 -0
- package/dist/contracts/env-ref.js +32 -0
- package/dist/contracts/errors.d.ts +56 -0
- package/dist/contracts/errors.js +266 -0
- package/dist/contracts/events.d.ts +70 -0
- package/dist/contracts/events.js +329 -0
- package/dist/contracts/index.d.ts +32 -0
- package/dist/contracts/index.js +26 -0
- package/dist/contracts/llm.d.ts +275 -0
- package/dist/contracts/llm.js +12 -0
- package/dist/contracts/messages.d.ts +63 -0
- package/dist/contracts/messages.js +65 -0
- package/dist/contracts/redact.d.ts +46 -0
- package/dist/contracts/redact.js +223 -0
- package/dist/contracts/tools.d.ts +197 -0
- package/dist/contracts/tools.js +33 -0
- package/dist/contracts/types.d.ts +302 -0
- package/dist/contracts/types.js +50 -0
- package/dist/contracts/ui.d.ts +122 -0
- package/dist/contracts/ui.js +15 -0
- package/dist/conversation/agent-service.js +61 -0
- package/dist/conversation/approval-wiring.js +82 -0
- package/dist/conversation/backoff.js +38 -0
- package/dist/conversation/codes.js +47 -0
- package/dist/conversation/control-tools.js +102 -0
- package/dist/conversation/control.js +210 -0
- package/dist/conversation/driver.js +922 -0
- package/dist/conversation/index.js +12 -0
- package/dist/conversation/model-visible.js +169 -0
- package/dist/conversation/open-tools.js +126 -0
- package/dist/conversation/reseed.js +132 -0
- package/dist/conversation/sessions.js +215 -0
- package/dist/conversation/todo.js +203 -0
- package/dist/conversation/types.js +35 -0
- package/dist/conversation/wiring.js +204 -0
- package/dist/credentials/codes.js +59 -0
- package/dist/credentials/commands.js +186 -0
- package/dist/credentials/env-ref.js +34 -0
- package/dist/credentials/index.js +28 -0
- package/dist/credentials/migration.js +18 -0
- package/dist/credentials/oauth.js +266 -0
- package/dist/credentials/refresh.js +152 -0
- package/dist/credentials/secrets.js +118 -0
- package/dist/credentials/types.js +30 -0
- package/dist/exec/bash.js +279 -0
- package/dist/exec/codes.js +37 -0
- package/dist/exec/env.js +99 -0
- package/dist/exec/environment.js +39 -0
- package/dist/exec/git-guard.js +623 -0
- package/dist/exec/index.js +24 -0
- package/dist/exec/registry.js +120 -0
- package/dist/exec/spawn.js +281 -0
- package/dist/exec/tail.js +77 -0
- package/dist/exec/types.js +5 -0
- package/dist/goal/codes.js +33 -0
- package/dist/goal/command.js +175 -0
- package/dist/goal/fold.js +121 -0
- package/dist/goal/gates.js +122 -0
- package/dist/goal/index.js +22 -0
- package/dist/goal/migration.js +48 -0
- package/dist/goal/service.js +501 -0
- package/dist/goal/todo-tool.js +174 -0
- package/dist/goal/types.js +1 -0
- package/dist/goal/update-tool.js +46 -0
- package/dist/host/approval-cmd.js +276 -0
- package/dist/host/assembly.js +1146 -0
- package/dist/host/boot-failures.js +104 -0
- package/dist/host/budget-advisory.js +74 -0
- package/dist/host/budget-broadcast.js +50 -0
- package/dist/host/builtins.js +54 -0
- package/dist/host/cli.js +544 -0
- package/dist/host/codes.js +120 -0
- package/dist/host/config-schema.js +195 -0
- package/dist/host/conversation-stack.js +654 -0
- package/dist/host/core-plugins.js +1685 -0
- package/dist/host/credentials-cmd.js +42 -0
- package/dist/host/disclosure.js +30 -0
- package/dist/host/dispatch.js +105 -0
- package/dist/host/doors-cmd.js +154 -0
- package/dist/host/dump-config.js +44 -0
- package/dist/host/hook-dispatch-guard.js +27 -0
- package/dist/host/import-gate.js +153 -0
- package/dist/host/index.js +54 -0
- package/dist/host/issue-session.js +241 -0
- package/dist/host/loader.js +372 -0
- package/dist/host/main.js +161 -0
- package/dist/host/manifest.js +381 -0
- package/dist/host/mcp-entry.js +53 -0
- package/dist/host/plugin-boot.js +952 -0
- package/dist/host/plugin-context.js +672 -0
- package/dist/host/plugin-install.js +480 -0
- package/dist/host/plugin-reload.js +124 -0
- package/dist/host/plugin-store.js +462 -0
- package/dist/host/plugin-tools.js +359 -0
- package/dist/host/plugin-uninstall.js +257 -0
- package/dist/host/plugins-cmd.js +302 -0
- package/dist/host/plugins-command.js +130 -0
- package/dist/host/plugins-config.js +195 -0
- package/dist/host/prompt-sections.js +133 -0
- package/dist/host/run-entry.js +544 -0
- package/dist/host/runtime.js +209 -0
- package/dist/host/scheduler-tick.js +294 -0
- package/dist/host/serve-daemon.js +501 -0
- package/dist/host/serve-entry.js +279 -0
- package/dist/host/session-anchor.js +41 -0
- package/dist/host/sessions-cmd.js +219 -0
- package/dist/host/sessions-face.js +47 -0
- package/dist/host/settings-store.js +112 -0
- package/dist/host/signals.js +69 -0
- package/dist/host/single-instance.js +105 -0
- package/dist/host/subagent-factory.js +291 -0
- package/dist/host/testkit/harness.js +283 -0
- package/dist/host/testkit/index.js +14 -0
- package/dist/host/testkit/matrix.js +302 -0
- package/dist/host/tool-policy-store.js +177 -0
- package/dist/host/triggers.js +212 -0
- package/dist/host/tui-entry.js +216 -0
- package/dist/host/webui-bridge.js +165 -0
- package/dist/issue/codes.js +38 -0
- package/dist/issue/filter.js +146 -0
- package/dist/issue/github.js +116 -0
- package/dist/issue/index.js +17 -0
- package/dist/issue/mount.js +70 -0
- package/dist/issue/poll.js +58 -0
- package/dist/issue/service.js +341 -0
- package/dist/issue/tools.js +70 -0
- package/dist/issue/types.js +34 -0
- package/dist/issue/webhook.js +126 -0
- package/dist/llm/codes.js +77 -0
- package/dist/llm/complete.js +196 -0
- package/dist/llm/events.js +15 -0
- package/dist/llm/index.js +24 -0
- package/dist/llm/inflight.js +65 -0
- package/dist/llm/model-id.js +58 -0
- package/dist/llm/provider-face.d.ts +32 -0
- package/dist/llm/provider-face.js +34 -0
- package/dist/llm/recovery.js +146 -0
- package/dist/llm/runtime.js +46 -0
- package/dist/llm/stream-fn.js +147 -0
- package/dist/lsp/codes.js +31 -0
- package/dist/lsp/connection.js +147 -0
- package/dist/lsp/frame.js +110 -0
- package/dist/lsp/index.js +16 -0
- package/dist/lsp/inject.js +58 -0
- package/dist/lsp/instance.js +264 -0
- package/dist/lsp/service.js +522 -0
- package/dist/lsp/tools.js +53 -0
- package/dist/lsp/types.js +201 -0
- package/dist/mcp/bridge.js +222 -0
- package/dist/mcp/codes.js +21 -0
- package/dist/mcp/index.js +19 -0
- package/dist/mcp/jsonrpc.js +230 -0
- package/dist/mcp/service.js +125 -0
- package/dist/mcp/tools.js +145 -0
- package/dist/mcp/types.js +108 -0
- package/dist/memory/cite.js +131 -0
- package/dist/memory/codes.js +50 -0
- package/dist/memory/command.js +71 -0
- package/dist/memory/consolidate.js +219 -0
- package/dist/memory/cycle.js +109 -0
- package/dist/memory/dao.js +878 -0
- package/dist/memory/diff.js +238 -0
- package/dist/memory/extract.js +181 -0
- package/dist/memory/fts.js +58 -0
- package/dist/memory/index.js +42 -0
- package/dist/memory/inject.js +354 -0
- package/dist/memory/merge.js +178 -0
- package/dist/memory/migration.js +132 -0
- package/dist/memory/pollution.js +59 -0
- package/dist/memory/port.js +385 -0
- package/dist/memory/review.js +201 -0
- package/dist/memory/scan.js +89 -0
- package/dist/memory/tools.js +508 -0
- package/dist/memory/types.js +140 -0
- package/dist/obs/codes.js +38 -0
- package/dist/obs/db.js +115 -0
- package/dist/obs/index.js +20 -0
- package/dist/obs/rollup.js +61 -0
- package/dist/obs/service.js +363 -0
- package/dist/obs/session-tools.js +282 -0
- package/dist/obs/session-view.js +225 -0
- package/dist/obs/tool.js +97 -0
- package/dist/obs/types.js +1 -0
- package/dist/persist/audit.js +63 -0
- package/dist/persist/aux.js +39 -0
- package/dist/persist/codes.js +35 -0
- package/dist/persist/index.js +30 -0
- package/dist/persist/load-history.js +50 -0
- package/dist/persist/migrations.js +37 -0
- package/dist/persist/paths.js +108 -0
- package/dist/persist/persistence.js +229 -0
- package/dist/persist/schema.js +99 -0
- package/dist/persist/secret-box.js +95 -0
- package/dist/persist/store.js +706 -0
- package/dist/persist/write-behind.js +238 -0
- package/dist/safety/approval.js +171 -0
- package/dist/safety/bwrap.js +123 -0
- package/dist/safety/codes.js +57 -0
- package/dist/safety/danger.js +505 -0
- package/dist/safety/gate.js +226 -0
- package/dist/safety/index.js +24 -0
- package/dist/safety/presets.js +73 -0
- package/dist/safety/roots.js +139 -0
- package/dist/safety/sandbox.js +214 -0
- package/dist/safety/seatbelt.js +88 -0
- package/dist/safety/sensitive.js +34 -0
- package/dist/safety/tool-policy.js +203 -0
- package/dist/safety/types.js +13 -0
- package/dist/scheduler/codes.js +54 -0
- package/dist/scheduler/cron-backend.js +135 -0
- package/dist/scheduler/engine.js +280 -0
- package/dist/scheduler/gates.js +60 -0
- package/dist/scheduler/index.js +25 -0
- package/dist/scheduler/migration.js +22 -0
- package/dist/scheduler/runner.js +171 -0
- package/dist/scheduler/schedule.js +193 -0
- package/dist/scheduler/service.js +283 -0
- package/dist/scheduler/tick.js +139 -0
- package/dist/scheduler/types.js +1 -0
- package/dist/sdk/http.js +865 -0
- package/dist/sdk/index.js +22 -0
- package/dist/sdk/mcp.js +289 -0
- package/dist/sdk/plugin-route-registry.js +126 -0
- package/dist/sdk/plugin-routes.js +149 -0
- package/dist/sdk/security.js +116 -0
- package/dist/sdk/types.js +32 -0
- package/dist/session/budget.js +159 -0
- package/dist/session/codes.js +40 -0
- package/dist/session/derive.js +180 -0
- package/dist/session/event-data.js +1 -0
- package/dist/session/fork.js +72 -0
- package/dist/session/import-gates.js +148 -0
- package/dist/session/index.js +18 -0
- package/dist/session/recover.js +103 -0
- package/dist/session/session.js +279 -0
- package/dist/session/snapshot.js +107 -0
- package/dist/skills/agents.js +242 -0
- package/dist/skills/codes.js +51 -0
- package/dist/skills/discovery.js +266 -0
- package/dist/skills/frontmatter.js +215 -0
- package/dist/skills/index.js +28 -0
- package/dist/skills/load.js +118 -0
- package/dist/skills/manage.js +201 -0
- package/dist/skills/registry.js +138 -0
- package/dist/skills/render.js +124 -0
- package/dist/skills/sections.js +171 -0
- package/dist/skills/types.js +21 -0
- package/dist/subagent/codes.js +50 -0
- package/dist/subagent/declarative.js +55 -0
- package/dist/subagent/index.js +19 -0
- package/dist/subagent/notify.js +51 -0
- package/dist/subagent/provide.js +7 -0
- package/dist/subagent/registry.js +149 -0
- package/dist/subagent/service.js +338 -0
- package/dist/subagent/surface.js +44 -0
- package/dist/subagent/tool.js +153 -0
- package/dist/subagent/types.js +25 -0
- package/dist/tools/apply-patch.js +164 -0
- package/dist/tools/codes.js +109 -0
- package/dist/tools/fs.js +501 -0
- package/dist/tools/index.js +17 -0
- package/dist/tools/observed.js +99 -0
- package/dist/tools/pipeline.js +176 -0
- package/dist/tools/protected-read.js +54 -0
- package/dist/tools/registry.js +250 -0
- package/dist/tools/search.js +451 -0
- package/dist/tools/worktree.js +327 -0
- package/dist/web/codes.js +37 -0
- package/dist/web/gate.js +43 -0
- package/dist/web/hygiene.js +195 -0
- package/dist/web/index.js +16 -0
- package/dist/web/service.js +207 -0
- package/dist/web/ssrf-guard.js +44 -0
- package/dist/web/tool.js +76 -0
- package/dist/web/types.js +10 -0
- package/dist/webui/assets/index-B-t2O8gh.js +22 -0
- package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
- package/dist/webui/index.html +14 -0
- package/dist/webui/index.js +14 -0
- package/dist/webui/server.js +549 -0
- package/dist/webui/types.js +38 -0
- package/examples/README.md +26 -0
- package/examples/minimal-code-plugin/entry.js +43 -0
- package/examples/minimal-code-plugin/package.json +12 -0
- package/examples/pure-skill-pack/package.json +14 -0
- package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
- package/package.json +86 -0
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP 服务编排(03 §10.2——惰性 per-(server, rootUri) 实例生命周期承载)。
|
|
3
|
+
*
|
|
4
|
+
* 结构性差异(与 MCP 件 apply 异步发现对照):工具面**静态四件先注册**——
|
|
5
|
+
* 注册不依赖服务器在线;实例首用才 spawn(diagnostics/symbols/definitions/
|
|
6
|
+
* references 任一调用或诊断注入的预热)。rootUri = 工作区根 realpath 物理
|
|
7
|
+
* 根(v1 单根,懒解析缓存)。
|
|
8
|
+
*
|
|
9
|
+
* 失败语义:连接失败/crash 计熔断一败(实例 onDown 幂等闸保「一次进程事故
|
|
10
|
+
* 恰计一败」)+ ui.notify warn + 下用再试;同 server 3 连败熔断开
|
|
11
|
+
* (LSP_CIRCUIT_OPEN——复位走 /reload 即重 apply,行内他服务器不受累)。
|
|
12
|
+
* 作用域回卷/apply 整值替换的协议化关停**不计熔断**(非服务器故障——
|
|
13
|
+
* deliberate 旗隔离)。
|
|
14
|
+
*
|
|
15
|
+
* 惰性握手窗跨异步边界:握手成后置活接线前查 scope 活,死即协议化关停 +
|
|
16
|
+
* 登记簿对称删行(子进程真退自动出册)+ 不计熔断。
|
|
17
|
+
*/
|
|
18
|
+
import { resolve as resolvePath } from 'node:path';
|
|
19
|
+
import { pathToFileURL } from 'node:url';
|
|
20
|
+
import { BaseError } from '../contracts/index.js';
|
|
21
|
+
import { createLogger } from '../context/index.js';
|
|
22
|
+
import { connectLspInstance } from './instance.js';
|
|
23
|
+
import { createDiagnosticsInjector } from './inject.js';
|
|
24
|
+
import { buildLspToolDefs } from './tools.js';
|
|
25
|
+
import { TOOL_POST_EXECUTE_EVENT } from '../contracts/index.js';
|
|
26
|
+
import { LSP_CIRCUIT_FAILURE_LIMIT, LSP_DIAGNOSTICS_MAX_ITEMS, LSP_DIAGNOSTICS_SEGMENT_LIMIT_BYTES, LSP_DIAGNOSTICS_TIMEOUT_MS_CAP, LSP_DIAGNOSTICS_TIMEOUT_MS_DEFAULT, LSP_REQUEST_TIMEOUT_SEC_DEFAULT, extensionOf, routeServer, textResult, } from './types.js';
|
|
27
|
+
/** 组服务(静态四件注册 + 注入器挂线 + scope 回卷登记) */
|
|
28
|
+
export function createLspService(deps) {
|
|
29
|
+
const logger = deps.logger ?? createLogger('lsp');
|
|
30
|
+
const instances = new Map();
|
|
31
|
+
const failures = new Map();
|
|
32
|
+
const circuitOpen = new Set();
|
|
33
|
+
/** 预热注记已落位集合(首触一次性——server 维度) */
|
|
34
|
+
const warmNoted = new Set();
|
|
35
|
+
let config = {};
|
|
36
|
+
let diagTimeoutMs = LSP_DIAGNOSTICS_TIMEOUT_MS_DEFAULT;
|
|
37
|
+
/** 物理根(懒 realpath 缓存——rootUri 单源) */
|
|
38
|
+
let realRoot;
|
|
39
|
+
let realRootPromise;
|
|
40
|
+
const toolDisposers = [];
|
|
41
|
+
const isAlive = () => !deps.scope.isDisposed;
|
|
42
|
+
/** 物理根懒解析(缓存 promise——并发首用合一) */
|
|
43
|
+
const ensureRoot = () => {
|
|
44
|
+
if (realRoot !== undefined)
|
|
45
|
+
return Promise.resolve(realRoot);
|
|
46
|
+
if (realRootPromise === undefined) {
|
|
47
|
+
realRootPromise = deps.fs.realpath(deps.rootPath).then((p) => {
|
|
48
|
+
realRoot = p;
|
|
49
|
+
return p;
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return realRootPromise;
|
|
53
|
+
};
|
|
54
|
+
/** 路径归一(相对锚物理根;绝对原样) */
|
|
55
|
+
const resolveAbs = async (path) => resolvePath(await ensureRoot(), path);
|
|
56
|
+
/** 根内判定(含根本身;根外写入不诊断/不路由) */
|
|
57
|
+
const isInsideRoot = async (abs) => {
|
|
58
|
+
const root = await ensureRoot();
|
|
59
|
+
return abs === root || abs.startsWith(`${root}/`);
|
|
60
|
+
};
|
|
61
|
+
const notifyWarn = (message) => {
|
|
62
|
+
deps.notify?.(message);
|
|
63
|
+
logger.warn(message);
|
|
64
|
+
};
|
|
65
|
+
/** 计熔断一败(一次进程事故恰计一败——调用面幂等闸已保) */
|
|
66
|
+
const countFailure = (server, reason) => {
|
|
67
|
+
const n = (failures.get(server) ?? 0) + 1;
|
|
68
|
+
failures.set(server, n);
|
|
69
|
+
if (n >= LSP_CIRCUIT_FAILURE_LIMIT) {
|
|
70
|
+
if (!circuitOpen.has(server)) {
|
|
71
|
+
circuitOpen.add(server);
|
|
72
|
+
notifyWarn(`LSP 服务器 ${server} 连续 ${n} 败——熔断开(/reload 复位;其余服务器不受累);最近原因:${reason}`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
return; // 已熔断——不重复 notify
|
|
76
|
+
}
|
|
77
|
+
notifyWarn(`LSP 服务器 ${server} 失败(第 ${n}/${LSP_CIRCUIT_FAILURE_LIMIT} 次,下用再试):${reason}`);
|
|
78
|
+
};
|
|
79
|
+
/** 惰性取实例(熔断拒 + 在途合一 + 失败计账 + scope 活查) */
|
|
80
|
+
const getInstance = (server) => {
|
|
81
|
+
if (circuitOpen.has(server)) {
|
|
82
|
+
return Promise.reject(new BaseError('LSP_CIRCUIT_OPEN', `LSP 服务器 ${server} 已熔断(3 连败)——/reload 复位后恢复`));
|
|
83
|
+
}
|
|
84
|
+
const existing = instances.get(server);
|
|
85
|
+
if (existing !== undefined)
|
|
86
|
+
return existing.promise;
|
|
87
|
+
const cfg = config[server];
|
|
88
|
+
if (cfg === undefined) {
|
|
89
|
+
return Promise.reject(new BaseError('LSP_CONNECT_FAILED', `LSP 服务器 ${server} 不在配置内(apply 后再试)`));
|
|
90
|
+
}
|
|
91
|
+
const entry = { deliberate: false, state: 'starting', promise: undefined };
|
|
92
|
+
entry.promise = (async () => {
|
|
93
|
+
try {
|
|
94
|
+
const rootUri = pathToFileURL(await ensureRoot()).href;
|
|
95
|
+
const inst = await connectLspInstance(server, cfg, rootUri, {
|
|
96
|
+
spawn: deps.spawn,
|
|
97
|
+
logger,
|
|
98
|
+
closeGraceMs: deps.closeGraceMs,
|
|
99
|
+
});
|
|
100
|
+
// 惰性握手窗跨异步边界——置活接线前查 scope 活
|
|
101
|
+
if (!isAlive()) {
|
|
102
|
+
entry.deliberate = true; // 作用域回卷非服务器故障——不计熔断
|
|
103
|
+
void inst.close();
|
|
104
|
+
instances.delete(server);
|
|
105
|
+
throw new BaseError('LSP_CONNECT_FAILED', `作用域已回卷(${server})——实例协议化关停安静退场`);
|
|
106
|
+
}
|
|
107
|
+
inst.onDown((reason) => {
|
|
108
|
+
if (entry.deliberate)
|
|
109
|
+
return; // 协议化关停路径不计熔断
|
|
110
|
+
instances.delete(server);
|
|
111
|
+
countFailure(server, `crash:${reason}`);
|
|
112
|
+
});
|
|
113
|
+
entry.state = 'live';
|
|
114
|
+
return inst;
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
instances.delete(server);
|
|
118
|
+
if (!entry.deliberate)
|
|
119
|
+
countFailure(server, errText(err));
|
|
120
|
+
throw err;
|
|
121
|
+
}
|
|
122
|
+
})();
|
|
123
|
+
instances.set(server, entry);
|
|
124
|
+
return entry.promise;
|
|
125
|
+
};
|
|
126
|
+
/** 协议化关停全部实例(apply 整值替换 / scope 回卷共用——不计熔断) */
|
|
127
|
+
const shutdownAll = () => {
|
|
128
|
+
for (const [server, entry] of [...instances]) {
|
|
129
|
+
entry.deliberate = true; // 先立旗——close 编舞触发的退出事件不进熔断账
|
|
130
|
+
void entry.promise.then((inst) => inst.close()).catch(() => undefined);
|
|
131
|
+
instances.delete(server);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
/* ---------------- 四工具执行面 ---------------- */
|
|
135
|
+
/** 单请求预算 ms(配置单源) */
|
|
136
|
+
const requestMsOf = (server) => (config[server]?.request_timeout_sec ?? LSP_REQUEST_TIMEOUT_SEC_DEFAULT) * 1_000;
|
|
137
|
+
/** 诊断竞速钟(min(配置, 硬帽) 执法位) */
|
|
138
|
+
const diagClock = () => Math.min(diagTimeoutMs, LSP_DIAGNOSTICS_TIMEOUT_MS_CAP);
|
|
139
|
+
/** 读盘 + 全文同步(盘真相——触达 URI 即读盘取全文发 didOpen/didChange) */
|
|
140
|
+
const syncFile = async (path) => {
|
|
141
|
+
const abs = await resolveAbs(path);
|
|
142
|
+
const server = routeServer(config, abs);
|
|
143
|
+
if (server === null) {
|
|
144
|
+
return {
|
|
145
|
+
error: errResult(`无语言服务器路由(扩展名 .${extensionOf(abs) || '?'} 未配置——core:lsp 行 config servers.languages)`),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
let inst;
|
|
149
|
+
try {
|
|
150
|
+
inst = await getInstance(server);
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
return { error: errResult(errText(err)) };
|
|
154
|
+
}
|
|
155
|
+
let text;
|
|
156
|
+
try {
|
|
157
|
+
text = (await deps.fs.readFile(abs)).toString('utf8');
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
return { error: errResult(`读盘失败(${abs}):${errText(err)}`) };
|
|
161
|
+
}
|
|
162
|
+
const uri = pathToFileURL(abs).href;
|
|
163
|
+
const version = inst.syncDocument(uri, text, abs);
|
|
164
|
+
return { inst, uri, version };
|
|
165
|
+
};
|
|
166
|
+
const tools = {
|
|
167
|
+
async diagnostics(path) {
|
|
168
|
+
const synced = await syncFile(path);
|
|
169
|
+
if ('error' in synced)
|
|
170
|
+
return synced.error;
|
|
171
|
+
const items = await synced.inst.waitDiagnostics(synced.uri, synced.version, diagClock());
|
|
172
|
+
if (items === null) {
|
|
173
|
+
// 超钟诚实降级(工具面):非 isError——数据未及回流是事实回执非工具失败
|
|
174
|
+
return textResult(`诊断未在 ${diagClock()}ms 内回流(服务器忙/不支持推送)——稍后重试或直接读服务器输出`);
|
|
175
|
+
}
|
|
176
|
+
return textResult(formatDiagnosticsSegment(synced.uri, items, {
|
|
177
|
+
itemsCap: LSP_DIAGNOSTICS_MAX_ITEMS,
|
|
178
|
+
bytesCap: LSP_DIAGNOSTICS_SEGMENT_LIMIT_BYTES,
|
|
179
|
+
}).text);
|
|
180
|
+
},
|
|
181
|
+
async symbols(path) {
|
|
182
|
+
const synced = await syncFile(path);
|
|
183
|
+
if ('error' in synced)
|
|
184
|
+
return synced.error;
|
|
185
|
+
try {
|
|
186
|
+
const raw = await synced.inst.request('textDocument/documentSymbol', { textDocument: { uri: synced.uri } }, requestMsOf(routeOf(synced.uri)));
|
|
187
|
+
return textResult(formatSymbols(raw));
|
|
188
|
+
}
|
|
189
|
+
catch (err) {
|
|
190
|
+
return errResult(`documentSymbol 失败:${errText(err)}`);
|
|
191
|
+
}
|
|
192
|
+
},
|
|
193
|
+
async definitions(path, line, character) {
|
|
194
|
+
const synced = await syncFile(path);
|
|
195
|
+
if ('error' in synced)
|
|
196
|
+
return synced.error;
|
|
197
|
+
try {
|
|
198
|
+
const raw = await synced.inst.request('textDocument/definition', { textDocument: { uri: synced.uri }, position: { line, character } }, requestMsOf(routeOf(synced.uri)));
|
|
199
|
+
return textResult(formatLocations(raw, '定义'));
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
return errResult(`definition 失败:${errText(err)}`);
|
|
203
|
+
}
|
|
204
|
+
},
|
|
205
|
+
async references(path, line, character) {
|
|
206
|
+
const synced = await syncFile(path);
|
|
207
|
+
if ('error' in synced)
|
|
208
|
+
return synced.error;
|
|
209
|
+
try {
|
|
210
|
+
const raw = await synced.inst.request('textDocument/references', { textDocument: { uri: synced.uri }, position: { line, character }, context: { includeDeclaration: true } }, requestMsOf(routeOf(synced.uri)));
|
|
211
|
+
return textResult(formatLocations(raw, '引用'));
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
return errResult(`references 失败:${errText(err)}`);
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
};
|
|
218
|
+
/** uri → server 名(syncFile 已判路由——此为预算取值便捷面) */
|
|
219
|
+
function routeOf(uri) {
|
|
220
|
+
const p = fileURLToPathSafe(uri);
|
|
221
|
+
return p === null ? null : routeServer(config, p);
|
|
222
|
+
}
|
|
223
|
+
/* ---------------- 诊断注入面(tools_post_execute 消费) ---------------- */
|
|
224
|
+
const inject = {
|
|
225
|
+
async injectDiagnostics(paths) {
|
|
226
|
+
const segments = [];
|
|
227
|
+
let itemBudget = LSP_DIAGNOSTICS_MAX_ITEMS; // 条目上限:单次注入合计
|
|
228
|
+
let bytesBudget = LSP_DIAGNOSTICS_SEGMENT_LIMIT_BYTES; // 总段 ≤4KiB 合计
|
|
229
|
+
for (const path of paths) {
|
|
230
|
+
const abs = await resolveAbs(path);
|
|
231
|
+
if (!(await isInsideRoot(abs)))
|
|
232
|
+
continue; // 根外写入不诊断
|
|
233
|
+
const server = routeServer(config, abs);
|
|
234
|
+
if (server === null)
|
|
235
|
+
continue; // 无路由跳过(工具面才响亮——注入面静默增益)
|
|
236
|
+
/** 首触一次性「预热中」注记(server 维度) */
|
|
237
|
+
const noteWarm = () => {
|
|
238
|
+
if (warmNoted.has(server))
|
|
239
|
+
return;
|
|
240
|
+
warmNoted.add(server);
|
|
241
|
+
segments.push(`[lsp] 语言服务器 ${server} 预热中——本批写入暂不注入诊断(后续写入自动注入)`);
|
|
242
|
+
};
|
|
243
|
+
const entry = instances.get(server);
|
|
244
|
+
if (entry === undefined) {
|
|
245
|
+
// 未活:fire-and-forget 后台预热(失败计数归 getInstance——此处吞)
|
|
246
|
+
void getInstance(server).catch(() => undefined);
|
|
247
|
+
noteWarm();
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (entry.state === 'starting') {
|
|
251
|
+
// 预热在途:跳过注入(不 await 握手——竞速钟预算不留给等待)
|
|
252
|
+
noteWarm();
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
let inst;
|
|
256
|
+
try {
|
|
257
|
+
inst = await entry.promise; // live——已 resolve 零等待
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
continue; // 结算竞速兜底(正常不可达——live 态 promise 已 resolve)
|
|
261
|
+
}
|
|
262
|
+
let text;
|
|
263
|
+
try {
|
|
264
|
+
text = (await deps.fs.readFile(abs)).toString('utf8');
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
continue; // 读盘失败(写后即删等)——跳过
|
|
268
|
+
}
|
|
269
|
+
const uri = pathToFileURL(abs).href;
|
|
270
|
+
const version = inst.syncDocument(uri, text, abs);
|
|
271
|
+
const items = await inst.waitDiagnostics(uri, version, diagClock());
|
|
272
|
+
if (items === null) {
|
|
273
|
+
// 超钟诚实降级:逐路径点名「未及回流」——不得并成全清宣称
|
|
274
|
+
segments.push(`[lsp] ${path}:诊断未及回流(${diagClock()}ms 竞速钟尽)`);
|
|
275
|
+
continue;
|
|
276
|
+
}
|
|
277
|
+
if (items.length === 0)
|
|
278
|
+
continue; // 全清零噪音——不注入
|
|
279
|
+
const segment = formatDiagnosticsSegment(uri, items, { itemsCap: itemBudget, bytesCap: bytesBudget });
|
|
280
|
+
if (segment.usedItems === 0)
|
|
281
|
+
continue; // 预算尽(一条未放)——不再注段
|
|
282
|
+
itemBudget -= segment.usedItems;
|
|
283
|
+
bytesBudget -= Buffer.byteLength(segment.text, 'utf8');
|
|
284
|
+
segments.push(segment.text);
|
|
285
|
+
}
|
|
286
|
+
return segments;
|
|
287
|
+
},
|
|
288
|
+
closeDocuments(paths) {
|
|
289
|
+
for (const path of paths) {
|
|
290
|
+
const abs = resolvePathSyncSafe(path);
|
|
291
|
+
if (abs === null)
|
|
292
|
+
continue;
|
|
293
|
+
const server = routeServer(config, abs);
|
|
294
|
+
if (server === null)
|
|
295
|
+
continue;
|
|
296
|
+
const entry = instances.get(server);
|
|
297
|
+
if (entry === undefined)
|
|
298
|
+
continue;
|
|
299
|
+
void entry.promise.then((inst) => inst.closeDocument(pathToFileURL(abs).href)).catch(() => undefined);
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
/** resolveAbs 同步形(closeDocuments 同步面——根已缓存时直取) */
|
|
304
|
+
function resolvePathSyncSafe(path) {
|
|
305
|
+
if (realRoot === undefined)
|
|
306
|
+
return null;
|
|
307
|
+
return resolvePath(realRoot, path);
|
|
308
|
+
}
|
|
309
|
+
/* ---------------- 静态注册 + 挂线 + 回卷 ---------------- */
|
|
310
|
+
for (const def of buildLspToolDefs(tools)) {
|
|
311
|
+
toolDisposers.push(deps.registry.register(def));
|
|
312
|
+
}
|
|
313
|
+
const offInjector = deps.events.onWaterfall(TOOL_POST_EXECUTE_EVENT, createDiagnosticsInjector({ face: inject, warn: (m) => logger.warn(m) }));
|
|
314
|
+
deps.scope.effect(() => () => {
|
|
315
|
+
// 回卷序:摘注入器 → 撤工具 → 协议化关停实例(LIFO 语义)
|
|
316
|
+
offInjector();
|
|
317
|
+
for (const dispose of toolDisposers)
|
|
318
|
+
dispose();
|
|
319
|
+
shutdownAll();
|
|
320
|
+
});
|
|
321
|
+
return {
|
|
322
|
+
apply(settings) {
|
|
323
|
+
shutdownAll(); // 旧实例协议化关停(不计熔断)
|
|
324
|
+
failures.clear();
|
|
325
|
+
circuitOpen.clear();
|
|
326
|
+
warmNoted.clear();
|
|
327
|
+
config = settings.servers;
|
|
328
|
+
diagTimeoutMs = settings.diagnostics_timeout_ms ?? LSP_DIAGNOSTICS_TIMEOUT_MS_DEFAULT;
|
|
329
|
+
},
|
|
330
|
+
liveServers() {
|
|
331
|
+
const out = [];
|
|
332
|
+
for (const [server, entry] of instances) {
|
|
333
|
+
out.push({ server, state: entry.state });
|
|
334
|
+
}
|
|
335
|
+
return out;
|
|
336
|
+
},
|
|
337
|
+
async queryDiagnostics(files) {
|
|
338
|
+
const out = [];
|
|
339
|
+
for (const file of files) {
|
|
340
|
+
const synced = await syncFile(file);
|
|
341
|
+
if ('error' in synced) {
|
|
342
|
+
// 无路由/连接失败/读盘失败:折 error 级条目——gate fail-closed(诊断
|
|
343
|
+
// 缺席不得伪装全绿放行)
|
|
344
|
+
const first = synced.error.content[0];
|
|
345
|
+
out.push({
|
|
346
|
+
file,
|
|
347
|
+
level: 'error',
|
|
348
|
+
message: first !== undefined && first.type === 'text' ? first.text : '诊断不可用',
|
|
349
|
+
});
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
const items = await synced.inst.waitDiagnostics(synced.uri, synced.version, diagClock());
|
|
353
|
+
if (items === null) {
|
|
354
|
+
out.push({ file, level: 'error', message: `诊断超时未回流(${diagClock()}ms)——fail-closed 不放行` });
|
|
355
|
+
continue;
|
|
356
|
+
}
|
|
357
|
+
for (const item of items) {
|
|
358
|
+
out.push({
|
|
359
|
+
file,
|
|
360
|
+
level: item.severity === 1 ? 'error' : item.severity === 2 ? 'warning' : 'info',
|
|
361
|
+
message: `L${item.line + 1}:${item.character + 1} ${item.message}`,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
},
|
|
367
|
+
tools,
|
|
368
|
+
inject,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/* ---------------- 格式化助手(工具面与注入面共用) ---------------- */
|
|
372
|
+
/** isError 文本结果便捷构造(textResult 单源在 types——本件只做 isError 折叠) */
|
|
373
|
+
function errResult(message) {
|
|
374
|
+
return textResult(message, true);
|
|
375
|
+
}
|
|
376
|
+
/** severity → 标签(1=Error 2=Warning 3=Info 4=Hint——标签即 counts 键域) */
|
|
377
|
+
function severityLabel(severity) {
|
|
378
|
+
if (severity === 1)
|
|
379
|
+
return 'error';
|
|
380
|
+
if (severity === 2)
|
|
381
|
+
return 'warning';
|
|
382
|
+
if (severity === 3)
|
|
383
|
+
return 'info';
|
|
384
|
+
return 'hint';
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* 诊断段格式化(uri 面显示转 path 尾段;条目/字节双预算——预算尽截断注记,
|
|
388
|
+
* 预算尽返空段)。返回段文本与实际消耗条目数(预算扣减用)。
|
|
389
|
+
*/
|
|
390
|
+
function formatDiagnosticsSegment(uri, items, budget) {
|
|
391
|
+
const label = uri.replace(/^file:\/\//, '');
|
|
392
|
+
const counts = { error: 0, warning: 0, info: 0, hint: 0 };
|
|
393
|
+
for (const item of items)
|
|
394
|
+
counts[severityLabel(item.severity)] += 1;
|
|
395
|
+
const header = `[lsp 诊断] ${label}:error ×${counts.error}、warning ×${counts.warning}、info ×${counts.info}、hint ×${counts.hint}`;
|
|
396
|
+
const lines = [header];
|
|
397
|
+
let bytes = Buffer.byteLength(header, 'utf8');
|
|
398
|
+
let used = 0;
|
|
399
|
+
let truncated = false;
|
|
400
|
+
for (const item of items) {
|
|
401
|
+
if (used >= budget.itemsCap) {
|
|
402
|
+
truncated = true;
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
const line = ` [${severityLabel(item.severity)}] L${item.line + 1}:${item.character + 1} ${item.message}${item.source !== undefined ? `(${item.source})` : ''}`;
|
|
406
|
+
if (bytes + Buffer.byteLength(line, 'utf8') > budget.bytesCap) {
|
|
407
|
+
truncated = true;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
lines.push(line);
|
|
411
|
+
bytes += Buffer.byteLength(line, 'utf8');
|
|
412
|
+
used += 1;
|
|
413
|
+
}
|
|
414
|
+
if (truncated)
|
|
415
|
+
lines.push(` …(条目上限 ${budget.itemsCap}/段字节帽——截断)`);
|
|
416
|
+
return { text: lines.join('\n'), usedItems: used };
|
|
417
|
+
}
|
|
418
|
+
/** documentSymbol 结果格式化(层级形/平铺形两容) */
|
|
419
|
+
function formatSymbols(raw) {
|
|
420
|
+
const lines = [];
|
|
421
|
+
const walk = (entries, depth) => {
|
|
422
|
+
for (const entry of entries) {
|
|
423
|
+
if (typeof entry !== 'object' || entry === null)
|
|
424
|
+
continue;
|
|
425
|
+
const s = entry;
|
|
426
|
+
if (typeof s.name !== 'string')
|
|
427
|
+
continue;
|
|
428
|
+
const line = typeof s.range?.start?.line === 'number'
|
|
429
|
+
? s.range.start.line
|
|
430
|
+
: typeof s.location?.range?.start?.line === 'number'
|
|
431
|
+
? s.location.range.start.line
|
|
432
|
+
: 0;
|
|
433
|
+
lines.push(`${' '.repeat(depth)}${s.name}(${symbolKindName(typeof s.kind === 'number' ? s.kind : 0)})L${line + 1}`);
|
|
434
|
+
if (Array.isArray(s.children))
|
|
435
|
+
walk(s.children, depth + 1);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
if (Array.isArray(raw))
|
|
439
|
+
walk(raw, 0);
|
|
440
|
+
return lines.length > 0 ? lines.join('\n') : '(无符号)';
|
|
441
|
+
}
|
|
442
|
+
/** SymbolKind 名(1-26;未知返 unknown) */
|
|
443
|
+
function symbolKindName(kind) {
|
|
444
|
+
const names = [
|
|
445
|
+
'',
|
|
446
|
+
'File',
|
|
447
|
+
'Module',
|
|
448
|
+
'Namespace',
|
|
449
|
+
'Package',
|
|
450
|
+
'Class',
|
|
451
|
+
'Method',
|
|
452
|
+
'Property',
|
|
453
|
+
'Field',
|
|
454
|
+
'Constructor',
|
|
455
|
+
'Enum',
|
|
456
|
+
'Interface',
|
|
457
|
+
'Function',
|
|
458
|
+
'Variable',
|
|
459
|
+
'Constant',
|
|
460
|
+
'String',
|
|
461
|
+
'Number',
|
|
462
|
+
'Boolean',
|
|
463
|
+
'Array',
|
|
464
|
+
'Object',
|
|
465
|
+
'Key',
|
|
466
|
+
'Null',
|
|
467
|
+
'EnumMember',
|
|
468
|
+
'Struct',
|
|
469
|
+
'Event',
|
|
470
|
+
'Operator',
|
|
471
|
+
'TypeParameter',
|
|
472
|
+
];
|
|
473
|
+
return names[kind] ?? 'unknown';
|
|
474
|
+
}
|
|
475
|
+
/** definition/references 位置清单格式化(Location/LocationLink 两容) */
|
|
476
|
+
function formatLocations(raw, label) {
|
|
477
|
+
const list = Array.isArray(raw) ? raw : [raw];
|
|
478
|
+
const lines = [];
|
|
479
|
+
for (const entry of list) {
|
|
480
|
+
if (typeof entry !== 'object' || entry === null)
|
|
481
|
+
continue;
|
|
482
|
+
const l = entry;
|
|
483
|
+
const uri = typeof l.uri === 'string' ? l.uri : typeof l.targetUri === 'string' ? l.targetUri : undefined;
|
|
484
|
+
if (uri === undefined)
|
|
485
|
+
continue;
|
|
486
|
+
const start = l.range?.start ?? l.targetRange?.start;
|
|
487
|
+
const line = typeof start?.line === 'number' ? start.line : 0;
|
|
488
|
+
const ch = typeof start?.character === 'number' ? start.character : 0;
|
|
489
|
+
lines.push(`${uri.replace(/^file:\/\//, '')}:${line + 1}:${ch + 1}`);
|
|
490
|
+
}
|
|
491
|
+
return lines.length > 0 ? `${label} ${lines.length} 处:\n${lines.join('\n')}` : `(无${label})`;
|
|
492
|
+
}
|
|
493
|
+
/** file:// → path(失败返 null) */
|
|
494
|
+
function fileURLToPathSafe(uri) {
|
|
495
|
+
try {
|
|
496
|
+
return fileURLToPathImpl(uri);
|
|
497
|
+
}
|
|
498
|
+
catch {
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
/** fileURLToPath 实装位(node:url 延迟绑定——测试可覆盖面留给装配批) */
|
|
503
|
+
function fileURLToPathImpl(uri) {
|
|
504
|
+
// node:url 的 fileURLToPath 形——模块顶已 import pathToFileURL,此处同源
|
|
505
|
+
// 直接用 decodeURI 手写最小形(pathToFileURL 的逆;% 编码解码)
|
|
506
|
+
const prefix = 'file://';
|
|
507
|
+
if (!uri.startsWith(prefix))
|
|
508
|
+
return uri;
|
|
509
|
+
let rest = uri.slice(prefix.length);
|
|
510
|
+
// macOS/Linux 绝对形:/path;盘符形不进 v1(平台面随装配批)
|
|
511
|
+
try {
|
|
512
|
+
return decodeURI(rest);
|
|
513
|
+
}
|
|
514
|
+
catch {
|
|
515
|
+
return rest;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
function errText(err) {
|
|
519
|
+
if (err instanceof BaseError)
|
|
520
|
+
return err.message;
|
|
521
|
+
return err instanceof Error ? err.message : String(err);
|
|
522
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP 四工具(03 §10.2——静态注册面:diagnostics / symbols / definitions /
|
|
3
|
+
* references)。
|
|
4
|
+
*
|
|
5
|
+
* 结构性差异(与 MCP 件对照):工具面**静态四件、无发现步**——注册不依赖
|
|
6
|
+
* 服务器在线(实例惰性首用才 spawn)。扩展名路由选服务器,无路由 isError
|
|
7
|
+
* 诚实回执;一切失败折 isError 结果(数据面不改抛出面)。
|
|
8
|
+
*/
|
|
9
|
+
import { Type } from 'typebox';
|
|
10
|
+
/** 造四件静态工具(execute 落 face;效果面全 read——查询族零写效应) */
|
|
11
|
+
export function buildLspToolDefs(face) {
|
|
12
|
+
const diagnostics = {
|
|
13
|
+
name: 'diagnostics',
|
|
14
|
+
effect: 'read',
|
|
15
|
+
description: '查询文件的语言服务诊断(LSP——lint/类型错等)。按文件扩展名路由到已配置的语言服务器(惰性启动,首次调用可能较慢)。返回 error/warning 分级清单;诊断超时诚实降级说明。',
|
|
16
|
+
parameters: Type.Object({
|
|
17
|
+
path: Type.String({ description: '目标文件路径(相对路径锚工作区根)' }),
|
|
18
|
+
}),
|
|
19
|
+
execute: async (args) => face.diagnostics(args.path),
|
|
20
|
+
};
|
|
21
|
+
const symbols = {
|
|
22
|
+
name: 'symbols',
|
|
23
|
+
effect: 'read',
|
|
24
|
+
description: '列出文件的文档符号(LSP documentSymbol——函数/类/变量等结构清单,带位置)。按扩展名路由语言服务器(惰性启动)。',
|
|
25
|
+
parameters: Type.Object({
|
|
26
|
+
path: Type.String({ description: '目标文件路径(相对路径锚工作区根)' }),
|
|
27
|
+
}),
|
|
28
|
+
execute: async (args) => face.symbols(args.path),
|
|
29
|
+
};
|
|
30
|
+
const definitions = {
|
|
31
|
+
name: 'definitions',
|
|
32
|
+
effect: 'read',
|
|
33
|
+
description: '查符号定义位置(LSP definition)。入参行/列 0-based。按扩展名路由语言服务器(惰性启动)。',
|
|
34
|
+
parameters: Type.Object({
|
|
35
|
+
path: Type.String({ description: '目标文件路径(相对路径锚工作区根)' }),
|
|
36
|
+
line: Type.Integer({ description: '行号(0-based)' }),
|
|
37
|
+
character: Type.Integer({ description: '列号(0-based)' }),
|
|
38
|
+
}),
|
|
39
|
+
execute: async (args) => face.definitions(args.path, args.line, args.character),
|
|
40
|
+
};
|
|
41
|
+
const references = {
|
|
42
|
+
name: 'references',
|
|
43
|
+
effect: 'read',
|
|
44
|
+
description: '查符号全部引用位置(LSP references,含声明)。入参行/列 0-based。按扩展名路由语言服务器(惰性启动)。',
|
|
45
|
+
parameters: Type.Object({
|
|
46
|
+
path: Type.String({ description: '目标文件路径(相对路径锚工作区根)' }),
|
|
47
|
+
line: Type.Integer({ description: '行号(0-based)' }),
|
|
48
|
+
character: Type.Integer({ description: '列号(0-based)' }),
|
|
49
|
+
}),
|
|
50
|
+
execute: async (args) => face.references(args.path, args.line, args.character),
|
|
51
|
+
};
|
|
52
|
+
return [diagnostics, symbols, definitions, references];
|
|
53
|
+
}
|