@trim21/personal-pi-extensions 0.0.302 → 0.0.304

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.302",
3
+ "version": "0.0.304",
4
4
  "type": "module",
5
5
  "description": "Custom pi coding-agent extensions: bwrap sandbox, workspace guard, opencode edit, and more",
6
6
  "keywords": [
@@ -16,9 +16,10 @@ import { Type } from "typebox";
16
16
  import { Value } from "typebox/value";
17
17
 
18
18
  import { requireAbsolutePath } from "../claude-code/common.js";
19
+ import { type ToolPendant } from "../lib/pendant.js";
19
20
  import { guardWriteAccess } from "../lib/write-guard.js";
20
21
  import { callAftTool } from "./bridge.js";
21
- import { type AftToolContext, bridgeFor } from "./tools.js";
22
+ import { type AftToolContext, bridgeFor, buildPendantMarkdown } from "./tools.js";
22
23
 
23
24
  // ── 纯函数(可测) ──────────────────────────────────────────────────────────
24
25
 
@@ -256,9 +257,20 @@ export function registerAstEditTool(pi: ExtensionAPI, ctx: AftToolContext): void
256
257
 
257
258
  // 第三步:AFT 原子写盘(备份 + 格式化 + undo),结果由 Rust 格式化返回。
258
259
  const { text } = await callAftTool(bridgeFor(ctx), "edit", rawArgs, extCtx);
260
+ const files = previewFiles.map((f) => f.file || filePath);
259
261
  return {
260
262
  content: [{ type: "text", text }],
261
- details: { input: params, files: previewFiles.map((f) => f.file || filePath) },
263
+ details: {
264
+ files,
265
+ pendant: {
266
+ markdown: buildPendantMarkdown({
267
+ title: "ast_edit",
268
+ input: params,
269
+ output: text,
270
+ }),
271
+ expanded: true,
272
+ } satisfies ToolPendant,
273
+ },
262
274
  };
263
275
  },
264
276
  });
