pi-extensions-tool-display 1.0.1 → 1.1.1

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/README.md CHANGED
@@ -14,6 +14,8 @@ It is also a standalone Pi extension. Install or include this package in Pi's pa
14
14
 
15
15
  This package owns the display protocol and generic component composition only. Each feature extension continues to own its domain-specific audit extraction, copy, status, and layout.
16
16
 
17
+ Built-in overrides only replace tools that Pi has already activated; `registerToolOverrides` does not enable inactive built-in tools.
18
+
17
19
  ## Upstream attribution
18
20
 
19
21
  The display integration is designed to work with the MIT-licensed [`MasuRii/pi-tool-display`](https://github.com/MasuRii/pi-tool-display), the original full-featured Pi tool-display project. We thank MasuRii for that work.
package/README.zh-CN.md CHANGED
@@ -14,6 +14,8 @@
14
14
 
15
15
  这个包负责实际工具展示、展示协议和通用组件组合。具体业务的审计数据提取、文案、状态和布局仍由调用方维护,避免把不同扩展耦合在一起。
16
16
 
17
+ 内建工具覆盖只替换 Pi 当前已经激活的工具;`registerToolOverrides` 不会启用原本未激活的内建工具。
18
+
17
19
  ## 上游引用
18
20
 
19
21
  本包的展示集成设计用于兼容 MIT 许可的 [`MasuRii/pi-tool-display`](https://github.com/MasuRii/pi-tool-display),这是 Pi 生态中原始的完整工具展示项目。感谢 MasuRii 的工作。
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.1",
3
+ "version": "1.1.1",
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
@@ -13,6 +13,7 @@ import {
13
13
  type ToolDisplayCapabilities,
14
14
  } from "./capabilities.js";
15
15
  import { registerToolDisplayOverrides } from "./tool-overrides.js";
16
+ import { logToolDisplayDebug } from "./debug-logger.js";
16
17
  import { disposeAll, resetDisposed } from "./disposable.js";
17
18
  import { registerThinkingLabeling } from "./thinking-label.js";
18
19
  import registerNativeUserMessageBox from "./user-message-box-native.js";
@@ -24,6 +25,12 @@ import {
24
25
 
25
26
  export * from "./result-render-middleware.js";
26
27
 
28
+ const TOOL_DISPLAY_RELOAD_ACTIVE_TOOLS_KEY = Symbol.for("pi-tool-display.reloadActiveTools.v1");
29
+ const BUILT_IN_TOOL_OVERRIDE_NAME_SET = new Set<string>(BUILT_IN_TOOL_OVERRIDE_NAMES);
30
+ type GlobalWithToolDisplayReloadState = typeof globalThis & {
31
+ [TOOL_DISPLAY_RELOAD_ACTIVE_TOOLS_KEY]?: string[];
32
+ };
33
+
27
34
  function ownershipChanged(
28
35
  previous: ToolDisplayConfig,
29
36
  next: ToolDisplayConfig,
@@ -44,9 +51,9 @@ export function toolDisplayReloadRequired(
44
51
 
45
52
  const initializedHosts = new WeakSet<ExtensionAPI>();
46
53
 
47
- function hasRegisteredToolDisplayCommand(pi: ExtensionAPI): boolean {
54
+ function hasRegisteredToolDisplayCommand(pi: ExtensionAPI, name: string): boolean {
48
55
  try {
49
- return pi.getCommands().some((command) => command.name === "tool-display");
56
+ return pi.getCommands().some((command) => command.name === name);
50
57
  } catch {
51
58
  return false;
52
59
  }
@@ -65,10 +72,21 @@ export function ensureToolDisplayHost(
65
72
  if (initializedHosts.has(pi)) return;
66
73
  initializedHosts.add(pi);
67
74
 
75
+ const globalReloadState = globalThis as GlobalWithToolDisplayReloadState;
76
+ const activeBuiltInToolNamesBeforeReload = globalReloadState[TOOL_DISPLAY_RELOAD_ACTIVE_TOOLS_KEY];
77
+ delete globalReloadState[TOOL_DISPLAY_RELOAD_ACTIVE_TOOLS_KEY];
78
+
68
79
  resetDisposed();
69
80
 
70
81
  pi.on("session_shutdown", (event: { reason: string }) => {
71
82
  if (event.reason === "reload") {
83
+ try {
84
+ globalReloadState[TOOL_DISPLAY_RELOAD_ACTIVE_TOOLS_KEY] = pi
85
+ .getActiveTools()
86
+ .filter((toolName) => BUILT_IN_TOOL_OVERRIDE_NAME_SET.has(toolName));
87
+ } catch (error) {
88
+ logToolDisplayDebug("Failed to preserve active built-in tools for reload.", error);
89
+ }
72
90
  disposeAll();
73
91
  initializedHosts.delete(pi);
74
92
  }
@@ -112,22 +130,23 @@ export function ensureToolDisplayHost(
112
130
  };
113
131
 
114
132
  if (initial.config.enabled) {
115
- registerToolDisplayOverrides(pi, getEffectiveConfig);
133
+ registerToolDisplayOverrides(pi, getEffectiveConfig, activeBuiltInToolNamesBeforeReload);
116
134
  registerNativeUserMessageBox(pi, getConfig);
117
135
  registerThinkingLabeling(pi);
118
136
  }
119
137
 
120
138
  // Each Pi extension has its own registration map, while getCommands() sees
121
139
  // 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
- });
140
+ // duplicate management commands when they initialize this host separately.
141
+ const command = {
142
+ description: "Configure tool output rendering (OpenCode-style)",
143
+ handler: async (args: string, ctx: ExtensionCommandContext) => {
144
+ const { runToolDisplayCommandHandler } = await import("./config-modal.js");
145
+ await runToolDisplayCommandHandler(args, ctx, { getConfig, setConfig, getCapabilities });
146
+ },
147
+ };
148
+ for (const name of ["config:tool-display", "tool-display", "pi-tool-display"] as const) {
149
+ if (!hasRegisteredToolDisplayCommand(pi, name)) pi.registerCommand(name, command);
131
150
  }
132
151
 
133
152
  pi.on("session_start", async (_event, ctx) => {
@@ -1668,9 +1668,20 @@ function tryGetAllTools(pi: ExtensionAPI, debugMessage: string): unknown[] | und
1668
1668
  }
1669
1669
  }
1670
1670
 
1671
+ // Return undefined until Pi binds the extension action methods.
1672
+ function tryGetActiveToolNames(pi: ExtensionAPI, debugMessage: string): string[] | undefined {
1673
+ try {
1674
+ return pi.getActiveTools();
1675
+ } catch (error) {
1676
+ logToolDisplayDebug(debugMessage, error);
1677
+ return undefined;
1678
+ }
1679
+ }
1680
+
1671
1681
  export function registerToolDisplayOverrides(
1672
1682
  pi: ExtensionAPI,
1673
1683
  getConfig: ConfigGetter,
1684
+ activeToolNamesDuringLoad?: readonly string[],
1674
1685
  ): void {
1675
1686
  clearBuiltInToolCache();
1676
1687
  const toolDisplayApi = installToolDisplayApi(getConfig);
@@ -1688,7 +1699,7 @@ export function registerToolDisplayOverrides(
1688
1699
  const registeredBuiltInToolOverrides = new Set<BuiltInToolOverrideName>();
1689
1700
 
1690
1701
  const isExternallyOwnedBuiltInTool = (toolName: BuiltInToolOverrideName): boolean => {
1691
- const allTools = tryGetAllTools(pi, "Built-in tool override ownership discovery unavailable during extension load; registering renderer for pre-bind history rendering.");
1702
+ const allTools = tryGetAllTools(pi, "Built-in tool override ownership discovery unavailable; skipping conflicting owners.");
1692
1703
  if (!allTools) {
1693
1704
  return false;
1694
1705
  }
@@ -1780,7 +1791,37 @@ export function registerToolDisplayOverrides(
1780
1791
  return { scope: getSearchScope(args), limitSuffix: args.limit !== undefined ? ` (limit ${args.limit})` : "" };
1781
1792
  };
1782
1793
 
1783
- registerIfOwned("read", () => {
1794
+ const builtInToolRegistrations = new Map<BuiltInToolOverrideName, () => void>();
1795
+
1796
+ // Record each built-in replacement until Pi exposes its active tool selection.
1797
+ const recordBuiltInToolRegistration = (
1798
+ toolName: BuiltInToolOverrideName,
1799
+ register: () => void,
1800
+ ): void => {
1801
+ builtInToolRegistrations.set(toolName, register);
1802
+ };
1803
+
1804
+ // Retry deferred replacements against Pi's current active tool selection.
1805
+ const registerActiveBuiltInToolOverrides = (
1806
+ knownActiveToolNames?: readonly string[],
1807
+ ): void => {
1808
+ const activeToolNames = knownActiveToolNames ?? tryGetActiveToolNames(
1809
+ pi,
1810
+ "Built-in tool activation discovery unavailable; skipping display overrides.",
1811
+ );
1812
+ if (!activeToolNames) {
1813
+ return;
1814
+ }
1815
+
1816
+ const activeTools = new Set(activeToolNames);
1817
+ for (const [toolName, register] of builtInToolRegistrations) {
1818
+ if (activeTools.has(toolName)) {
1819
+ registerIfOwned(toolName, register);
1820
+ }
1821
+ }
1822
+ };
1823
+
1824
+ recordBuiltInToolRegistration("read", () => {
1784
1825
  registerRuntimeTool(pi, {
1785
1826
  name: "read",
1786
1827
  label: "read",
@@ -1795,7 +1836,7 @@ export function registerToolDisplayOverrides(
1795
1836
  });
1796
1837
  });
1797
1838
 
1798
- registerIfOwned("grep", () => {
1839
+ recordBuiltInToolRegistration("grep", () => {
1799
1840
  registerRuntimeTool(pi, {
1800
1841
  name: "grep",
1801
1842
  label: "grep",
@@ -1816,7 +1857,7 @@ export function registerToolDisplayOverrides(
1816
1857
  });
1817
1858
  });
1818
1859
 
1819
- registerIfOwned("find", () => {
1860
+ recordBuiltInToolRegistration("find", () => {
1820
1861
  registerRuntimeTool(pi, {
1821
1862
  name: "find",
1822
1863
  label: "find",
@@ -1834,7 +1875,7 @@ export function registerToolDisplayOverrides(
1834
1875
  });
1835
1876
  });
1836
1877
 
1837
- registerIfOwned("ls", () => {
1878
+ recordBuiltInToolRegistration("ls", () => {
1838
1879
  registerRuntimeTool(pi, {
1839
1880
  name: "ls",
1840
1881
  label: "ls",
@@ -1850,7 +1891,7 @@ export function registerToolDisplayOverrides(
1850
1891
  });
1851
1892
  });
1852
1893
 
1853
- registerIfOwned("edit", () => {
1894
+ recordBuiltInToolRegistration("edit", () => {
1854
1895
  registerRuntimeTool(pi, {
1855
1896
  name: "edit",
1856
1897
  label: "edit",
@@ -1872,7 +1913,7 @@ export function registerToolDisplayOverrides(
1872
1913
  });
1873
1914
  });
1874
1915
 
1875
- registerIfOwned("write", () => {
1916
+ recordBuiltInToolRegistration("write", () => {
1876
1917
  registerRuntimeTool(pi, {
1877
1918
  name: "write",
1878
1919
  label: "write",
@@ -1945,7 +1986,7 @@ export function registerToolDisplayOverrides(
1945
1986
  });
1946
1987
  });
1947
1988
 
1948
- registerIfOwned("bash", () => {
1989
+ recordBuiltInToolRegistration("bash", () => {
1949
1990
  registerRuntimeTool(pi, {
1950
1991
  name: "bash",
1951
1992
  label: "bash",
@@ -2024,6 +2065,8 @@ export function registerToolDisplayOverrides(
2024
2065
  });
2025
2066
  });
2026
2067
 
2068
+ registerActiveBuiltInToolOverrides(activeToolNamesDuringLoad);
2069
+
2027
2070
  const wrappedCustomToolNames = new Set<string>();
2028
2071
  registerCleanup(() => wrappedCustomToolNames.clear());
2029
2072
 
@@ -2229,11 +2272,13 @@ export function registerToolDisplayOverrides(
2229
2272
 
2230
2273
  pi.on("session_start", async () => {
2231
2274
  clearWriteExecutionMeta(writeExecutionMetaByToolCallId);
2275
+ registerActiveBuiltInToolOverrides();
2232
2276
  registerMcpToolOverrides();
2233
2277
  scheduleMcpToolOverrideDiscovery();
2234
2278
  });
2235
2279
  pi.on("before_agent_start", async () => {
2236
2280
  clearWriteExecutionMeta(writeExecutionMetaByToolCallId);
2281
+ registerActiveBuiltInToolOverrides();
2237
2282
  registerMcpToolOverrides();
2238
2283
  scheduleMcpToolOverrideDiscovery();
2239
2284
  });