@trim21/personal-pi-extensions 0.0.313 → 0.0.314

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trim21/personal-pi-extensions",
3
- "version": "0.0.313",
3
+ "version": "0.0.314",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
package/src/aft/tools.ts CHANGED
@@ -120,15 +120,6 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
120
120
  content: [{ type: "text", text }],
121
121
  details: {
122
122
  truncated,
123
- pendant: {
124
- markdown: buildPendantMarkdown({
125
- title: "aft_outline",
126
- input: params,
127
- output: text,
128
- truncated,
129
- }),
130
- expanded: true,
131
- } satisfies ToolPendant,
132
123
  },
133
124
  };
134
125
  },
@@ -15,6 +15,7 @@ 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";
18
19
  import { guardWriteAccess } from "../lib/write-guard.js";
19
20
  import {
20
21
  type ClaudeCodeState,
@@ -45,6 +46,43 @@ function formatFileSize(bytes: number): string {
45
46
  return `${(bytes / 1024 / 1024 / 1024).toFixed(1)} GB`;
46
47
  }
47
48
 
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
+
48
86
  /** Tool guidance, kept in markdown so it reads like documentation. */
49
87
  const READ_PROMPT = readFileSync(fileURLToPath(new URL("read.md", import.meta.url)), "utf8").trim();
50
88
  const EDIT_PROMPT = readFileSync(fileURLToPath(new URL("edit.md", import.meta.url)), "utf8").trim();
@@ -69,6 +107,8 @@ export interface FileToolDetails {
69
107
  * session 文件,resume 后由 session_start 重建 reads state。
70
108
  */
71
109
  reads?: Record<string, FileSnapshot>;
110
+ /** 可折叠的 diff / LSP 结果面板(pi TUI 渲染)。 */
111
+ pendant?: ToolPendant;
72
112
  }
73
113
 
74
114
  function snapshotOf(content: Uint8Array | string, textEditable = true): FileSnapshot {
@@ -286,7 +326,17 @@ export function registerFileTools(
286
326
  const snapshot = snapshotOf(image, false);
287
327
  const key = await readStateKey(filePath);
288
328
  state.reads.set(key, snapshot);
289
- return { content, details: { reads: { [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
+ };
290
340
  }
291
341
 
292
342
  const buffer = await readFile(filePath);
@@ -316,7 +366,16 @@ export function registerFileTools(
316
366
  });
317
367
  return {
318
368
  content: [{ type: "text", text: formatted.text }],
319
- details: { reads: { [key]: snapshot } },
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
+ },
320
379
  };
321
380
  },
322
381
  });
@@ -388,9 +447,15 @@ export function registerFileTools(
388
447
  const snapshot = snapshotOf(newString);
389
448
  const key = await readStateKey(filePath);
390
449
  state.reads.set(key, snapshot);
450
+ const diff = generateDiffString("", newString);
391
451
  return [
392
452
  `The file ${filePath} has been updated successfully.`,
393
- { reads: { [key]: snapshot } },
453
+ {
454
+ diff: diff.diff,
455
+ patch: generateUnifiedPatch(filePath, "", newString),
456
+ firstChangedLine: diff.firstChangedLine,
457
+ reads: { [key]: snapshot },
458
+ },
394
459
  ];
395
460
  }
396
461
  const replaceAll = params.replace_all ?? false;
@@ -477,7 +542,17 @@ export function registerFileTools(
477
542
  const text = diagnosticText
478
543
  ? `${message}\n\nLSP errors detected in this file, please fix:\n${diagnosticText}`
479
544
  : message;
480
- return { content: [{ type: "text" as const, text }], details };
545
+ return {
546
+ content: [{ type: "text" as const, text }],
547
+ details: {
548
+ ...details,
549
+ pendant: buildEditPendant({
550
+ filePath,
551
+ diff: details.diff ?? "",
552
+ diagnosticText,
553
+ }),
554
+ },
555
+ };
481
556
  },
482
557
  });
483
558
 
@@ -556,7 +631,17 @@ export function registerFileTools(
556
631
  const text = diagnosticText
557
632
  ? `${message}\n\nLSP errors detected in this file, please fix:\n${diagnosticText}`
558
633
  : message;
559
- return { content: [{ type: "text" as const, text }], details };
634
+ return {
635
+ content: [{ type: "text" as const, text }],
636
+ details: {
637
+ ...details,
638
+ pendant: buildEditPendant({
639
+ filePath,
640
+ diff: details.diff ?? "",
641
+ diagnosticText,
642
+ }),
643
+ },
644
+ };
560
645
  },
561
646
  });
562
647
  }