package/src/aft/tools.ts CHANGED
@@ -20,6 +20,7 @@ import {
20
20
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
21
21
  import { Type } from "typebox";
22
22
 
23
+ import { type ToolPendant } from "../lib/pendant.js";
23
24
  import { callAftTool, SEMANTIC_INDEX_WAIT_TIMEOUT_MS } from "./bridge.js";
24
25
 
25
26
  /** 解析 `~` 前缀与相对路径(相对 session cwd)。URL 与绝对路径原样返回。 */
@@ -40,16 +41,42 @@ export function bridgeFor(ctx: AftToolContext): AftProjectTransport {
40
41
  return ctx.pool.getBridge(ctx.cwd);
41
42
  }
42
43
 
44
+ /** 人类视角的调用记录:input params + 与 LLM 相同的输出结果。 */
45
+ export function buildPendantMarkdown(params: {
46
+ title: string;
47
+ input: unknown;
48
+ output: string;
49
+ truncated?: boolean;
50
+ }): string {
51
+ const lines = [
52
+ `## ${params.title}`,
53
+ "",
54
+ "**Input**",
55
+ "",
56
+ "```json",
57
+ JSON.stringify(params.input, null, 2),
58
+ "```",
59
+ "",
60
+ "**Output**",
61
+ "",
62
+ params.output,
63
+ ];
64
+ if (params.truncated) {
65
+ lines.push("", "_输出已截断_");
66
+ }
67
+ return lines.join("\n").trim();
68
+ }
69
+
43
70
  const OutlineParams = Type.Object(
44
71
  {
45
- target: Type.Union([Type.String(), Type.Array(Type.String())], {
72
+ target: Type.String({
46
73
  description:
47
- "要 outline 的对象:文件路径、目录路径、URL(http:// 或 https://),或文件路径数组。模式自动识别:URL 按前缀、目录按 stat、数组按多文件。目录递归上限 200 个文件。",
74
+ "要 outline 的对象:文件路径、目录路径或 URL(http:// 或 https://)。只接受单个 target;目录递归上限 200 个文件。",
48
75
  }),
49
76
  files: Type.Optional(
50
77
  Type.Boolean({
51
78
  description:
52
- "目录模式:为 true 时 target 必须是目录(或目录数组),返回带语言/符号数/字节大小的扁平文件树,而非符号大纲。",
79
+ "目录模式:为 true 时 target 必须是目录,返回带语言/符号数/字节大小的扁平文件树,而非符号大纲。",
53
80
  }),
54
81
  ),
55
82
  includeTests: Type.Optional(
@@ -67,24 +94,20 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
67
94
  "输出代码文件、目录、URL 的结构化大纲:函数/类/类型等符号及其行号范围;Markdown/HTML 返回标题层级。",
68
95
  "用它在读取具体内容之前先了解文件结构(比整文件 read 省 token)。",
69
96
  "深入了解某个符号用 aft_zoom;看跨文件调用关系用 aft_callgraph。",
70
- "target 支持:文件路径(带签名的符号大纲)、目录路径(递归最多 200 文件)、URL、文件路径数组。",
97
+ "target 支持:文件路径(带签名的符号大纲)、目录路径(递归最多 200 文件)、URL。只接受单个 target。",
71
98
  "files: true 且 target 为目录时返回扁平文件树(语言、顶层符号数、字节大小)。",
72
99
  ].join("\n"),
73
100
  promptSnippet: "Output structural outline of a file/directory/URL",
74
101
  parameters: OutlineParams,
75
102
  async execute(_id, params, _signal, _onUpdate, extCtx) {
76
103
  const target = coerceTargetParam(params.target);
77
- if (
78
- (typeof target !== "string" || target.length === 0) &&
79
- (!Array.isArray(target) || target.length === 0)
80
- ) {
81
- throw new Error("'target' must be a non-empty string or array of strings");
104
+ if (typeof target !== "string" || target.length === 0) {
105
+ throw new Error("'target' must be a single path or URL (array targets are not supported)");
82
106
  }
83
107
  const filesMode = coerceBoolean(params.files);
84
108
  const rawArgs: Record<string, unknown> = {
85
- target: Array.isArray(target)
86
- ? target.map((t) => resolvePathArg(extCtx.cwd, t))
87
- : filesMode || target.startsWith("http://") || target.startsWith("https://")
109
+ target:
110
+ filesMode || target.startsWith("http://") || target.startsWith("https://")
88
111
  ? target
89
112
  : resolvePathArg(extCtx.cwd, target),
90
113
  };
@@ -92,9 +115,21 @@ export function registerOutlineTool(pi: ExtensionAPI, ctx: AftToolContext): void
92
115
  if (params.includeTests !== undefined) rawArgs.includeTests = params.includeTests;
93
116
 
94
117
  const { text, response } = await callAftTool(bridgeFor(ctx), "outline", rawArgs, extCtx);
118
+ const truncated = response.truncated === true;
95
119
  return {
96
120
  content: [{ type: "text", text }],
97
- details: { input: params, truncated: response.truncated === true },
121
+ details: {
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
+ },
98
133
  };
99
134
  },
100
135
  });
@@ -197,9 +232,21 @@ export function registerZoomTool(pi: ExtensionAPI, ctx: AftToolContext): void {
197
232
  if (coerceBoolean(params.callgraph)) rawArgs.callgraph = true;
198
233
 
199
234
  const { text, response } = await callAftTool(bridgeFor(ctx), "zoom", rawArgs, extCtx);
235
+ const truncated = response.truncated === true;
200
236
  return {
201
237
  content: [{ type: "text", text }],
202
- details: { input: params, truncated: response.truncated === true },
238
+ details: {
239
+ truncated,
240
+ pendant: {
241
+ markdown: buildPendantMarkdown({
242
+ title: "aft_zoom",
243
+ input: params,
244
+ output: text,
245
+ truncated,
246
+ }),
247
+ expanded: true,
248
+ } satisfies ToolPendant,
249
+ },
203
250
  };
204
251
  },
205
252
  });
@@ -295,9 +342,21 @@ export function registerCallgraphTool(pi: ExtensionAPI, ctx: AftToolContext): vo
295
342
  formatCallgraphSections(params.op, response, PLAIN_CALLGRAPH_THEME, {
296
343
  includeUnresolved: coerceBoolean(params.includeUnresolved),
297
344
  }).join("\n");
345
+ const truncated = response.truncated === true;
298
346
  return {
299
347
  content: [{ type: "text", text: out }],
300
- details: { input: params, truncated: response.truncated === true },
348
+ details: {
349
+ truncated,
350
+ pendant: {
351
+ markdown: buildPendantMarkdown({
352
+ title: "aft_callgraph",
353
+ input: params,
354
+ output: out,
355
+ truncated,
356
+ }),
357
+ expanded: true,
358
+ } satisfies ToolPendant,
359
+ },
301
360
  };
302
361
  },
303
362
  });
@@ -358,9 +417,21 @@ export function registerSearchTool(pi: ExtensionAPI, ctx: AftToolContext): void
358
417
  transportTimeoutMs: SEMANTIC_INDEX_WAIT_TIMEOUT_MS + 60_000,
359
418
  keepBridgeOnTimeout: true,
360
419
  });
420
+ const truncated = response.truncated === true;
361
421
  return {
362
422
  content: [{ type: "text", text }],
363
- details: { input: params, truncated: response.truncated === true },
423
+ details: {
424
+ truncated,
425
+ pendant: {
426
+ markdown: buildPendantMarkdown({
427
+ title: "aft_search",
428
+ input: params,
429
+ output: text,
430
+ truncated,
431
+ }),
432
+ expanded: true,
433
+ } satisfies ToolPendant,
434
+ },
364
435
  };
365
436
  },
366
437
  });