@epoch-agent/plugin-lsp 3.2.0 → 4.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/dist/index.d.ts CHANGED
@@ -193,50 +193,40 @@ interface ResolveServerOptions {
193
193
  declare function resolveServer(spec: ServerSpec, projectRoot: string, options?: ResolveServerOptions): ServerResolution;
194
194
 
195
195
  /**
196
- * 一个 language server 连接(方案 38)。
196
+ * 一个 language server 连接。
197
197
  *
198
198
  * 协议实现**不手写**:`vscode-jsonrpc` 负责分帧和 JSON-RPC,
199
199
  * `vscode-languageserver-protocol` 提供消息描述符和类型。真正用到的只有五条:
200
200
  * `initialize` / `initialized` / `didOpen` / `didChange` / `publishDiagnostics`
201
201
  * (外加 `didClose` 做逐出、`shutdown` + `exit` 做优雅退出)。
202
202
  *
203
- * 进程**一律走 infra 的 `startLongLivedProcess`**(方案 36),这个包里不长
204
- * 第二套进程管理 —— 顺带的好处是 `epoch` 退出时 server 被天然一并收掉(验收 7),
205
- * 我们一行清理代码都不用写。
203
+ * 进程**一律走 infra 的 `startLongLivedProcess`**,这个包里不长第二套进程管理 ——
204
+ * 顺带的好处是 `epoch` 退出时 server 被天然一并收掉,我们一行清理代码都不用写。
206
205
  *
207
- * 收它的那条链子(方案 60 之后):`startLongLivedProcess` 登记进的是**进程级
208
- * 兜底表** —— 这个池是模块级单例、按项目根共享,压根没有 `sessionId`,归兜底表
209
- * 是结论不是妥协(判据在 [README](../README.md))。最后一个 runtime
210
- * `dispose()` → `OwnedProcessTable.release()` 把兜底表的引用计数减到 0,那一下
211
- * 连兜底表一起收。一个进程一个 runtime 的 CLI / TUI 每次 dispose 都归零,
212
- * 所以和方案 60 之前逐字节相同;两个 runtime 并存时先走的那个碰不到这个池
206
+ * 收它的那条链子:`startLongLivedProcess` 登记进的是**进程级兜底表** —— 这个池是
207
+ * 模块级单例、按项目根共享,压根没有 `sessionId`,归兜底表是结论不是妥协(判据在
208
+ * [README](../README.md))。最后一个 runtime `dispose()` → `OwnedProcessTable.release()`
209
+ * 把引用计数减到 0 时连兜底表一起收;两个 runtime 并存时先走的那个碰不到这个池
213
210
  * (否则表现是「跑了条定时任务,之后 `lsp_diagnostics` 每次都要重新冷启 tsserver」)。
214
211
  *
215
212
  * ## 诊断是**推**过来的,所以「什么时候算收全了」得自己判
216
213
  *
217
- * 这个协议里没有「诊断请求」这种东西(typescript-language-server 4.4 也不支持
218
- * 3.17 的 pull 模式 —— `initialize` 的结果里没有 `diagnosticProvider`)。
219
- * server 想发就发,而 tsserver 一个文件会**分三次**发(语法 / 语义 / 建议,
220
- * 各自 50ms 去抖),先到的那一份是不全的。
221
- *
222
- * 判据因此是**静默**:请求的文件都答过了,且最近一次动静距今超过 `settleMs`。
214
+ * 这个协议里没有「诊断请求」这种东西(typescript-language-server 4.4 也不支持 3.17
215
+ * 的 pull 模式 —— `initialize` 的结果里没有 `diagnosticProvider`)。server 想发就发,
216
+ * 而 tsserver 一个文件会**分三次**发(语法 / 语义 / 建议,各自 50ms 去抖),先到的
217
+ * 那一份是不全的。判据因此是**静默**:请求的文件都答过了,且最近一次动静距今超过
218
+ * `settleMs`。
223
219
  *
224
220
  * ### 还有一条更阴的:内容没变时 server 干脆不发
225
221
  *
226
- * typescript-language-server 的 `FileDiagnostics.update()` 里有这么一句
227
- * (lib/cli.mjs):
228
- *
229
- * ```js
230
- * if (this.diagnosticsPerKind.get(kind)?.length === 0 && diagnostics.length === 0) return;
231
- * ```
232
- *
233
- * 也就是「这一类上次是空、这次还是空」就**不推送**。于是一个干净的文件被诊断
234
- * 第二次时一条消息都收不到 —— 如果按「没答复 = 没结果」处理,就会把它报成
235
- * 「server 没回答」,而它其实是最正常的那种情况。
222
+ * typescript-language-server 的 `FileDiagnostics.update()`(lib/cli.mjs)里有一句
223
+ * `if (this.diagnosticsPerKind.get(kind)?.length === 0 && diagnostics.length === 0) return;`
224
+ * —— 「这一类上次是空、这次还是空」就**不推送**。于是一个干净的文件被诊断第二次时
225
+ * 一条消息都收不到;按「没答复 = 没结果」处理就会把它报成「server 没回答」,而它
226
+ * 其实是最正常的那种情况。
236
227
  *
237
- * 所以有第二条判据:**没答复但有上一次的结果,就用上一次的**。这在语义上是
238
- * 对的,因为那句 `return` 恰好只在「什么都没变、也没什么可报」时生效;一旦某一类
239
- * 从有变没或从没变有,它就会照常推送。
228
+ * 所以有第二条判据:**没答复但有上一次的结果,就用上一次的**。那句 `return` 恰好只在
229
+ * 「什么都没变、也没什么可报」时生效;一旦某一类从有变没或从没变有,它就会照常推送。
240
230
  */
241
231
 
242
232
  /** 一次批量诊断的输入 */
@@ -315,14 +305,13 @@ declare class LspClient implements DiagnosticsClient {
315
305
  private stderr;
316
306
  private constructor();
317
307
  get pid(): number;
318
- /** 还能用吗。池按这个决定要不要重启(验收 9) */
308
+ /** 还能用吗。池按这个决定要不要重启 */
319
309
  get alive(): boolean;
320
310
  /**
321
311
  * 起进程 + `initialize` + `initialized`。
322
312
  *
323
- * 起不来 / 初始化超时都**返回 `null` 而不抛** —— 调用方据此把这个语言标记
324
- * 不可用,整轮照常出结果(验收 8)。抛出去的话一个慢 server 就能让整次工具
325
- * 调用失败,而那正是这条验收要避免的。
313
+ * 起不来 / 初始化超时都**返回 `null` 而不抛** —— 调用方据此把这个语言标记不可用,
314
+ * 整轮照常出结果。抛出去的话一个慢 server 就能让整次工具调用失败。
326
315
  */
327
316
  static start(options: StartClientOptions): Promise<LspClient | null>;
328
317
  /** 订阅推送 + 兜住 server 反过来发的那几种请求 */
@@ -333,7 +322,7 @@ declare class LspClient implements DiagnosticsClient {
333
322
  /**
334
323
  * 打开 / 更新这批文件,等到静默,交出诊断。
335
324
  *
336
- * @throws {LspServerDownError} 进程中途没了(验收 9 的输入)
325
+ * @throws {LspServerDownError} 进程中途没了
337
326
  */
338
327
  diagnose(documents: DocumentInput[], wait: WaitOptions): Promise<DiagnoseBatch>;
339
328
  /** 没打开过就 `didOpen`,打开过就 `didChange`(全量) */
@@ -345,21 +334,17 @@ declare class LspClient implements DiagnosticsClient {
345
334
  *
346
335
  * 先按协议 `shutdown` + `exit`,给它清临时文件的机会;超预算就直接杀树。
347
336
  * **杀树那一步不能省** —— `typescript-language-server` 自己还 fork 了一个
348
- * `tsserver.js`,只杀父进程会留下孤儿(验收 7)。
337
+ * `tsserver.js`,只杀父进程会留下孤儿。
349
338
  *
350
339
  * ## ⚠️ 三步的**顺序**是判据:拍快照 → 优雅关 → 清扫快照
351
340
  *
352
- * 这里原来只有两步(优雅关 → `killTrackedProcess`),而那是**漏的**:
353
341
  * `killTrackedProcess` 底下是 `killProcessTree`,靠的是**现场遍历**
354
- * (`pgrep -P` / `taskkill /t`)。等我们优雅关完,直接子进程已经退了 ——
355
- * 树的根没了,孙进程的 PPID 指向一个死 pid,遍历再也找不到它们。
356
- * 于是 `tsserver` 那一层在 POSIX 上被 init 收养、在 Windows 上直接常驻到关机。
342
+ * (`pgrep -P` / `taskkill /t`)。等我们优雅关完,直接子进程已经退了 —— 树的根
343
+ * 没了,孙进程的 PPID 指向一个死 pid,遍历再也找不到它们:`tsserver` 那一层在
344
+ * POSIX 上被 init 收养、在 Windows 上直接常驻到关机。所以快照必须在**动手之前**拍。
357
345
  *
358
- * 所以快照必须在**动手之前**拍。这不是新发明:`plugin-mcp` 的
359
- * `closeTransportAndReap` 就是这三步,而它有一条会红的用例守着;
360
- * 我们这边验收 #7 一直挂着「双平台手验」没人做,于是漏了一年半。
361
- * 2026-08-12 补的 [orphan-reap.test.ts](../__tests__/orphan-reap.test.ts)
362
- * 把这条钉死了 —— 把下面的快照挪到 `killTrackedProcess` 之后,它当场红。
346
+ * `orphan-reap.test.ts` 把这条钉死了 —— 把下面的快照挪到 `killTrackedProcess`
347
+ * 之后,它当场红。
363
348
  */
364
349
  dispose(): Promise<void>;
365
350
  }
package/dist/index.js CHANGED
@@ -34,7 +34,7 @@ var MAX_OPEN_DOCUMENTS = 64;
34
34
  var POLL_STEP_MS = 25;
35
35
  var LspServerDownError = class extends Error {
36
36
  constructor(detail) {
37
- super(`language server \u5DF2\u9000\u51FA\uFF1A${detail}`);
37
+ super(`language server exited: ${detail}`);
38
38
  this.detail = detail;
39
39
  this.name = "LspServerDownError";
40
40
  }
@@ -137,8 +137,8 @@ var LspClient = class _LspClient {
137
137
  this.connection.onRequest(RegistrationRequest.type, () => void 0);
138
138
  this.connection.onRequest(UnregistrationRequest.type, () => void 0);
139
139
  this.connection.onRequest(WorkDoneProgressCreateRequest.type, () => void 0);
140
- this.connection.onError(() => this.markDead("JSON-RPC \u8FDE\u63A5\u9519\u8BEF"));
141
- this.connection.onClose(() => this.markDead("JSON-RPC \u8FDE\u63A5\u5173\u95ED"));
140
+ this.connection.onError(() => this.markDead("JSON-RPC connection error"));
141
+ this.connection.onClose(() => this.markDead("JSON-RPC connection closed"));
142
142
  }
143
143
  appendStderr(chunk) {
144
144
  this.stderr = (this.stderr + chunk).slice(-STDERR_KEEP);
@@ -146,7 +146,7 @@ var LspClient = class _LspClient {
146
146
  markDead(detail, onCrash) {
147
147
  if (this.dead) return;
148
148
  const tail = this.stderr.trim();
149
- this.dead = tail ? `${detail}\uFF1Bstderr: ${tail.slice(-500)}` : detail;
149
+ this.dead = tail ? `${detail}; stderr: ${tail.slice(-500)}` : detail;
150
150
  if (!this.disposing) onCrash?.(this.dead);
151
151
  }
152
152
  async initialize(projectRoot) {
@@ -258,7 +258,7 @@ var LspClient = class _LspClient {
258
258
  this.connection.dispose();
259
259
  } catch {
260
260
  }
261
- this.dead ??= "\u5DF2\u4E3B\u52A8\u5173\u95ED";
261
+ this.dead ??= "disposed";
262
262
  await killTrackedProcess(this.pidValue);
263
263
  if (tree.length > 0) await killStampedPids(tree, { escalate: true });
264
264
  }
@@ -322,7 +322,7 @@ var TYPESCRIPT = {
322
322
  pkg: "typescript-language-server",
323
323
  bin: "typescript-language-server",
324
324
  args: ["--stdio"],
325
- install: "npm i -D typescript-language-server typescript\uFF08\u6216\u5168\u5C40 npm i -g\uFF09",
325
+ install: "npm i -D typescript-language-server typescript (or install globally with npm i -g)",
326
326
  initTimeoutMs: 3e4,
327
327
  initializationOptions: (projectRoot) => {
328
328
  const path = tsserverPath(projectRoot);
@@ -394,7 +394,7 @@ async function classify(paths, workDir, outcome) {
394
394
  const absPath = isAbsolute(raw) ? resolve2(raw) : resolve2(root, raw);
395
395
  const rel = relative(root, absPath);
396
396
  if (rel !== "" && (rel.startsWith("..") || isAbsolute(rel))) {
397
- outcome.rejected.push({ path: raw, reason: `\u4E0D\u5728\u5DE5\u4F5C\u533A ${root} \u5185` });
397
+ outcome.rejected.push({ path: raw, reason: `outside the workspace ${root}` });
398
398
  continue;
399
399
  }
400
400
  const key = uriKey(`file://${absPath.split(sep).join("/")}`);
@@ -404,11 +404,11 @@ async function classify(paths, workDir, outcome) {
404
404
  try {
405
405
  const info = await stat(absPath);
406
406
  if (!info.isFile()) {
407
- outcome.rejected.push({ path: display, reason: "\u4E0D\u662F\u6587\u4EF6" });
407
+ outcome.rejected.push({ path: display, reason: "not a file" });
408
408
  continue;
409
409
  }
410
410
  } catch {
411
- outcome.rejected.push({ path: display, reason: "\u6587\u4EF6\u4E0D\u5B58\u5728" });
411
+ outcome.rejected.push({ path: display, reason: "file does not exist" });
412
412
  continue;
413
413
  }
414
414
  const match = serverForExtension(extname(absPath));
@@ -452,7 +452,7 @@ async function runGroup(spec, files, input, outcome) {
452
452
  } catch (err) {
453
453
  outcome.rejected.push({
454
454
  path: file.display,
455
- reason: `\u8BFB\u4E0D\u51FA\u6765\uFF08${err instanceof Error ? err.message : String(err)}\uFF09`
455
+ reason: `cannot read it (${err instanceof Error ? err.message : String(err)})`
456
456
  });
457
457
  continue;
458
458
  }
@@ -464,7 +464,7 @@ async function runGroup(spec, files, input, outcome) {
464
464
  try {
465
465
  batch = await acquired.client.diagnose(documents, input.wait ?? DEFAULT_WAIT);
466
466
  } catch (err) {
467
- const reason = err instanceof LspServerDownError ? err.message : `\u8BCA\u65AD\u5931\u8D25\uFF08${String(err)}\uFF09`;
467
+ const reason = err instanceof LspServerDownError ? err.message : `diagnostics failed (${String(err)})`;
468
468
  outcome.skipped.push(skip(spec, reason, files));
469
469
  return;
470
470
  }
@@ -477,7 +477,11 @@ async function runGroup(spec, files, input, outcome) {
477
477
  if (batch.pending.length > 0) {
478
478
  const stuck = batch.pending.map((key) => byKey.get(key)).filter((f) => !!f);
479
479
  outcome.skipped.push(
480
- skip(spec, `server \u672A\u5728 ${(input.wait ?? DEFAULT_WAIT).timeoutMs}ms \u5185\u7ED9\u51FA\u7ED3\u679C`, stuck)
480
+ skip(
481
+ spec,
482
+ `the server gave no result within ${(input.wait ?? DEFAULT_WAIT).timeoutMs}ms`,
483
+ stuck
484
+ )
481
485
  );
482
486
  }
483
487
  }
@@ -491,12 +495,12 @@ async function diagnosePaths(input) {
491
495
  };
492
496
  const paths = input.paths.slice(0, MAX_FILES_PER_CALL);
493
497
  for (const extra of input.paths.slice(MAX_FILES_PER_CALL)) {
494
- outcome.rejected.push({ path: extra, reason: `\u4E00\u6B21\u6700\u591A ${MAX_FILES_PER_CALL} \u4E2A\u6587\u4EF6` });
498
+ outcome.rejected.push({ path: extra, reason: `at most ${MAX_FILES_PER_CALL} files per call` });
495
499
  }
496
500
  const candidates = await classify(paths, resolve2(input.workDir), outcome);
497
501
  for (const group of groupBySpec(candidates).values()) {
498
502
  if (input.signal?.aborted) {
499
- outcome.skipped.push(skip(group.spec, "\u5DF2\u53D6\u6D88", group.files));
503
+ outcome.skipped.push(skip(group.spec, "cancelled", group.files));
500
504
  continue;
501
505
  }
502
506
  await runGroup(group.spec, group.files, input, outcome);
@@ -533,7 +537,7 @@ function renderCounts(items) {
533
537
  }
534
538
  function renderFiles(files) {
535
539
  if (files.length <= MAX_FILE_NAMES) return files.join(", ");
536
- return `${files.slice(0, MAX_FILE_NAMES).join(", ")} \u7B49 ${files.length} \u4E2A`;
540
+ return `${files.slice(0, MAX_FILE_NAMES).join(", ")} \u2026 (${files.length} total)`;
537
541
  }
538
542
  function pickItems(items) {
539
543
  if (items.length <= MAX_ITEMS) return { shown: items, dropped: 0 };
@@ -551,29 +555,29 @@ function formatOutcome(outcome) {
551
555
  const { checked, diagnostics, skipped, unsupported, rejected } = outcome;
552
556
  if (diagnostics.length === 0) {
553
557
  lines.push(
554
- checked.length > 0 ? `\u672A\u53D1\u73B0\u95EE\u9898 \xB7 \u5DF2\u68C0\u67E5 ${checked.length} \u4E2A\u6587\u4EF6\uFF1A${renderFiles(checked)}` : "\u672A\u68C0\u67E5\u4EFB\u4F55\u6587\u4EF6"
558
+ checked.length > 0 ? `No problems found \xB7 checked ${checked.length} file(s): ${renderFiles(checked)}` : "No files were checked"
555
559
  );
556
560
  } else {
557
561
  const { shown, dropped } = pickItems(diagnostics);
558
562
  lines.push(
559
- `${diagnostics.length} \u6761\u8BCA\u65AD\uFF08${renderCounts(diagnostics)}\uFF09 \xB7 \u5DF2\u68C0\u67E5 ${checked.length} \u4E2A\u6587\u4EF6`,
563
+ `${diagnostics.length} diagnostic(s) (${renderCounts(diagnostics)}) \xB7 checked ${checked.length} file(s)`,
560
564
  "",
561
565
  ...shown.map(renderItem)
562
566
  );
563
567
  if (dropped > 0) {
564
- lines.push("", `\uFF08\u8FD8\u6709 ${dropped} \u6761\u672A\u5217\u51FA\uFF0C\u5DF2\u4F18\u5148\u4FDD\u7559 error\uFF09`);
568
+ lines.push("", `(${dropped} more not listed; errors were kept first)`);
565
569
  }
566
570
  }
567
571
  for (const skip2 of skipped) {
568
- lines.push("", `\u8DF3\u8FC7 ${skip2.label}\uFF1A${skip2.reason}`);
569
- if (skip2.install) lines.push(` \u5B89\u88C5\uFF1A${skip2.install}`);
570
- if (skip2.files.length > 0) lines.push(` \u672A\u68C0\u67E5\uFF1A${renderFiles(skip2.files)}`);
572
+ lines.push("", `Skipped ${skip2.label}: ${skip2.reason}`);
573
+ if (skip2.install) lines.push(` install: ${skip2.install}`);
574
+ if (skip2.files.length > 0) lines.push(` not checked: ${renderFiles(skip2.files)}`);
571
575
  }
572
576
  if (unsupported.length > 0) {
573
- lines.push("", `\u65E0\u5BF9\u5E94 language server\uFF0C\u672A\u68C0\u67E5\uFF1A${renderFiles(unsupported)}`);
577
+ lines.push("", `No matching language server; not checked: ${renderFiles(unsupported)}`);
574
578
  }
575
579
  for (const bad of rejected) {
576
- lines.push("", `\u65E0\u6CD5\u68C0\u67E5 ${bad.path}\uFF1A${bad.reason}`);
580
+ lines.push("", `Cannot check ${bad.path}: ${bad.reason}`);
577
581
  }
578
582
  return lines.join("\n");
579
583
  }
@@ -664,7 +668,7 @@ function resolveServer(spec, projectRoot, options = {}) {
664
668
  if (launch) return { available: true, launch };
665
669
  return {
666
670
  available: false,
667
- reason: `\u6CA1\u627E\u5230 ${spec.bin}\uFF08\u9879\u76EE node_modules \u548C PATH \u91CC\u90FD\u6CA1\u6709\uFF09`,
671
+ reason: `could not find ${spec.bin} (not in the project's node_modules, nor on PATH)`,
668
672
  install: spec.install
669
673
  };
670
674
  }
@@ -702,13 +706,13 @@ var LspServerPool = class {
702
706
  return { ok: true, client: existing.client };
703
707
  }
704
708
  this.entries.delete(key);
705
- this.noteFailure(key, "language server \u8FDB\u7A0B\u5DF2\u9000\u51FA");
709
+ this.noteFailure(key, "language server process has exited");
706
710
  }
707
711
  const failed = this.failures.get(key);
708
712
  if (failed && failed.count >= MAX_FAILURES) {
709
713
  return {
710
714
  ok: false,
711
- reason: `${failed.detail}\uFF08\u5DF2\u8FDE\u7EED\u5931\u8D25 ${failed.count} \u6B21\uFF0C\u672C\u6B21\u4F1A\u8BDD\u4E0D\u518D\u91CD\u542F\uFF09`
715
+ reason: `${failed.detail} (failed ${failed.count} times in a row; not restarting in this session)`
712
716
  };
713
717
  }
714
718
  const inflight = this.starting.get(key);
@@ -732,15 +736,18 @@ var LspServerPool = class {
732
736
  projectRoot,
733
737
  onCrash: (detail) => {
734
738
  this.entries.delete(key);
735
- this.noteFailure(key, `language server \u5D29\u4E86\uFF08${detail}\uFF09`);
739
+ this.noteFailure(key, `language server crashed (${detail})`);
736
740
  }
737
741
  });
738
742
  if (!client) {
739
- this.noteFailure(key, `language server \u8D77\u4E0D\u6765\u6216\u521D\u59CB\u5316\u8D85\u65F6\uFF08>${spec.initTimeoutMs}ms\uFF09`);
743
+ this.noteFailure(
744
+ key,
745
+ `language server failed to start or initialize timed out (>${spec.initTimeoutMs}ms)`
746
+ );
740
747
  const now = this.failures.get(key);
741
748
  return {
742
749
  ok: false,
743
- reason: now && now.count >= MAX_FAILURES ? `${now.detail}\uFF08\u672C\u6B21\u4F1A\u8BDD\u4E0D\u518D\u91CD\u542F\uFF09` : now?.detail ?? "\u542F\u52A8\u5931\u8D25"
750
+ reason: now && now.count >= MAX_FAILURES ? `${now.detail} (not restarting in this session)` : now?.detail ?? "failed to start"
744
751
  };
745
752
  }
746
753
  this.entries.set(key, { client, lastUsedAt: this.now() });
@@ -814,18 +821,18 @@ function fail(code, message, suggestion, started) {
814
821
  var lspPlugin = {
815
822
  name: "@epoch-agent/plugin-lsp",
816
823
  version: "0.0.0",
817
- description: "LSP \u8BCA\u65AD\u63D2\u4EF6 \u2014 \u7528\u9879\u76EE\u81EA\u5DF1\u7684 language server \u53D6\u7C7B\u578B / \u8BED\u6CD5\u9519\u8BEF",
824
+ description: "Language-server diagnostics \u2014 type and syntax errors from the project's own language server",
818
825
  tools: [
819
826
  {
820
827
  name: "lsp_diagnostics",
821
- description: `\u7528\u9879\u76EE\u81EA\u5DF1\u7684 language server \u68C0\u67E5\u6587\u4EF6\uFF0C\u8FD4\u56DE\u7C7B\u578B\u9519\u8BEF\u3001\u8BED\u6CD5\u9519\u8BEF\u548C\u672A\u4F7F\u7528\u53D8\u91CF\u7B49\u8BCA\u65AD\u3002\u53C2\u6570: paths(\u5FC5\u586B, \u6587\u4EF6\u8DEF\u5F84\u6570\u7EC4, \u76F8\u5BF9\u5DE5\u4F5C\u76EE\u5F55)\u3002\u5F53\u524D\u652F\u6301 ${supportedExtensions().join(" ")}\u3002**\u6539\u5B8C\u4EE3\u7801\u4E4B\u540E\u3001\u5BA3\u5E03\u5B8C\u6210\u4E4B\u524D\u8C03\u4E00\u6B21**\uFF0C\u628A\u672C\u8F6E\u65B0\u5EFA\u6216\u4FEE\u6539\u8FC7\u7684\u6E90\u6587\u4EF6\u90FD\u5217\u8FDB\u53BB \u2014\u2014 \u5B83\u6BD4\u8BA9 shell \u8DD1\u4E00\u904D tsc \u5FEB\u5F97\u591A\uFF0C\u56E0\u4E3A language server \u662F\u5E38\u9A7B\u7684\u3002\u6CE8\u610F\u5B83\u53EA\u505A\u5355\u6587\u4EF6\u7EA7\u8BCA\u65AD\uFF0C\u4E0D\u7B49\u4E8E\u4E00\u6B21\u5B8C\u6574\u6784\u5EFA\uFF1A\u6784\u5EFA\u811A\u672C\u3001\u6253\u5305\u914D\u7F6E\u3001\u8DE8\u5305\u7C7B\u578B\u89E3\u6790\u51FA\u7684\u95EE\u9898\u5B83\u770B\u4E0D\u5230\u3002\u6CA1\u88C5\u5BF9\u5E94 language server \u65F6\u4F1A\u5982\u5B9E\u62A5\u300C\u8DF3\u8FC7\u300D\u5E76\u7ED9\u51FA\u5B89\u88C5\u547D\u4EE4\uFF0C\u4E0D\u4F1A\u8BA9\u8FD9\u6B21\u8C03\u7528\u5931\u8D25\u3002`,
828
+ description: `Check files with the project's own language server and return type errors, syntax errors and unused-variable warnings. Supported extensions: ${supportedExtensions().join(" ")}. Call it once after changing code and before saying the work is finished, listing the source files you created or modified. It is much faster than running tsc through the shell because the language server is already resident. It only does single-file diagnostics, which is not the same as a full build: problems from build scripts, bundler config or cross-package type resolution are invisible to it. When no language server is installed it reports "skipped" and prints the install command instead of failing.`,
822
829
  parameters: {
823
830
  type: "object",
824
831
  properties: {
825
832
  paths: {
826
833
  type: "array",
827
834
  items: { type: "string" },
828
- description: '\u8981\u68C0\u67E5\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u76F8\u5BF9\u5DE5\u4F5C\u76EE\u5F55\u6216\u7EDD\u5BF9\u8DEF\u5F84\uFF09\uFF0C\u4F8B\u5982 ["src/a.ts", "src/b.tsx"]'
835
+ description: 'File paths to check (relative to the working directory, or absolute), e.g. ["src/a.ts", "src/b.tsx"].'
829
836
  }
830
837
  },
831
838
  required: ["paths"]
@@ -839,8 +846,8 @@ var lspPlugin = {
839
846
  if (!paths) {
840
847
  return fail(
841
848
  ERR_BAD_PATHS,
842
- "paths \u5FC5\u987B\u662F\u975E\u7A7A\u7684\u6587\u4EF6\u8DEF\u5F84\u6570\u7EC4",
843
- '\u628A\u672C\u8F6E\u6539\u52A8\u8FC7\u7684\u6E90\u6587\u4EF6\u5217\u8FDB\u53BB\uFF0C\u4F8B\u5982 {"paths": ["src/a.ts"]}',
849
+ "paths must be a non-empty array of file paths",
850
+ 'List the source files you changed in this turn, e.g. {"paths": ["src/a.ts"]}',
844
851
  started
845
852
  );
846
853
  }
@@ -855,8 +862,8 @@ var lspPlugin = {
855
862
  } catch (err) {
856
863
  return fail(
857
864
  ERR_UNEXPECTED,
858
- `\u8BCA\u65AD\u5931\u8D25: ${err instanceof Error ? err.message : String(err)}`,
859
- "\u53EF\u4EE5\u5148\u7EE7\u7EED\uFF0C\u6216\u6539\u7528 terminal \u8DD1\u9879\u76EE\u81EA\u5DF1\u7684\u7C7B\u578B\u68C0\u67E5\u547D\u4EE4",
865
+ `diagnostics failed: ${err instanceof Error ? err.message : String(err)}`,
866
+ "You can keep going, or run the project's own type-check command through terminal instead",
860
867
  started
861
868
  );
862
869
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoch-agent/plugin-lsp",
3
- "version": "3.2.0",
3
+ "version": "4.1.0",
4
4
  "private": false,
5
5
  "description": "LSP 诊断插件 — 探测项目自带的 language server,按文件取语义诊断",
6
6
  "repository": {
@@ -24,8 +24,8 @@
24
24
  "dependencies": {
25
25
  "vscode-jsonrpc": "^9.0.1",
26
26
  "vscode-languageserver-protocol": "^3.17.5",
27
- "@epoch-agent/infra": "3.2.0",
28
- "@epoch-agent/protocol": "3.2.0"
27
+ "@epoch-agent/infra": "4.1.0",
28
+ "@epoch-agent/protocol": "4.1.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^26.1.2",