pi-extensions-tool-display 1.0.0 → 1.1.0

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/SKILL.md ADDED
@@ -0,0 +1,28 @@
1
+ ---
2
+ name: configure-pi-extensions-tool-display
3
+ description: "配置与排查 Pi 工具展示宿主、内建/自定义工具 override、输出模式和 diff 视图。Use when changing pi-extensions-tool-display rendering."
4
+ ---
5
+
6
+ # 配置 pi-extensions-tool-display
7
+
8
+ ## 诊断
9
+
10
+ 读取实际 Pi agent 目录下的 `extensions/pi-tool-display/config.json`。注意目录名是 `pi-tool-display`,不是 npm 包名。以包内 `config.example.json` 和当前源码支持的字段为准。
11
+
12
+ 确认没有同时加载另一份工具展示宿主;`pi-distill` 与 `pi-tool-supervisor` 已会加载这个共享宿主,通常无需重复安装。
13
+
14
+ ## 修改
15
+
16
+ 优先运行 `/config:tool-display`;`/tool-display`、`/pi-tool-display` 是兼容别名。按目标最小修改:
17
+
18
+ - `registerToolOverrides` 控制 `read/grep/find/ls/bash/edit/write` 的展示所有权;
19
+ - `customToolOverrides.<tool>` 使用 `enabled`、`kind`、`outputMode`;
20
+ - `readOutputMode`、`searchOutputMode`、`mcpOutputMode`、`bashOutputMode` 控制输出展示;
21
+ - `diffViewMode`、`diffIndicatorMode`、折叠行数和换行字段只影响 diff UI;
22
+ - `enabled: false` 关闭宿主展示。
23
+
24
+ 不要把展示开关解释为禁用工具,也不要让多个宿主同时注册同一内建 override。
25
+
26
+ ## 验证
27
+
28
+ 分别调用一个受影响的内建或自定义工具,观察折叠、预览、diff 或输出模式。若消费者审计卡片未出现,先确认宿主只加载一次,再确认消费者 middleware 已注册;不能用配置文件可解析代替真实 UI 证据。
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-extensions-tool-display",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Pi tool display host and shared result-rendering protocol for extensions",
5
5
  "type": "module",
6
6
  "main": "./index.ts",
@@ -15,6 +15,7 @@
15
15
  "config.example.json",
16
16
  "README.md",
17
17
  "README.zh-CN.md",
18
+ "SKILL.md",
18
19
  "tsconfig.json"
19
20
  ],
