@trim21/personal-pi-extensions 0.1.503 → 0.1.507
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/package.json +3 -2
- package/src/aft/zoom.md +0 -2
- package/src/lib/lsp/lsp.ts +83 -26
- package/src/skills/lsp-config/SKILL.md +111 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trim21/personal-pi-extensions",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.507",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
|
|
6
6
|
"keywords": [
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"@types/lodash-es": "^4.17.12",
|
|
44
44
|
"@types/node": "^24.13.3",
|
|
45
45
|
"@types/turndown": "^5.0.6",
|
|
46
|
+
"@typescript/native": "npm:typescript@^7.0.2",
|
|
46
47
|
"@vitest/coverage-v8": "^4.1.10",
|
|
47
48
|
"citty": "^0.2.2",
|
|
48
49
|
"esbuild": "^0.28.2",
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
"shlex": "^3.0.0",
|
|
61
62
|
"tsx": "^4.23.12",
|
|
62
63
|
"typebox": "1.3.1",
|
|
63
|
-
"typescript": "
|
|
64
|
+
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
64
65
|
"typescript-eslint": "^8.67.0",
|
|
65
66
|
"vitest": "^4.1.10"
|
|
66
67
|
},
|
package/src/aft/zoom.md
CHANGED
package/src/lib/lsp/lsp.ts
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* 是配置错误:全局配置在扩展加载(createLspService)时抛错,本地配置在
|
|
14
14
|
* session 开始预加载时通知,工具调用时校验抛错兜底。disabled 中未注册
|
|
15
15
|
* 的 id 直接忽略;
|
|
16
|
-
* - client 按 (root, serverID) 缓存,并发 spawn 去重,启动失败记入
|
|
17
|
-
*
|
|
16
|
+
* - client 按 (root, serverID) 缓存,并发 spawn 去重,启动失败记入
|
|
17
|
+
* broken(冷却期内跳过,冷却过后下次触碰自动重试)并主动 notify;
|
|
18
18
|
* - 工具只与 touchFile / notifyFile / diagnostics / lspDiagnosticsForFile 四个方法打交道;通知回调按请求传入。
|
|
19
19
|
*/
|
|
20
20
|
|
|
@@ -109,6 +109,11 @@ export const configDefaults = {
|
|
|
109
109
|
maxOpenDocuments: clientDefaults.maxOpenDocuments,
|
|
110
110
|
} as const;
|
|
111
111
|
|
|
112
|
+
/** 服务器启动失败后自动重试的冷却(ms);冷却内跳过,之后下次触碰自动重试。 */
|
|
113
|
+
const RETRY_COOLDOWN_MS = 60_000;
|
|
114
|
+
/** 同一服务器启动失败错误通知的最小间隔(ms),冷却重试反复失败时不刷屏。 */
|
|
115
|
+
const NOTIFY_INTERVAL_MS = 5 * 60_000;
|
|
116
|
+
|
|
112
117
|
/** 生效的工作区监听配置(缺省值已应用)。 */
|
|
113
118
|
export interface EffectiveWatchConfig {
|
|
114
119
|
enabled: boolean;
|
|
@@ -292,7 +297,10 @@ export function filterAdapters(
|
|
|
292
297
|
|
|
293
298
|
interface LspState {
|
|
294
299
|
clients: LspClient[];
|
|
295
|
-
|
|
300
|
+
/** root+serverID → 最近一次启动失败时间;冷却期过后允许自动重试。 */
|
|
301
|
+
brokenFailAt: Map<string, number>;
|
|
302
|
+
/** root+serverID → 最近一次启动失败错误通知时间;节流避免反复刷屏。 */
|
|
303
|
+
brokenNotifiedAt: Map<string, number>;
|
|
296
304
|
spawning: Map<string, Promise<LspClient | undefined>>;
|
|
297
305
|
closing: boolean;
|
|
298
306
|
/** /lsp-stop 置 true:所有工具调用不再 spawn 服务器,直到 start/reload。 */
|
|
@@ -380,10 +388,10 @@ export interface LspService {
|
|
|
380
388
|
shutdownAll(): Promise<void>;
|
|
381
389
|
/** 停止全部服务器并禁用 LSP:之后工具调用不再 spawn,直到 start/reload。 */
|
|
382
390
|
stop(): Promise<void>;
|
|
383
|
-
/**
|
|
391
|
+
/** 解除禁用;服务器在下次工具调用时惰性启动(启动失败会按冷却自动重试)。 */
|
|
384
392
|
start(): void;
|
|
385
393
|
/**
|
|
386
|
-
* 重启指定服务器:关闭其全部 client
|
|
394
|
+
* 重启指定服务器:关闭其全部 client、清除对应失败记录并解除禁用;
|
|
387
395
|
* 其余服务器不受影响。配置在下次工具调用时重新读取。
|
|
388
396
|
*/
|
|
389
397
|
reload(serverID: string): Promise<void>;
|
|
@@ -408,10 +416,26 @@ function containsPath(file: string, cwd: string): boolean {
|
|
|
408
416
|
* 内置默认服务器合并构建。globalConfigPath 供测试注入固定的全局配置
|
|
409
417
|
* 路径,避免被本机 ~/.pi/agent/lsp.json 影响。
|
|
410
418
|
*/
|
|
419
|
+
export interface LspServiceStartupOptions {
|
|
420
|
+
/**
|
|
421
|
+
* 服务器启动失败后到允许自动重试的冷却时长(ms)。冷却期内该服务器被
|
|
422
|
+
* 跳过,冷却过后下次触碰匹配文件时自动重试,无需 /lsp-reload。
|
|
423
|
+
*/
|
|
424
|
+
retryCooldownMs?: number;
|
|
425
|
+
/** 同一服务器的启动失败错误通知最小间隔(ms),防止反复重试刷屏。 */
|
|
426
|
+
notifyIntervalMs?: number;
|
|
427
|
+
/** 会话级通知:启动失败时主动上报,不依赖触发请求恰好携带 notify。 */
|
|
428
|
+
notify?: ExtensionUIContext["notify"];
|
|
429
|
+
}
|
|
430
|
+
|
|
411
431
|
export function createLspService(
|
|
412
432
|
adapters?: LspServerAdapter[],
|
|
413
433
|
globalConfigPath?: string,
|
|
434
|
+
startupOptions?: LspServiceStartupOptions,
|
|
414
435
|
): LspService {
|
|
436
|
+
const retryCooldownMs = startupOptions?.retryCooldownMs ?? RETRY_COOLDOWN_MS;
|
|
437
|
+
const notifyIntervalMs = startupOptions?.notifyIntervalMs ?? NOTIFY_INTERVAL_MS;
|
|
438
|
+
const sessionNotify = startupOptions?.notify;
|
|
415
439
|
// 扩展加载时校验全局配置(本地配置在 session_start 预加载时校验)
|
|
416
440
|
validateConfig(
|
|
417
441
|
resolveConfig(
|
|
@@ -421,7 +445,8 @@ export function createLspService(
|
|
|
421
445
|
);
|
|
422
446
|
const state: LspState = {
|
|
423
447
|
clients: [],
|
|
424
|
-
|
|
448
|
+
brokenFailAt: new Map(),
|
|
449
|
+
brokenNotifiedAt: new Map(),
|
|
425
450
|
spawning: new Map(),
|
|
426
451
|
closing: false,
|
|
427
452
|
disabled: false,
|
|
@@ -558,6 +583,33 @@ export function createLspService(
|
|
|
558
583
|
notify?: ExtensionUIContext["notify"],
|
|
559
584
|
adapterFilter?: (adapter: LspServerAdapter) => boolean,
|
|
560
585
|
): Promise<LspClient[]> {
|
|
586
|
+
/**
|
|
587
|
+
* 记录一次启动失败:进入 broken(冷却期内跳过)、渲染 status,并按节流
|
|
588
|
+
* 间隔主动 notify。错误上报优先走会话级 sessionNotify——不依赖触发请求
|
|
589
|
+
* 恰好携带 notify(否则 Read warm-up 等静默通道会把失败吞掉);未注入
|
|
590
|
+
* 会话通知时退回请求级 notify 兜底。
|
|
591
|
+
*/
|
|
592
|
+
function reportStartupFailure(
|
|
593
|
+
key: string,
|
|
594
|
+
serverID: string,
|
|
595
|
+
root: string,
|
|
596
|
+
cause: string,
|
|
597
|
+
): void {
|
|
598
|
+
const now = Date.now();
|
|
599
|
+
state.brokenFailAt.set(key, now);
|
|
600
|
+
state.servers.set(key, { serverID, root, state: "broken" });
|
|
601
|
+
updateStatusText();
|
|
602
|
+
const reporter = sessionNotify ?? notify;
|
|
603
|
+
const lastNotified = state.brokenNotifiedAt.get(key);
|
|
604
|
+
if (reporter && (lastNotified === undefined || now - lastNotified >= notifyIntervalMs)) {
|
|
605
|
+
state.brokenNotifiedAt.set(key, now);
|
|
606
|
+
reporter(
|
|
607
|
+
`LSP server "${serverID}" failed to start for ${root}: ${cause}. ` +
|
|
608
|
+
`Fix the issue or run /lsp-reload ${serverID} to retry now.`,
|
|
609
|
+
"error",
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
561
613
|
if (state.closing || state.disabled) return [];
|
|
562
614
|
if (!containsPath(file, cwd)) return [];
|
|
563
615
|
const config = await loadLspConfig(cwd, globalConfigPath);
|
|
@@ -584,7 +636,12 @@ export function createLspService(
|
|
|
584
636
|
const root = await adapter.findRoot(file, cwd);
|
|
585
637
|
if (!root) continue;
|
|
586
638
|
const key = root + adapter.id;
|
|
587
|
-
|
|
639
|
+
const failedAt = state.brokenFailAt.get(key);
|
|
640
|
+
if (failedAt !== undefined) {
|
|
641
|
+
if (Date.now() - failedAt < retryCooldownMs) continue;
|
|
642
|
+
// 冷却已过:允许重试;重试成功后下面会清除 broken 记录
|
|
643
|
+
state.brokenFailAt.delete(key);
|
|
644
|
+
}
|
|
588
645
|
|
|
589
646
|
const existing = state.clients.find((c) => c.root === root && c.serverID === adapter.id);
|
|
590
647
|
if (existing) {
|
|
@@ -603,13 +660,7 @@ export function createLspService(
|
|
|
603
660
|
try {
|
|
604
661
|
const handle = await adapter.spawn(root, cwd);
|
|
605
662
|
if (!handle) {
|
|
606
|
-
|
|
607
|
-
state.servers.set(key, { serverID: adapter.id, root, state: "broken" });
|
|
608
|
-
updateStatusText();
|
|
609
|
-
notify?.(
|
|
610
|
-
`LSP server "${adapter.id}" is not available for ${root} (binary not found)`,
|
|
611
|
-
"error",
|
|
612
|
-
);
|
|
663
|
+
reportStartupFailure(key, adapter.id, root, "binary not found");
|
|
613
664
|
return;
|
|
614
665
|
}
|
|
615
666
|
const client = await create({
|
|
@@ -635,18 +686,18 @@ export function createLspService(
|
|
|
635
686
|
state.clients.push(client);
|
|
636
687
|
state.clientExtensions.set(client, adapter.extensions);
|
|
637
688
|
state.servers.set(key, { serverID: adapter.id, root, state: "running" });
|
|
689
|
+
// 启动成功:清除失败记录,之后若再次失败会立即重新上报
|
|
690
|
+
state.brokenFailAt.delete(key);
|
|
691
|
+
state.brokenNotifiedAt.delete(key);
|
|
638
692
|
updateStatusText();
|
|
639
693
|
void ensureWatcher(cwd, notify);
|
|
640
694
|
return client;
|
|
641
695
|
} catch (error) {
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
error instanceof Error ? error.message : String(error)
|
|
648
|
-
}`,
|
|
649
|
-
"error",
|
|
696
|
+
reportStartupFailure(
|
|
697
|
+
key,
|
|
698
|
+
adapter.id,
|
|
699
|
+
root,
|
|
700
|
+
error instanceof Error ? error.message : String(error),
|
|
650
701
|
);
|
|
651
702
|
return;
|
|
652
703
|
}
|
|
@@ -845,7 +896,8 @@ export function createLspService(
|
|
|
845
896
|
});
|
|
846
897
|
state.clients = [];
|
|
847
898
|
state.clientExtensions.clear();
|
|
848
|
-
state.
|
|
899
|
+
state.brokenFailAt.clear();
|
|
900
|
+
state.brokenNotifiedAt.clear();
|
|
849
901
|
state.servers.clear();
|
|
850
902
|
await stopWatcher();
|
|
851
903
|
updateStatusText();
|
|
@@ -866,7 +918,8 @@ export function createLspService(
|
|
|
866
918
|
function start(): void {
|
|
867
919
|
state.closing = false;
|
|
868
920
|
state.disabled = false;
|
|
869
|
-
state.
|
|
921
|
+
state.brokenFailAt.clear();
|
|
922
|
+
state.brokenNotifiedAt.clear();
|
|
870
923
|
updateStatusText();
|
|
871
924
|
}
|
|
872
925
|
|
|
@@ -880,7 +933,8 @@ export function createLspService(
|
|
|
880
933
|
for (const client of targets) state.clientExtensions.delete(client);
|
|
881
934
|
for (const [key, server] of state.servers) {
|
|
882
935
|
if (server.serverID !== serverID) continue;
|
|
883
|
-
state.
|
|
936
|
+
state.brokenFailAt.delete(key);
|
|
937
|
+
state.brokenNotifiedAt.delete(key);
|
|
884
938
|
state.servers.delete(key);
|
|
885
939
|
}
|
|
886
940
|
if (state.clients.length === 0) await stopWatcher();
|
|
@@ -985,7 +1039,10 @@ export function createLspManager(
|
|
|
985
1039
|
const config = await loadLspConfig(ctx.cwd, options?.globalConfigPath);
|
|
986
1040
|
validateConfig(config, options?.adapters);
|
|
987
1041
|
if (enabledServerCount(config, options?.adapters) === 0) return;
|
|
988
|
-
const next = createLspService(options?.adapters, options?.globalConfigPath
|
|
1042
|
+
const next = createLspService(options?.adapters, options?.globalConfigPath, {
|
|
1043
|
+
// 会话级通知:任何通道触发的启动失败都主动上报(不只依赖请求方 notify)
|
|
1044
|
+
notify: (message, level) => ctx.ui.notify(message, level),
|
|
1045
|
+
});
|
|
989
1046
|
// footer status 显示当前所有 LSP server 状态(无 UI 时不显示)
|
|
990
1047
|
next.attachStatus((text) =>
|
|
991
1048
|
ctx.ui.setStatus("lsp", text ? ctx.ui.theme.fg("accent", text) : undefined),
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lsp-config
|
|
3
|
+
description: Use when 编写、检查或排查 LSP 语言服务器配置 —— 项目本地 .pi/lsp.json 或全局 ~/.pi/agent/lsp.json:配置 typescript-language-server / pyright / ruff / gopls / clangd 等服务器的 bin、include、rootMarkers、initializationOptions、tsserver.path、watch 与诊断超时;或排查服务器起不来(binary not found / provides no tsserver.js)、typescript alias 依赖下 tsserver 找不到、诊断一直为空等问题。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 配置 LSP 服务器(.pi/lsp.json)
|
|
7
|
+
|
|
8
|
+
本扩展的 LSP 层由配置驱动:**没有内置默认服务器**,一切服务器都在 lsp.json 里定义。配置解析在 `src/lib/lsp/lsp.ts`(`lspConfigSchema` / `watchConfigSchema` / 合并与校验)与 `src/lib/lsp/server-config.ts`(`serverConfigSchema`),schema 即权威文档;改代码先看这两处。
|
|
9
|
+
|
|
10
|
+
## 两个配置文件与合并语义
|
|
11
|
+
|
|
12
|
+
- 全局:`~/.pi/agent/lsp.json`(所有项目的基底)
|
|
13
|
+
- 本地:`<项目根>/.pi/lsp.json`(本地覆盖;常被 gitignore,只在本机生效)
|
|
14
|
+
|
|
15
|
+
合并是**纯函数**(`mergeConfig(global, local)`),规则:
|
|
16
|
+
|
|
17
|
+
| 段 | 合并方式 |
|
|
18
|
+
| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| 顶层其余字段 | 本地覆盖全局(浅合并) |
|
|
20
|
+
| `servers` | 按服务器 id **整条覆盖**:本地写某个 id 会替换全局同 id 的整条记录,**不是字段级合并**。只改一个字段(如补 `initializationOptions`)也必须把 `include`/`rootMarkers`/`bin`/`args`/`cwd`/`languageIdByExtension` 全部带上,否则丢全局字段 |
|
|
21
|
+
| `watch` | 字段级合并:全局为基底、本地逐字段覆盖;`ignore` 两侧**并集去重**(全局在前),本地写 watch 段不会清掉全局 ignore |
|
|
22
|
+
| 某段全局本地都未出现 | 保持缺失(调用方用 `...(watch && { watch })` 省略键,不要输出空对象) |
|
|
23
|
+
|
|
24
|
+
配置经 typebox **严格校验**(`Type.Object` 默认拒绝多余字段):未知的 `version`、多余键、非法时长格式都会在读取时抛错。`version` 当前为 1。
|
|
25
|
+
|
|
26
|
+
## 顶层字段速查
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
version / servers / enabled / disabled / watch / maxOpenDocuments /
|
|
30
|
+
diagnosticsDebounceMs / diagnosticsDocumentWaitTimeoutMs /
|
|
31
|
+
diagnosticsFullWaitTimeoutMs / diagnosticsRequestTimeoutMs / initializeTimeoutMs
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `enabled`:只启用列出的服务器 id(缺省 = 全部启用);`disabled`:从启用集中排除
|
|
35
|
+
- 时长字段:毫秒数字,或带单位的字符串(`"300ms"` / `"5s"` / `"1m"` / `"2h"`,空单位按 ms);默认值见 `clientDefaults`(client.ts):debounce 150ms、document 等待 5s、full 等待 10s、pull 请求 3s、initialize 45s、`maxOpenDocuments` 32
|
|
36
|
+
- `watch`:`enabled` / `debounceMs`(缺省 300)/ `maxBatch`(缺省 500)/ `ignore`(glob,相对工作区根的 POSIX 路径)。注意 `flushMs` 只在默认值里、**不可配**
|
|
37
|
+
|
|
38
|
+
## servers.<id> 字段
|
|
39
|
+
|
|
40
|
+
| 字段 | 说明 |
|
|
41
|
+
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
42
|
+
| `include` | 文件 glob(相对项目根或调用 cwd,任一命中即可);缺省匹配所有文件 |
|
|
43
|
+
| `kind` | `language`(缺省)或 `linter`。rename / inspect 等符号级功能**只面向 `language`**;linter 只参与诊断 |
|
|
44
|
+
| `rootMarkers` | 项目根标记文件名(从文件目录向上查找);缺省把调用 cwd 当根 |
|
|
45
|
+
| `bin` | 可执行文件:绝对路径、相对调用 cwd 的路径、或仅名字(项目工作区优先,PATH 兜底) |
|
|
46
|
+
| `args` | 启动参数数组 |
|
|
47
|
+
| `cwd` | 启动工作目录,支持 `{root}` / `{cwd}` 模板;缺省 `{root}` |
|
|
48
|
+
| `env` | 追加的环境变量:string 值支持 `{root}`/`{cwd}` 与 `${VAR}` 插值;`{ "sh": ["cmd","arg"] }` 启动时执行命令取 stdout(非零退出/空输出 → 启动失败并报错) |
|
|
49
|
+
| `languageIdByExtension` | 扩展名(含点)→ languageId;缺省回退内置映射(见 `src/lib/lsp/language.ts`,覆盖主流语言) |
|
|
50
|
+
| `startupTimeoutMs` / `diagnosticsWaitMs` | 覆盖该服务器的初始化握手 / 写文件后诊断等待(缺省用全局配置与 client 默认) |
|
|
51
|
+
| `initializationOptions` | 透传给 initialize 请求;字符串值支持 `${VAR}` / `${VAR:-default}`(读 process env,**不支持 `{root}`/`{cwd}` 模板**) |
|
|
52
|
+
| `settings` | `workspace/didChangeConfiguration` 与 `workspace/configuration` 请求的负载;缺省回退 `initializationOptions` |
|
|
53
|
+
|
|
54
|
+
## typescript-language-server:TS 从哪来(易踩坑)
|
|
55
|
+
|
|
56
|
+
typescript-language-server **不内置 TypeScript**(零依赖)。启动时按下面顺序找一个可用的 tsserver,**全找不到就在 initialize 阶段报错退出**:
|
|
57
|
+
|
|
58
|
+
1. `initializationOptions.tsserver.path`(UserSetting,优先级最高)
|
|
59
|
+
2. workspace 探测:`<项目根>/node_modules/typescript/lib/tsserver.js`
|
|
60
|
+
3. 自身 `require.resolve('typescript')`(即它安装位置能解析到的 typescript)
|
|
61
|
+
|
|
62
|
+
`tsserver.path` 必须指向 **`typescript/lib/tsserver.js` 或 `typescript/lib/` 目录的绝对路径**(或 PATH 可执行名);因为初始化字符串不支持 `{root}` 模板,无法用相对 workspace 的表达式。
|
|
63
|
+
|
|
64
|
+
**alias 依赖坑**:当项目把 typescript 写成 `npm:@typescript/typescript6`(alias stub)时,`node_modules/typescript/lib/` 下只有转发 stub(`typescript.js`/`tsserverlibrary.js`/`tsc.js`),**没有 `tsserver.js`** → workspace 探测失效;实体 tsserver 在 pnpm 虚拟 store:`node_modules/.pnpm/typescript@<真实版本>/node_modules/typescript/lib/tsserver.js`(目录名无 peer 后缀、相对稳定;**升级 typescript 版本后要同步更新路径**)。依赖为标准 `typescript` 包时 `node_modules/typescript` 直接是完整包,workspace 探测即命中、无需配 path。
|
|
65
|
+
|
|
66
|
+
配 `initializationOptions.tsserver.path` 时,因为 servers 按 id 整条覆盖,本地 `.pi/lsp.json` 要写完整条目,示例:
|
|
67
|
+
|
|
68
|
+
```json
|
|
69
|
+
{
|
|
70
|
+
"servers": {
|
|
71
|
+
"typescript": {
|
|
72
|
+
"include": ["**/*.{ts,tsx,js,jsx,mjs,cjs,mts,cts}"],
|
|
73
|
+
"rootMarkers": ["package-lock.json", "pnpm-lock.yaml", "yarn.lock"],
|
|
74
|
+
"bin": "typescript-language-server",
|
|
75
|
+
"args": ["--stdio"],
|
|
76
|
+
"cwd": "{root}",
|
|
77
|
+
"languageIdByExtension": {
|
|
78
|
+
".ts": "typescript",
|
|
79
|
+
".tsx": "typescriptreact",
|
|
80
|
+
".js": "javascript",
|
|
81
|
+
".jsx": "javascriptreact"
|
|
82
|
+
},
|
|
83
|
+
"initializationOptions": {
|
|
84
|
+
"tsserver": {
|
|
85
|
+
"path": "/path/to/node_modules/.pnpm/typescript@6.0.3/node_modules/typescript/lib/tsserver.js"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 服务器启动失败的行为(改了配置后怎么验证)
|
|
94
|
+
|
|
95
|
+
- 启动失败(binary 缺失 / initialize 拒绝)会**主动 notify 报错**(server id、root、原因、提示 `/lsp-reload <id>`),不需要等请求方触发
|
|
96
|
+
- 失败进入 60s 冷却:冷却内该服务器被跳过,**冷却过后下次触碰自动重试**——修复配置后不用重启 agent,等冷却过即可,或立即 `/lsp-reload <id>` 重启指定服务器(同时清除失败记录)
|
|
97
|
+
- 命令:`/lsp-reload <id>`(重启单个)、`/lsp-stop`(停全部并禁用)、`/lsp-start`(重新启用)
|
|
98
|
+
|
|
99
|
+
## 生效时机
|
|
100
|
+
|
|
101
|
+
- `servers` 配置在每次工具调用时惰性重读:**还没启动**的服务器,改 `bin`/`initializationOptions` 等下次触碰即生效;**已启动**的进程要用 `/lsp-reload <id>`(或重启 agent)才换新配置
|
|
102
|
+
- `session_start` 时若 enabled 服务器数为 0 则不创建 service,之后从无到有地启用需要重启 agent
|
|
103
|
+
|
|
104
|
+
## 常见排查
|
|
105
|
+
|
|
106
|
+
| 现象 | 原因与修法 |
|
|
107
|
+
| ----------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
108
|
+
| notify `failed to start …: binary not found` | `bin` 不在 PATH(测试场景二进制缺失时整组跳过) |
|
|
109
|
+
| `… provides no tsserver.js. No other valid TypeScript installation was found` | workspace 的 `node_modules/typescript` 是 alias stub:配 `initializationOptions.tsserver.path` 指 `.pnpm` 实体,或换标准 typescript 依赖 |
|
|
110
|
+
| 诊断一直为空且无任何报错 | 服务器没匹配到文件(`include`/扩展名)、在 broken 冷却中、或文件在调用 cwd 之外(LSP 只在工作目录内启用) |
|
|
111
|
+
| 读取配置直接抛错 | typebox 严格校验拒绝:多余字段 / 未知 `version` / 非法时长格式 |
|