@trim21/personal-pi-extensions 0.0.315 → 0.0.316
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 +1 -1
- package/src/claude-code/files.ts +6 -83
- package/src/lib/lsp/lsp.ts +24 -3
package/package.json
CHANGED
package/src/claude-code/files.ts
CHANGED
|
@@ -15,7 +15,6 @@ import {
|
|
|
15
15
|
import { Type } from "typebox";
|
|
16
16
|
|
|
17
17
|
import { createLspService, initLsp, type LspService } from "../lib/lsp/lsp.js";
|
|
18
|
-
import { type ToolPendant } from "../lib/pendant.js";
|
|
19
18
|
import { guardWriteAccess } from "../lib/write-guard.js";
|
|
20
19
|
import {
|
|
21
20
|
type ClaudeCodeState,
|
|
@@ -46,43 +45,6 @@ function formatFileSize(bytes: number): string {
|
|
|
46
45
|
return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
|
|
47
46
|
}
|
|
48
47
|
|
|
49
|
-
/** Edit/Write 结果的可折叠面板:diff(```diff 代码块)+ LSP errors(有则显示)。 */
|
|
50
|
-
export function buildEditPendant(options: {
|
|
51
|
-
filePath: string;
|
|
52
|
-
diff: string;
|
|
53
|
-
diagnosticText: string;
|
|
54
|
-
}): ToolPendant {
|
|
55
|
-
const lines = [`## Diff — \`${options.filePath}\``, "", "```diff", options.diff, "```"];
|
|
56
|
-
if (options.diagnosticText !== "") {
|
|
57
|
-
lines.push("", "## LSP errors", "", "```text", options.diagnosticText, "```");
|
|
58
|
-
}
|
|
59
|
-
return { markdown: lines.join("\n"), expanded: true };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Read 结果的可折叠面板:路径、总行数、读取范围与文件大小。 */
|
|
63
|
-
export function buildReadPendant(options: {
|
|
64
|
-
filePath: string;
|
|
65
|
-
byteSize: number;
|
|
66
|
-
offset?: number;
|
|
67
|
-
limit?: number;
|
|
68
|
-
totalLines?: number;
|
|
69
|
-
type?: string;
|
|
70
|
-
}): ToolPendant {
|
|
71
|
-
const lines = ["## Read", "", `- **Path**: \`${options.filePath}\``];
|
|
72
|
-
if (options.type) lines.push(`- **Type**: ${options.type}`);
|
|
73
|
-
if (options.totalLines !== undefined) {
|
|
74
|
-
const start = options.offset ?? 1;
|
|
75
|
-
const end = Math.min(
|
|
76
|
-
options.limit === undefined ? options.totalLines : start + options.limit - 1,
|
|
77
|
-
options.totalLines,
|
|
78
|
-
);
|
|
79
|
-
lines.push(`- **Total lines**: ${options.totalLines}`);
|
|
80
|
-
if (start <= end) lines.push(`- **Range**: ${start}-${end}`);
|
|
81
|
-
}
|
|
82
|
-
lines.push(`- **Size**: ${formatFileSize(options.byteSize)}`);
|
|
83
|
-
return { markdown: lines.join("\n"), expanded: false };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
48
|
/** Tool guidance, kept in markdown so it reads like documentation. */
|
|
87
49
|
const READ_PROMPT = readFileSync(fileURLToPath(new URL("read.md", import.meta.url)), "utf8").trim();
|
|
88
50
|
const EDIT_PROMPT = readFileSync(fileURLToPath(new URL("edit.md", import.meta.url)), "utf8").trim();
|
|
@@ -107,8 +69,6 @@ export interface FileToolDetails {
|
|
|
107
69
|
* session 文件,resume 后由 session_start 重建 reads state。
|
|
108
70
|
*/
|
|
109
71
|
reads?: Record<string, FileSnapshot>;
|
|
110
|
-
/** 可折叠的 diff / LSP 结果面板(pi TUI 渲染)。 */
|
|
111
|
-
pendant?: ToolPendant;
|
|
112
72
|
}
|
|
113
73
|
|
|
114
74
|
function snapshotOf(content: Uint8Array | string, textEditable = true): FileSnapshot {
|
|
@@ -326,17 +286,7 @@ export function registerFileTools(
|
|
|
326
286
|
const snapshot = snapshotOf(image, false);
|
|
327
287
|
const key = await readStateKey(filePath);
|
|
328
288
|
state.reads.set(key, snapshot);
|
|
329
|
-
return {
|
|
330
|
-
content,
|
|
331
|
-
details: {
|
|
332
|
-
reads: { [key]: snapshot },
|
|
333
|
-
pendant: buildReadPendant({
|
|
334
|
-
filePath,
|
|
335
|
-
byteSize: image.length,
|
|
336
|
-
type: imageMime,
|
|
337
|
-
}),
|
|
338
|
-
},
|
|
339
|
-
};
|
|
289
|
+
return { content, details: { reads: { [key]: snapshot } } };
|
|
340
290
|
}
|
|
341
291
|
|
|
342
292
|
const buffer = await readFile(filePath);
|
|
@@ -366,16 +316,7 @@ export function registerFileTools(
|
|
|
366
316
|
});
|
|
367
317
|
return {
|
|
368
318
|
content: [{ type: "text", text: formatted.text }],
|
|
369
|
-
details: {
|
|
370
|
-
reads: { [key]: snapshot },
|
|
371
|
-
pendant: buildReadPendant({
|
|
372
|
-
filePath,
|
|
373
|
-
byteSize: buffer.length,
|
|
374
|
-
offset: params.offset ?? 1,
|
|
375
|
-
limit: params.limit,
|
|
376
|
-
totalLines: formatted.totalLines,
|
|
377
|
-
}),
|
|
378
|
-
},
|
|
319
|
+
details: { reads: { [key]: snapshot } },
|
|
379
320
|
};
|
|
380
321
|
},
|
|
381
322
|
});
|
|
@@ -544,17 +485,7 @@ export function registerFileTools(
|
|
|
544
485
|
const text = diagnosticText
|
|
545
486
|
? `${message}\n\nLSP errors detected in this file, please fix:\n${diagnosticText}`
|
|
546
487
|
: message;
|
|
547
|
-
return {
|
|
548
|
-
content: [{ type: "text" as const, text }],
|
|
549
|
-
details: {
|
|
550
|
-
...details,
|
|
551
|
-
pendant: buildEditPendant({
|
|
552
|
-
filePath,
|
|
553
|
-
diff: details.diff ?? "",
|
|
554
|
-
diagnosticText,
|
|
555
|
-
}),
|
|
556
|
-
},
|
|
557
|
-
};
|
|
488
|
+
return { content: [{ type: "text" as const, text }], details };
|
|
558
489
|
},
|
|
559
490
|
});
|
|
560
491
|
|
|
@@ -632,17 +563,7 @@ export function registerFileTools(
|
|
|
632
563
|
const text = diagnosticText
|
|
633
564
|
? `${message}\n\nLSP errors detected in this file, please fix:\n${diagnosticText}`
|
|
634
565
|
: message;
|
|
635
|
-
return {
|
|
636
|
-
content: [{ type: "text" as const, text }],
|
|
637
|
-
details: {
|
|
638
|
-
...details,
|
|
639
|
-
pendant: buildEditPendant({
|
|
640
|
-
filePath,
|
|
641
|
-
diff: details.diff ?? "",
|
|
642
|
-
diagnosticText,
|
|
643
|
-
}),
|
|
644
|
-
},
|
|
645
|
-
};
|
|
566
|
+
return { content: [{ type: "text" as const, text }], details };
|
|
646
567
|
},
|
|
647
568
|
});
|
|
648
569
|
}
|
|
@@ -686,6 +607,7 @@ export default function claudeCodeFileTools(pi: ExtensionAPI): void {
|
|
|
686
607
|
// 若文件在此期间被外部修改,Edit/Write 时的指纹对比仍会要求重新 Read,
|
|
687
608
|
// 防呆语义不因重建而弱化。
|
|
688
609
|
pi.on("session_start", (_event, ctx) => {
|
|
610
|
+
service.setUi(ctx.ui);
|
|
689
611
|
restoreFileReads(state, ctx.sessionManager);
|
|
690
612
|
});
|
|
691
613
|
|
|
@@ -693,6 +615,7 @@ export default function claudeCodeFileTools(pi: ExtensionAPI): void {
|
|
|
693
615
|
// session_start,扩展实例也不重建。这里同样重放当前分支,丢弃被抛弃分支
|
|
694
616
|
// 的记账,避免 state 与当前分支脱节。
|
|
695
617
|
pi.on("session_tree", (_event, ctx) => {
|
|
618
|
+
service.setUi(ctx.ui);
|
|
696
619
|
restoreFileReads(state, ctx.sessionManager);
|
|
697
620
|
});
|
|
698
621
|
|
package/src/lib/lsp/lsp.ts
CHANGED
|
@@ -16,7 +16,7 @@ import { readFile } from "node:fs/promises";
|
|
|
16
16
|
import { homedir } from "node:os";
|
|
17
17
|
import { extname, join, normalize, sep } from "node:path";
|
|
18
18
|
|
|
19
|
-
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
|
+
import type { ExtensionAPI, ExtensionUIContext } from "@earendil-works/pi-coding-agent";
|
|
20
20
|
import { type Static, Type } from "typebox";
|
|
21
21
|
import { Value } from "typebox/value";
|
|
22
22
|
|
|
@@ -137,6 +137,8 @@ export interface LspService {
|
|
|
137
137
|
diagnostics(): Promise<Record<string, Diagnostic[]>>;
|
|
138
138
|
lspDiagnosticsForFile(file: string, cwd: string): Promise<string>;
|
|
139
139
|
shutdownAll(): Promise<void>;
|
|
140
|
+
/** 绑定当前会话的 UI 上下文,LSP 服务器启动失败时用它发错误通知(传 undefined 解绑)。 */
|
|
141
|
+
setUi(ui?: Pick<ExtensionUIContext, "notify">): void;
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
/** 文件必须在工作目录内才启用 LSP(对齐 opencode 的 containsPath)。 */
|
|
@@ -161,6 +163,7 @@ export function createLspService(
|
|
|
161
163
|
broken: new Set(),
|
|
162
164
|
spawning: new Map(),
|
|
163
165
|
};
|
|
166
|
+
let ui: Pick<ExtensionUIContext, "notify"> | undefined;
|
|
164
167
|
|
|
165
168
|
async function getClients(file: string, cwd: string): Promise<LspClient[]> {
|
|
166
169
|
if (!containsPath(file, cwd)) return [];
|
|
@@ -195,6 +198,10 @@ export function createLspService(
|
|
|
195
198
|
const handle = await adapter.spawn(root, cwd);
|
|
196
199
|
if (!handle) {
|
|
197
200
|
state.broken.add(key);
|
|
201
|
+
ui?.notify(
|
|
202
|
+
`LSP server "${adapter.id}" is not available for ${root} (binary not found)`,
|
|
203
|
+
"error",
|
|
204
|
+
);
|
|
198
205
|
return;
|
|
199
206
|
}
|
|
200
207
|
const client = await create({
|
|
@@ -214,8 +221,14 @@ export function createLspService(
|
|
|
214
221
|
}
|
|
215
222
|
state.clients.push(client);
|
|
216
223
|
return client;
|
|
217
|
-
} catch {
|
|
224
|
+
} catch (error) {
|
|
218
225
|
state.broken.add(key);
|
|
226
|
+
ui?.notify(
|
|
227
|
+
`LSP server "${adapter.id}" failed to start for ${root}: ${
|
|
228
|
+
error instanceof Error ? error.message : String(error)
|
|
229
|
+
}`,
|
|
230
|
+
"error",
|
|
231
|
+
);
|
|
219
232
|
return;
|
|
220
233
|
}
|
|
221
234
|
})();
|
|
@@ -284,7 +297,15 @@ export function createLspService(
|
|
|
284
297
|
state.broken.clear();
|
|
285
298
|
}
|
|
286
299
|
|
|
287
|
-
return {
|
|
300
|
+
return {
|
|
301
|
+
touchFile,
|
|
302
|
+
diagnostics,
|
|
303
|
+
lspDiagnosticsForFile,
|
|
304
|
+
shutdownAll,
|
|
305
|
+
setUi(nextUi) {
|
|
306
|
+
ui = nextUi;
|
|
307
|
+
},
|
|
308
|
+
};
|
|
288
309
|
}
|
|
289
310
|
|
|
290
311
|
/** 注册进程级生命周期:session_shutdown 时清理全部服务器进程。 */
|