20
21
  "scripts": {
@@ -53,6 +54,9 @@
53
54
  "pi": {
54
55
  "extensions": [
55
56
  "./index.ts"
57
+ ],
58
+ "skills": [
59
+ "./SKILL.md"
56
60
  ]
57
61
  },
58
62
  "devDependencies": {
@@ -104,7 +104,7 @@ export function buildInspectorSettings(
104
104
  inspectorTitle: "Global Tool Display Switch",
105
105
  inspectorSummary: [
106
106
  "Enables or disables all pi-tool-display rendering and tool ownership behavior.",
107
- "The /tool-display command remains available while disabled so the extension can be enabled again without editing Pi package settings.",
107
+ "The /config:tool-display command remains available while disabled so the extension can be enabled again without editing Pi package settings.",
108
108
  ],
109
109
  inspectorOptions: [
110
110
  "on — enable tool overrides and enhanced rendering",
@@ -500,7 +500,7 @@ export function handleToolDisplayArgs(args: string, ctx: ExtensionCommandContext
500
500
  const candidate = normalized.slice("preset ".length).trim();
501
501
  const preset = parseToolDisplayPreset(candidate);
502
502
  if (!preset) {
503
- ctx.ui.notify(`Unknown preset. Use: /tool-display preset ${PRESET_COMMAND_HINT}`, "warning");
503
+ ctx.ui.notify(`Unknown preset. Use: /config:tool-display preset ${PRESET_COMMAND_HINT}`, "warning");
504
504
  return true;
505
505
  }
506
506
 
@@ -509,7 +509,7 @@ export function handleToolDisplayArgs(args: string, ctx: ExtensionCommandContext
509
509
  return true;
510
510
  }
511
511
 
512
- ctx.ui.notify(`Usage: /tool-display [show|reset|preset ${PRESET_COMMAND_HINT}]`, "warning");
512
+ ctx.ui.notify(`Usage: /config:tool-display [show|reset|preset ${PRESET_COMMAND_HINT}]`, "warning");
513
513
  return true;
514
514
  }
515
515
 
@@ -523,7 +523,7 @@ export async function runToolDisplayCommandHandler(
523
523
  }
524
524
 
525
525
  if (!ctx.hasUI) {
526
- ctx.ui.notify("/tool-display requires interactive TUI mode.", "warning");
526
+ ctx.ui.notify("/config:tool-display requires interactive TUI mode.", "warning");
527
527
  return;
528
528
  }
529
529
 
@@ -531,10 +531,13 @@ export async function runToolDisplayCommandHandler(
531
531
  }
532
532
 
533
533
  export function registerToolDisplayCommand(pi: ExtensionAPI, controller: ToolDisplayConfigController): void {
534
- pi.registerCommand("tool-display", {
534
+ const command = {
535
535
  description: "Configure tool output rendering (OpenCode-style)",
536
536
  handler: async (args, ctx) => {
537
537
  await runToolDisplayCommandHandler(args, ctx, controller);
538
538
  },
539
- });
539
+ };
540
+ for (const name of ["config:tool-display", "tool-display", "pi-tool-display"] as const) {
541
+ pi.registerCommand(name, command);
542
+ }
540
543
  }
package/src/index.ts CHANGED
@@ -44,9 +44,9 @@ export function toolDisplayReloadRequired(
44
44
 
45
45
  const initializedHosts = new WeakSet<ExtensionAPI>();
46
46
 
47
- function hasRegisteredToolDisplayCommand(pi: ExtensionAPI): boolean {
47
+ function hasRegisteredToolDisplayCommand(pi: ExtensionAPI, name: string): boolean {
48
48
  try {
49
- return pi.getCommands().some((command) => command.name === "tool-display");
49
+ return pi.getCommands().some((command) => command.name === name);
50
50
  } catch {
51
51
  return false;
52
52
  }
@@ -119,15 +119,16 @@ export function ensureToolDisplayHost(
119
119
 
120
120
  // Each Pi extension has its own registration map, while getCommands() sees
121
121
  // the shared runtime. Check the latter so feature extensions do not expose
122
- // duplicate /tool-display entries when they initialize this host separately.
123
- if (!hasRegisteredToolDisplayCommand(pi)) {
124
- pi.registerCommand("tool-display", {
125
- description: "Configure tool output rendering (OpenCode-style)",
126
- handler: async (args, ctx) => {
127
- const { runToolDisplayCommandHandler } = await import("./config-modal.js");
128
- await runToolDisplayCommandHandler(args, ctx, { getConfig, setConfig, getCapabilities });
129
- },
130
- });
122
+ // duplicate management commands when they initialize this host separately.
123
+ const command = {
124
+ description: "Configure tool output rendering (OpenCode-style)",
125
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
126
+ const { runToolDisplayCommandHandler } = await import("./config-modal.js");
127
+ await runToolDisplayCommandHandler(args, ctx, { getConfig, setConfig, getCapabilities });
128
+ },
129
+ };
130
+ for (const name of ["config:tool-display", "tool-display", "pi-tool-display"] as const) {
131
+ if (!hasRegisteredToolDisplayCommand(pi, name)) pi.registerCommand(name, command);
131
132
  }
132
133
 
133
134
  pi.on("session_start", async (_event, ctx) => {
@@ -66,10 +66,14 @@ export function registerResultRenderMiddleware(
66
66
  const api = getApi();
67
67
  if (typeof api?.registerResultRenderMiddleware === "function") {
68
68
  api.registerResultRenderMiddleware(registration);
69
- } else {
70
- queueRegistration(registration);
71
69
  }
72
70
 
71
+ // Keep the registration in the shared protocol queue even when a host is
72
+ // already available. The host can be replaced during /reload or when a new
73
+ // session creates a new ExtensionAPI instance; the new host must be able to
74
+ // rehydrate registrations that were attached to the previous API object.
75
+ queueRegistration(registration);
76
+
73
77
  return () => {
74
78
  getApi()?.unregisterResultRenderMiddleware?.(registration.id);
75
79
  removeQueuedRegistration(registration.id);
@@ -1525,8 +1525,11 @@ function drainPendingResultRenderMiddlewares(api: ToolDisplayApi): void {
1525
1525
  const pending = globalWithApi[TOOL_DISPLAY_PENDING_RESULT_MIDDLEWARES_KEY];
1526
1526
  if (!Array.isArray(pending) || pending.length === 0) return;
1527
1527
 
1528
- const entries = pending.splice(0);
1529
- for (const entry of entries) {
1528
+ // This queue is the cross-host registry for external middleware, not a
1529
+ // one-shot bootstrap queue. Keep it populated so a replacement host during
1530
+ // /reload or a new session can rehydrate middleware registered on the old
1531
+ // API instance.
1532
+ for (const entry of pending) {
1530
1533
  if (!entry?.id || !entry.toolName || typeof entry.middleware !== "function") continue;
1531
1534
  api.registerResultRenderMiddleware(entry);
1532
1535
  }
@@ -53,19 +53,25 @@ export function registerToolResultRenderMiddleware(toolName, middleware, options
53
53
  }
54
54
 
55
55
  const id = options.id || `external-result-middleware-${++nextResultMiddlewareId}`;
56
+ const registration = { id, toolName, middleware };
56
57
  const api = getToolDisplayApi();
57
58
  if (typeof api?.registerResultRenderMiddleware === "function") {
58
- return api.registerResultRenderMiddleware({ id, toolName, middleware });
59
+ const registeredId = api.registerResultRenderMiddleware(registration);
60
+ queueResultRenderMiddleware(registration);
61
+ return registeredId;
59
62
  }
60
63
 
64
+ queueResultRenderMiddleware(registration);
65
+ return id;
66
+ }
67
+
68
+ function queueResultRenderMiddleware(registration) {
61
69
  const existing = globalThis[TOOL_DISPLAY_PENDING_RESULT_MIDDLEWARES_KEY];
62
70
  const queue = Array.isArray(existing) ? existing : [];
63
- const registration = { id, toolName, middleware };
64
- const index = queue.findIndex((entry) => entry?.id === id);
71
+ const index = queue.findIndex((entry) => entry?.id === registration.id);
65
72
  if (index >= 0) queue[index] = registration;
66
73
  else queue.push(registration);
67
74
  globalThis[TOOL_DISPLAY_PENDING_RESULT_MIDDLEWARES_KEY] = queue;
68
- return id;
69
75
  }
70
76
 
71
77
  export function unregisterToolResultRenderMiddleware(id) {