@trim21/personal-pi-extensions 0.0.414 → 0.0.422

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/src/lib/lsp/lsp.ts +110 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.0.414",
3
+ "version": "0.0.422",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -87,7 +87,7 @@
87
87
  "node": ">=24"
88
88
  },
89
89
  "dependencies": {
90
- "@cortexkit/aft-bridge": "0.52.1",
90
+ "@cortexkit/aft-bridge": "0.53.0",
91
91
  "@mozilla/readability": "^0.6.0",
92
92
  "@vscode/tree-sitter-wasm": "^0.3.1",
93
93
  "jsonc-parser": "^3.3.1",
@@ -99,6 +99,6 @@
99
99
  "web-tree-sitter": "^0.26.12"
100
100
  },
101
101
  "optionalDependencies": {
102
- "@cortexkit/aft-linux-x64": "0.52.1"
102
+ "@cortexkit/aft-linux-x64": "0.53.0"
103
103
  }
104
104
  }
@@ -175,6 +175,8 @@ interface LspState {
175
175
  broken: Set<string>;
176
176
  spawning: Map<string, Promise<LspClient | undefined>>;
177
177
  closing: boolean;
178
+ /** /lsp-stop 置 true:所有工具调用不再 spawn 服务器,直到 start/reload。 */
179
+ disabled: boolean;
178
180
  /** root+serverID → 服务器状态,用于 footer status 显示。 */
179
181
  servers: Map<string, { serverID: string; root: string; state: "running" | "broken" }>;
180
182
  }
@@ -214,6 +216,17 @@ export interface LspService {
214
216
  options?: LspRequestOptions,
215
217
  ): Promise<{ text: string; errorCount: number; warningCount: number }>;
216
218
  shutdownAll(): Promise<void>;
219
+ /** 停止全部服务器并禁用 LSP:之后工具调用不再 spawn,直到 start/reload。 */
220
+ stop(): Promise<void>;
221
+ /** 解除禁用并清空 broken 缓存;服务器在下次工具调用时惰性启动。 */
222
+ start(): void;
223
+ /**
224
+ * 重启指定服务器:关闭其全部 client、清除对应 broken 记录并解除禁用;
225
+ * 其余服务器不受影响。配置在下次工具调用时重新读取。
226
+ */
227
+ reload(serverID: string): Promise<void>;
228
+ /** 已知服务器 id(running 或 broken 的去重集合),供命令补全与提示。 */
229
+ serverIDs(): string[];
217
230
  /** 注入 status 渲染回调;传入 undefined 表示不再渲染。 */
218
231
  attachStatus(render: StatusRenderer | undefined): void;
219
232
  /** 用当前服务器状态主动刷新一次 status(agent start/end 等生命周期边界)。 */
@@ -247,6 +260,7 @@ export function createLspService(
247
260
  broken: new Set(),
248
261
  spawning: new Map(),
249
262
  closing: false,
263
+ disabled: false,
250
264
  servers: new Map(),
251
265
  };
252
266
 
