@epoch-agent/plugin-lsp 0.1.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/LICENSE ADDED
@@ -0,0 +1,219 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
203
+
204
+ --------------------------------------------------------------------------------
205
+
206
+ epoch-agent
207
+ Copyright 2024-2026 bowen
208
+
209
+ Portions of this software are derived from the Gemini CLI
210
+ (https://github.com/google-gemini/gemini-cli), Copyright 2025 Google LLC,
211
+ licensed under the Apache License, Version 2.0. Those files have been modified;
212
+ each one retains its original copyright notice and SPDX-License-Identifier
213
+ header. See the NOTICE file in the project source repository for the complete
214
+ list of derived files.
215
+
216
+ This software also quotes, with attribution, two lines of prose from OpenAI
217
+ Codex (https://github.com/openai/codex), Copyright 2025 OpenAI, licensed under
218
+ the Apache License, Version 2.0. No source file is a derived work of Codex.
219
+ See the NOTICE file for details.
package/README.md ADDED
@@ -0,0 +1,185 @@
1
+ # @epoch-agent/plugin-lsp
2
+
3
+ LSP 诊断插件。**一个工具**:`lsp_diagnostics` —— 用**项目自己的** language server
4
+ 回答「我刚改的这几个文件,编译器怎么说」。
5
+
6
+ - ✅ **做**:探测 language server(项目 `node_modules` → PATH)、起一个常驻连接、
7
+ `didOpen` / `didChange` 后收 `publishDiagnostics`、判「收全了没」、排版给模型看
8
+ - ❌ **不做**:**不捆绑任何 server**(探不到就如实报「跳过 + 装法」)、不做补全 /
9
+ 跳转 / 重命名、不做项目级构建、不自己 spawn(进程一律走
10
+ [infra](../../infra) 的 `startLongLivedProcess`)
11
+ - **依赖**:[protocol](../../protocol) + [infra](../../infra) +
12
+ `vscode-jsonrpc` / `vscode-languageserver-protocol`
13
+
14
+ ## 工具参数
15
+
16
+ ```json
17
+ { "paths": ["src/a.ts", "src/b.tsx"] }
18
+ ```
19
+
20
+ | 参数 | 必填 | 说明 |
21
+ | ------- | ---- | -------------------------------------------------------- |
22
+ | `paths` | ✅ | 文件路径数组,相对工作目录或绝对路径。一次最多 50 个文件 |
23
+
24
+ 单个路径写成字符串(`"paths": "src/a.ts"`)也收 —— 那是很自然的手误,
25
+ 为它回一条参数错误等于白烧一轮。
26
+
27
+ 当前认得 8 个扩展名:`.cjs` `.cts` `.js` `.jsx` `.mjs` `.mts` `.ts` `.tsx`
28
+ (唯一真源是 [servers.ts](src/servers.ts) 的 `supportedExtensions()`,
29
+ 工具描述和报错提示都从那儿取)。不认的扩展名进 `unsupported`,
30
+ 工作目录外的路径进 `rejected`,**都不会让这次调用失败**。
31
+
32
+ ## 输出是文本,不是 JSON 数组
33
+
34
+ ```
35
+ 2 条诊断(1 error · 1 warning) · 已检查 2 个文件
36
+
37
+ src/a.ts:12:5 error typescript(2322): 不能将类型 "string" 分配给类型 "number"
38
+ src/b.tsx:3:10 warning typescript(6133): "unused" 已声明但从未读取
39
+ ```
40
+
41
+ core 的 tool-executor 对非字符串 `output` 一律 `JSON.stringify`,那一行 JSON 里
42
+ 每条诊断都要重复七个键名 —— 对 30 条诊断是白烧一倍多的 token,而模型从
43
+ `src/a.ts:12:5 error …` 里读到的信息一模一样(它每天在 tsc / eslint 的输出里
44
+ 见的就是这个)。字段一个没少,只是换了排布。
45
+
46
+ **三件事一定在输出里**:检查了哪些文件(没这行,「没发现问题」和「一个文件都没
47
+ 检查」长得一样)、跳过的语言 + 安装命令、被截断了多少(上限 100 条,
48
+ 按 severity 优先保留 error)。
49
+
50
+ ## 两处与方案 38 原文的偏差
51
+
52
+ 1. **`paths` 是必填的。** 方案里「不传就检查本轮改过的文件」要拿
53
+ `CheckpointManager` 那份本轮改动清单,而插件的 `ToolContext` 只有
54
+ `{sessionId, workDir, extraRoots, permissionLevel, signal, onOutput}` —— 补这条得改
55
+ core 的 checkpoint 接口。先做成必填(模型知道自己刚改了哪几个文件),那条留给后续 PR
56
+ ([验收记录 8.2](../../../docs/verify/VERIFY_RECORD-38-lsp-diagnostics.md))。
57
+ 2. **输出是排好版的文本而不是 `[{file, line, ...}]`**,理由见上一节。
58
+
59
+ ## 三级探测,一个 server 都不捆绑
60
+
61
+ ```
62
+ 1. 项目本地 node_modules/<pkg> ← TypeScript 那条「版本跟项目走」的硬要求
63
+ 2. PATH
64
+ 3. 都没有 → 该语言 skipped + 安装命令,其它语言不受影响
65
+ ```
66
+
67
+ 和 [infra 的 ripgrep 探测](../../infra/src/ripgrep.ts)反着来:那边捆绑一个 6MB
68
+ 单文件,这边一个都不捆。language server 是一堆文件,而且**强绑定用户的项目配置**
69
+ —— 拿全局的 tsc 去检查一个 pin 了 5.8 的项目,报出来的错是假的。所以还额外把项目
70
+ 自己那份 `node_modules/typescript/lib/tsserver.js` 显式传进
71
+ `initializationOptions.tsserver.path`:server 自己也会找 workspace 版本,
72
+ 但那是它内部的一条兜底顺序,它升一版换个顺序我们就悄悄换了 TS 版本报错。
73
+
74
+ **故意不碰 `node_modules/.bin`。** Windows 上那儿躺的是 npm 生成的 `.cmd` 垫片,
75
+ Node 的 `spawn` 不过 shell 就执行不了;而 pnpm 下它是符号链接,跟到哪儿要看安装
76
+ 布局。走 `node_modules/<pkg>/package.json` 的 `bin` 字段拿真实入口、用
77
+ `process.execPath` 跑它,两个平台同一条代码路径,且**必然是项目里那一份**。
78
+ PATH 命中 `.cmd` / `.bat` 时才走 `cmd.exe /d /s /c "<整条命令>"` + `verbatim`。
79
+
80
+ > ⚠️ **项目本地那一档带 `ELECTRON_RUN_AS_NODE=1`**(`launch.env`,值来自 infra 的
81
+ > `EXEC_PATH_AS_NODE_ENV`)。嵌进 Electron 主进程的宿主里 `process.execPath`
82
+ > **不是 node** 而是宿主自己的可执行文件,少了那一格,起 language server 等于
83
+ > 启动第二个 app 实例。PATH 那两档起的是真可执行文件,不需要。
84
+ > 全仓「谁把 execPath 当 node 使」由根 `__tests__/exec-path-as-node.test.ts` 数着。
85
+
86
+ > PATH 扫描**保留绝对路径**去 spawn,而不是像 ripgrep 那样丢掉路径 spawn 裸名字。
87
+ > 因为这个工具的输入正是「别人的仓库」:`shell: true` + 裸名字在 Windows 上,
88
+ > 目标仓库根目录里放一个同名 `.cmd` 就能劫持。我们的扫描列表里没有 cwd。
89
+
90
+ ## 诊断是**推**过来的,所以「收全了没」要自己判
91
+
92
+ LSP 里没有「诊断请求」这种东西 —— typescript-language-server 4.4 也不支持 3.17 的
93
+ pull 模式(`initialize` 的结果里没有 `diagnosticProvider`)。server 想发就发,
94
+ 而 tsserver 一个文件会**分三次**发(语法 / 语义 / 建议,各自 50ms 去抖),
95
+ 先到的那一份是不全的。
96
+
97
+ **判据是静默**:请求的文件都答过了,且最近一次动静距今超过 `settleMs`(默认 400ms,
98
+ `fallbackMs` 1.5s,硬超时 15s)。
99
+
100
+ 还有一条更阴的:**内容没变时 server 干脆不发。** `FileDiagnostics.update()` 里有
101
+ 一句「这一类上次是空、这次还是空就不推送」,于是一个干净的文件被诊断第二次时
102
+ 一条消息都收不到。按「没答复 = 没结果」处理就会把最正常的那种情况报成
103
+ 「server 没回答」。所以有第二条判据:**没答复但有上一次的结果,就用上一次的**。
104
+ [e2e.test.ts](__tests__/e2e.test.ts) 拿真 server 盯着这一条。
105
+
106
+ ## 进程池:懒启动 / 10 分钟回收 / 崩一次重启一次
107
+
108
+ | 策略 | 值 |
109
+ | ---------- | --------------------------------------------- |
110
+ | 启动时机 | 懒启动 —— 第一次 `lsp_diagnostics` 才起 |
111
+ | 空闲回收 | 10 分钟没用就关(大项目的 tsserver 吃几个 G) |
112
+ | 并发 | 每种语言一个实例,**按项目根隔离** |
113
+ | 初始化超时 | 每种 server 自己给(TypeScript 是 30s) |
114
+ | 崩溃 | 下次调用重启一次;再崩就本会话内不再试 |
115
+
116
+ 池是**模块级单例**:工具每次调用都是一次新的 `execute`,而 language server 的意义
117
+ 全在于「活着、缓存着上次的语法树」。挂在调用上等于每次冷启一个 tsserver,
118
+ 那比让模型自己跑 tsc 还慢。
119
+
120
+ 「不可用」的每一条路径 —— 没装、起不来、初始化超时、崩了 —— 交出来的都是
121
+ `{ ok: false, reason }` 而不是 throw。一个 server 起不来只该让它那门语言的文件被
122
+ 跳过。**「没装」不计入失败次数**:那不是「试了没成」,计进去的话用户装完 server
123
+ 还得重启 epoch 才能用上。
124
+
125
+ **退出时宿主什么都不用做**:进程在 infra 的进程表里,`cleanupBackgroundProcesses()`
126
+ 会一并收掉。`disposeLspServers()` 是给「想早点收、且要走 LSP `shutdown` 礼节」的
127
+ 宿主用的。
128
+
129
+ ## 为什么是 `file_read` 而不是 `command`
130
+
131
+ ```ts
132
+ annotations: { readOnlyHint: true, idempotentHint: false, openWorldHint: false }
133
+ operation: 'file_read'
134
+ ```
135
+
136
+ 这个工具对外的语义就是「读几个文件、报问题」。底下确实起了子进程,但那个进程是
137
+ **我们表里写死的**([servers.ts](src/servers.ts)),参数里没有任何一段来自模型。
138
+ 归到 `command` 只会让每次诊断都弹一次审批 —— 而 core 的 system prompt 恰恰要求
139
+ 模型每轮改完都调它。`idempotentHint: false` 是因为文件内容会变:同一批路径
140
+ 这一秒和下一秒的诊断可以不一样。
141
+
142
+ `describeTarget` 给的是文件清单,不给就退回 `JSON.stringify(args)`,
143
+ 换个顺序传同样几个文件就不命中审批缓存了。
144
+
145
+ ## 加一门语言 = 往 `SERVERS` 里加一条
146
+
147
+ [servers.ts](src/servers.ts) 的 `SERVERS` 是**唯一**需要改的地方,client / pool /
148
+ diagnose 一行都不用动。别让语言判定漏到别处去。Python / Rust / Go 是方案 38 的
149
+ PR-3,**刻意砍掉的**(不是做到一半放弃)—— 砍的判据和真要做时的两条成本写在
150
+ [验收记录 8.1](../../../docs/verify/VERIFY_RECORD-38-lsp-diagnostics.md)。
151
+
152
+ ## 文件
153
+
154
+ | 文件 | 内容 |
155
+ | ------------- | ----------------------------------------------------------- |
156
+ | `index.ts` | `lspPlugin` + 模块级池 + `disposeLspServers()` |
157
+ | `servers.ts` | 支持哪些语言、怎么起、`tsserver.path` 怎么找 |
158
+ | `detect.ts` | 三级探测(不 spawn,纯文件系统问题) |
159
+ | `client.ts` | 一个 server 连接:五条消息 + 静默判据 + 崩溃回调 + 优雅退出 |
160
+ | `pool.ts` | 懒启动 / 空闲回收 / 并发去重 / 失败计数 |
161
+ | `diagnose.ts` | 编排:路径归一 → 边界校验 → 按 server 分组 → 收结果 |
162
+ | `format.ts` | 渲染成模型读的那段文字(截断 + 按 severity 优先) |
163
+ | `find-up.ts` | 逐级往上找一个相对路径 |
164
+ | `types.ts` | 本包的类型 |
165
+
166
+ 错误码占 **8xxx 段**:`8001` 参数不对、`8002` 我们没想到的形态。别复用别人的段 ——
167
+ 用户报错时那四位数是唯一能一眼定位到哪个包的东西。
168
+
169
+ ## 开发
170
+
171
+ ```bash
172
+ pnpm --filter @epoch-agent/plugin-lsp test
173
+ ```
174
+
175
+ 七个用例文件。两处值得先看:
176
+
177
+ - [fixtures/fake-server.mjs](__tests__/fixtures/fake-server.mjs) 是一个**手写分帧**
178
+ 的假 LSP server(故意不用 `vscode-jsonrpc`,那样才是一份独立的参照实现)。
179
+ `process.argv[2]` 选行为:`normal` / `three-kinds` / `silent` / `once` /
180
+ `hang-init` / `crash-on-open`
181
+ - [e2e.test.ts](__tests__/e2e.test.ts) 用**真的** typescript-language-server,
182
+ 被检查的文件写在临时目录里 —— 故意报错的 fixture 不该进本仓库的 tsc / lint 范围
183
+
184
+ `vitest.config.ts` 的 `testTimeout` 是 60s:起一个真 tsserver 加初始化,
185
+ 默认的 5000ms 不够。