
oh-my-openagent 的 LSP Diagnostics 实证核查以第一方 LSP MCP 实现验证工作树诊断【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent在 oh-my-openagent 仓库中当待核查的任务工作树位于会话工具固定 workspace 根目录之外时如何获得真实可信的语言服务器诊断结果本篇文章基于仓库中的核查记录文档.omo/evidence/20260810-windows-ci-root-causes/lsp-diagnostics.md完整还原这一过程从createStandaloneMcpRequestContext()上下文安装、executeLspTool(diagnostics, ...)调用到disposeDefaultLspManager()的清理收尾并深入 lsp-tools-mcp 与 lsp-core 两个包的源码讲清诊断在文件模式与目录模式下的执行路径、严重级别过滤规则以及 LSP 管理器生命周期管理。读完本文你将掌握在任意工作树上独立执行 LSP 诊断核查、判断核查结果有效性并完成进程清理的完整技术方案。背景为什么要用第一方 LSP MCP 实现执行诊断在 CI 根因分析Windows CI root causes的核查场景中任务工作树task worktree通常不在会话工具默认的固定 workspace 根目录之内。此时如果依赖会话工具的常规路径解析机制待核查文件根本无法被定位也就谈不上运行语言服务器诊断。该记录文档给出的解法是绕过固定 workspace 根的限制直接使用仓库自带的第一方独立 LSP MCP 实现来执行诊断。这条实现链路集中在三个文件packages/lsp-tools-mcp/src/request-context.ts —— 请求上下文入口packages/lsp-tools-mcp/src/tools.ts —— LSP 工具入口packages/lsp-tools-mcp/src/lsp/manager.ts —— LSP 客户端管理器入口值得注意的是这三个文件本身都是薄转发层真正的实现位于共享核心包 lsp-core 中。例如 request-context.ts 的全部内容就是export * from oh-my-opencode/lsp-core/request-contexttools.ts 则转发oh-my-opencode/lsp-core/tools。这意味着诊断核查走的是与正式发布路径完全一致的同一套第一方 LSP 核心而不是核查专用的临时代码——这正是后续为什么这份证据足够论断的基础。核查驱动的基本调用序列文档记录的标准驱动流程分三步且严格遵循安装上下文 → 执行工具 → 清理资源的顺序从任务工作树安装createStandaloneMcpRequestContext()调用executeLspTool(diagnostics, { severity: error })在finally块中释放默认 LSP 管理器。下面结合源码逐一展开这三步背后的机制。第一步安装独立 MCP 请求上下文createStandaloneMcpRequestContext()定义在 packages/lsp-core/src/request-context.ts它负责把一个LspRequestContext对象挂到AsyncLocalStorage上。其签名与输入如下export interface StandaloneMcpRequestContextInput { readonly cwd?: string; readonly env?: Recordstring, string | undefined; readonly homeDir?: string; } export function createStandaloneMcpRequestContext( input: StandaloneMcpRequestContextInput {}, ): LspRequestContext上下文解析的优先级从源码中可以清楚看到request-context.tscwd优先取input.cwd其次读环境变量LSP_TOOLS_MCP_CWD最后回退到process.cwd()项目级 LSP 配置读LSP_TOOLS_MCP_PROJECT_CONFIG默认回退到cwd/.codex/lsp-client.json用户级 LSP 配置读LSP_TOOLS_MCP_USER_CONFIG默认回退到~/.codex/lsp-client.json安装决策文件读LSP_TOOLS_MCP_INSTALL_DECISIONS默认回退到~/.codex/lsp-install-decisions.jsoncapabilities.installDecisionTool固定为true。其中cwd会经过canonicalCwd()严格校验必须解析为真实存在的目录并通过realpathSync得到规范路径否则抛出LspRequestContextParseError(invalid_cwd, ...)request-context.ts。这保证了上下文永远指向一个规范化后的真实目录不会出现悬空路径。parseLspRequestContext()request-context.ts还会做更细的防御性校验拒绝未知字段unknown_field、要求路径为绝对路径relative_path、要求项目配置路径必须位于 cwd 之内project_config_outside_cwd。这套校验决定了独立上下文既灵活可指向任意工作树又安全路径不能越界。上下文的消费方式是 AsyncLocalStorageexport function runWithRequestContextT(context: LspRequestContext, fn: () T): T { return storage.run(context, fn); } export function lspRequestContext(): LspRequestContext { const context storage.getStore(); if (!context) throw new LspRequestContextUnavailableError(); return context; }也就是说核查驱动只要在调用executeLspTool之前用runWithRequestContext包住执行体或直接安装上下文其后的诊断请求就能通过contextCwd()定位到真实工作树而无需依赖会话工具的固定 workspace 根。这一点正是整个方案成立的关键上下文与固定工作区解耦诊断就能落到任务工作树本身上。第二步调用 executeLspTool 执行 diagnosticsexecuteLspTool定义在 packages/lsp-core/src/tools/runtime.tsexport async function executeLspTool( name: string, params: Recordstring, unknown, signal?: AbortSignal, ): PromiseToolExecutionResult { const tool LSP_MCP_TOOLS.find((candidate) matchesToolName(candidate, name)); if (!tool) throw new Error(Unknown LSP tool: ${name}); return tool.execute(params, signal); }它会先在工具注册表中查找名字含别名匹配找不到直接抛Unknown LSP tool。诊断工具executeLspDiagnostics的实现位于 packages/lsp-core/src/tools/diagnostics.ts其核心逻辑如下参数解析filePath为必填字符串requireStringseverity经severityFilter归一化。路径解析resolveReadablePathInsideContext(filePath)把相对路径解析到上下文 cwd 之内。模式分流若路径是目录进入目录诊断模式否则进入单文件诊断模式。结果后处理按严重级别过滤、截断、格式化输出。severityFilter支持五档取值packages/lsp-core/src/tools/parameters.tsseverity 取值含义过滤行为error错误仅保留 error 级诊断warning警告仅保留 warning 级诊断information信息仅保留 information 级诊断hint提示仅保留 hint 级诊断all全部不过滤缺省值任何非法取值都会静默回退到all。核查驱动传入的{ severity: error }意味着只关注错误级诊断忽略警告与提示信息这正是 CI 根因核查最关心的信号。单文件模式下的完整流程diagnostics.tsconst result await withLspClient( filePath, async (client, _workspaceRoot, resolvedFilePath) client.diagnostics(resolvedFilePath, signal), diagnostics, clientOptions(signal), ); // ... const diagnostics filterDiagnosticsBySeverity(asDiagnosticArray(result), severity); const total diagnostics.length; const truncated total DEFAULT_MAX_DIAGNOSTICS; const limited truncated ? diagnostics.slice(0, DEFAULT_MAX_DIAGNOSTICS) : diagnostics;诊断数量默认上限由DEFAULT_MAX_DIAGNOSTICS控制超出时输出会提示Found N diagnostics (showing first M)避免一次性灌入海量文本若一个诊断都没有则输出No diagnostics found。遇到瞬时错误如服务器未就绪会返回带transientError标记的结果而不是直接抛异常。第三步在 finally 中释放默认 LSP 管理器disposeDefaultLspManager()定义在 packages/lsp-core/src/lsp/manager.tslet _defaultInstance: LspManager | null null; export function getLspManager(): LspManager { if (!_defaultInstance) { _defaultInstance new LspManager(); } return _defaultInstance; } export async function disposeDefaultLspManager(): Promisevoid { if (_defaultInstance) { const m _defaultInstance; _defaultInstance null; await m.stopAll(); } }stopAll()manager.ts会把管理器标记为已销毁、清理 reaper 定时器与信号处理器然后对全部存量客户端执行 best-effort 停止并用Promise.allSettled等待所有停止动作含已进入 tombstone 的 in-flight stop完成。放在finally中意味着无论诊断成功与否语言服务器进程都能被可靠回收不会在核查结束后残留孤儿进程。观察结果与证据充分性分析实测输出文档记录的核查结果为三个文件全部 0 条错误诊断script/build.ts: 0 error diagnostics script/build-graph-dependencies.test.ts: 0 error diagnostics packages/senpi-task/src/lifecycle/admission-lease.test.ts: 0 error diagnostics这三个文件覆盖了构建脚本script/build.ts、构建依赖测试script/build-graph-dependencies.test.ts以及生命周期准入租约测试packages/senpi-task/src/lifecycle/admission-lease.test.ts是任务工作树中与 Windows CI 根因直接相关的样本点。为什么这份证据是充分的文档给出了明确的充分性论证可归纳为三点每一点都能在源码中找到支撑真实语言服务器诊断结果并非静态扫描或正则匹配而是通过LspClient.diagnostics()向真实语言服务器发起 LSP 请求得到同源实现执行路径复用了仓库正式发布的同一套第一方 LSP 核心lsp-core与 request-context 路径而非核查专用的旁路实现扎根于真实工作树上下文 cwd 指向实际任务工作树路径解析、workspace 根发现、服务器解析全部基于该工作树进行。从实现角度再补一条佐证目录模式的聚合实现aggregateDiagnosticsForDirectorypackages/lsp-core/src/lsp/directory-diagnostics.ts会按扩展名收集文件默认跳过node_modules、.git、dist、build、.next、out等目录以并发上限 4 逐文件请求诊断并在输出中汇总Files scanned、Files with errors、Total diagnostics等统计项。也就是说即便核查对象是一个目录得到的结果也附带完整的扫描元数据可审计、可复现。清理收据进程无残留文档最后记录了清理收据disposeDefaultLspManager()完成Bun 驱动以退出码 0 结束。唯一残存的typescript-language-server进程其 cwd 为/Users/yeongyu/sionicai/arbiter-wt/stm-5917-skill-sanitization与本次核查无关因此保持原样未做处理。这份收据说明了三点核查纪律一是默认管理器被正确释放这是finallydisposeDefaultLspManager的预期行为二是核查进程干净退出exit 0三是残存的无关服务器进程不是本次核查产生的依据是其工作目录指向另一个完全不同的任务工作树因此不应被误杀。这种只清理自己创建的进程、不越界处理无关进程的做法正是诊断核查过程中资源管理的最佳实践。复盘这套核查方案的关键要点把整份核查记录还原成可复用的方法核心要点如下环节关键动作源码锚点上下文安装createStandaloneMcpRequestContext({ cwd })runWithRequestContextpackages/lsp-core/src/request-context.ts工具调用executeLspTool(diagnostics, { severity: error })packages/lsp-core/src/tools/runtime.ts严重级别过滤severity五档取值非法值回退allpackages/lsp-core/src/tools/parameters.ts目录模式按扩展名聚合扫描、并发 4、跳过构建产物目录packages/lsp-core/src/lsp/directory-diagnostics.ts资源清理finally中disposeDefaultLspManager()Promise.allSettled等待停止packages/lsp-core/src/lsp/manager.ts结果核验输出统计项 清理收据双重复核本文档lsp-diagnostics.md的 What/Why/Cleanup 结构当你再次遇到目标工作树不在固定 workspace 根内的诊断核查需求时可以直接复用这套模式独立安装请求上下文 → 通过第一方executeLspTool执行诊断 → 在finally中释放默认管理器 → 最后对照结果输出 进程收据双重确认证据有效性与资源零残留。这条链路同时保证了路径定位准确、实现与发布版本同源、进程生命周期可控也正是该证据在 Windows CI 根因分析中被判定为充分的原因所在。【免费下载链接】oh-my-openagentOmO: Just type mass ulw keyword with your prompt. Now you are the master of graph engineering.项目地址: https://gitcode.com/gh_mirrors/oh/oh-my-openagent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考