@@ -255,6 +269,10 @@ export function createLspService(
255
269
  /** 汇总当前所有 LSP server 状态并渲染到 footer status。 */
256
270
  function updateStatusText(): void {
257
271
  if (!renderStatus) return;
272
+ if (state.disabled) {
273
+ renderStatus("lsp: disabled");
274
+ return;
275
+ }
258
276
  if (state.servers.size === 0) {
259
277
  renderStatus(undefined);
260
278
  return;
@@ -275,7 +293,7 @@ export function createLspService(
275
293
  cwd: string,
276
294
  notify?: ExtensionUIContext["notify"],
277
295
  ): Promise<LspClient[]> {
278
- if (state.closing) return [];
296
+ if (state.closing || state.disabled) return [];
279
297
  if (!containsPath(file, cwd)) return [];
280
298
  const config = await loadLspConfig(cwd, globalConfigPath);
281
299
  const timeout = timeoutOptions(config);
@@ -326,7 +344,7 @@ export function createLspService(
326
344
  diagnosticsDocumentWaitTimeoutMs:
327
345
  adapter.diagnosticsWaitMs ?? timeout.diagnosticsDocumentWaitTimeoutMs,
328
346
  });
329
- if (state.closing) {
347
+ if (state.closing || state.disabled) {
330
348
  await client.shutdown();
331
349
  return;
332
350
  }
@@ -426,9 +444,8 @@ export function createLspService(
426
444
  return { text: report(normalized, issues), errorCount, warningCount };
427
445
  }
428
446
 
429
- /** 终止全部服务器进程(session_shutdown 时调用)。 */
430
- async function shutdownAll(): Promise<void> {
431
- if (state.closing) return;
447
+ /** 关闭全部 client 并清空缓存;closing 置 true 让 in-flight spawn 自行退出。 */
448
+ async function closeAll(): Promise<void> {
432
449
  state.closing = true;
433
450
  await Promise.all(state.clients.map((client) => client.shutdown())).catch(() => {
434
451
  // 个别进程退出失败不阻止清理流程
@@ -439,11 +456,55 @@ export function createLspService(
439
456
  updateStatusText();
440
457
  }
441
458
 
459
+ /** 终止全部服务器进程(session_shutdown 时调用,终态)。 */
460
+ async function shutdownAll(): Promise<void> {
461
+ await closeAll();
462
+ }
463
+
464
+ async function stop(): Promise<void> {
465
+ await closeAll();
466
+ state.closing = false;
467
+ state.disabled = true;
468
+ updateStatusText();
469
+ }
470
+
471
+ function start(): void {
472
+ state.closing = false;
473
+ state.disabled = false;
474
+ state.broken.clear();
475
+ updateStatusText();
476
+ }
477
+
478
+ async function reload(serverID: string): Promise<void> {
479
+ state.closing = true;
480
+ const targets = state.clients.filter((client) => client.serverID === serverID);
481
+ await Promise.all(targets.map((client) => client.shutdown())).catch(() => {
482
+ // 个别进程退出失败不阻止清理流程
483
+ });
484
+ state.clients = state.clients.filter((client) => client.serverID !== serverID);
485
+ for (const [key, server] of state.servers) {
486
+ if (server.serverID !== serverID) continue;
487
+ state.broken.delete(key);
488
+ state.servers.delete(key);
489
+ }
490
+ state.closing = false;
491
+ state.disabled = false;
492
+ updateStatusText();
493
+ }
494
+
495
+ function serverIDs(): string[] {
496
+ return [...new Set([...state.servers.values()].map((server) => server.serverID))];
497
+ }
498
+
442
499
  return {
443
500
  touchFile,
444
501
  diagnostics,
445
502
  lspDiagnosticsForFile,
446
503
  shutdownAll,
504
+ stop,
505
+ start,
506
+ reload,
507
+ serverIDs,
447
508
  attachStatus,
448
509
  refreshStatus: updateStatusText,
449
510
  };
@@ -476,5 +537,49 @@ export function registerLsp(pi: ExtensionAPI, options?: LspServiceOptions): LspS
476
537
  // spawn(首次工具调用),start/end 时保证 footer 反映当前实际状态。
477
538
  pi.on?.("agent_start", () => service.refreshStatus());
478
539
  pi.on?.("agent_end", () => service.refreshStatus());
540
+
541
+ pi.registerCommand?.("lsp-stop", {
542
+ description: "Stop all LSP servers and disable LSP until /lsp-start or /lsp-reload",
543
+ handler: async (_args, ctx) => {
544
+ await service.stop();
545
+ ctx.ui.notify?.("LSP disabled: all servers stopped", "info");
546
+ },
547
+ });
548
+
549
+ pi.registerCommand?.("lsp-start", {
550
+ description: "Re-enable LSP; servers start on the next tool call",
551
+ handler: (_args, ctx) => {
552
+ service.start();
553
+ ctx.ui.notify?.("LSP enabled: servers will start on the next tool call", "info");
554
+ return Promise.resolve();
555
+ },
556
+ });
557
+
558
+ pi.registerCommand?.("lsp-reload", {
559
+ description: "Restart a specific LSP server: /lsp-reload <server-id>",
560
+ getArgumentCompletions: (prefix) =>
561
+ service
562
+ .serverIDs()
563
+ .toSorted()
564
+ .filter((id) => id.startsWith(prefix))
565
+ .map((id) => ({ value: id, label: id })),
566
+ handler: async (args, ctx) => {
567
+ const serverID = args.trim();
568
+ const known = service.serverIDs().toSorted();
569
+ if (!serverID) {
570
+ ctx.ui.notify?.(
571
+ `usage: /lsp-reload <server-id>${known.length > 0 ? ` (known: ${known.join(", ")})` : ""}`,
572
+ "warning",
573
+ );
574
+ return;
575
+ }
576
+ await service.reload(serverID);
577
+ ctx.ui.notify?.(
578
+ `LSP server "${serverID}" reloaded: will restart on the next tool call`,
579
+ "info",
580
+ );
581
+ },
582
+ });
583
+
479
584
  return service;
480
585
  }