pi-ui-extension 1.1.5 → 1.1.7

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
@@ -11,7 +11,7 @@ The package can be installed from npm or Git, and uses GitHub Actions for type c
11
11
 
12
12
  ## Preview
13
13
 
14
- <img width="1377" height="671" alt="pi-ui-extension preview" src="https://github.com/user-attachments/assets/ddea5dc9-8796-433a-abfc-c7ea2adec089" />
14
+ <img width="1377" height="671" alt="pi-ui-extension preview" src="https://github.com/1993yihuan/pi-ui-extension/releases/download/v1.1.6/preview.png" />
15
15
 
16
16
  ## Features
17
17
 
package/README.zh-CN.md CHANGED
@@ -11,7 +11,7 @@
11
11
 
12
12
  ## 界面预览
13
13
 
14
- <img width="1377" height="671" alt="pi-ui-extension 界面预览" src="https://github.com/user-attachments/assets/ddea5dc9-8796-433a-abfc-c7ea2adec089" />
14
+ <img width="1377" height="671" alt="pi-ui-extension 界面预览" src="https://github.com/1993yihuan/pi-ui-extension/releases/download/v1.1.6/preview.png" />
15
15
 
16
16
  ## 功能概览
17
17
 
@@ -13,7 +13,7 @@ import { installContextEstimates } from "./ctx/data.ts";
13
13
  import { createActivityTracker, type ActivityTracker } from "./ctx/activity.ts";
14
14
  import { installFooter, type FooterController } from "./footer/index.ts";
15
15
  import { isFabricFullCodeMode } from "./fabric-detection.ts";
16
- import { mcpResourceEntriesFromStatus } from "./mcp/resources.ts";
16
+ import { mcpResourceEntriesFromConfig, mcpResourceEntriesFromStatus, mergeMcpResourceEntries } from "./mcp/resources.ts";
17
17
  import {
18
18
  createDefaultSidebarPanels,
19
19
  SESSION_PANEL_ID,
@@ -185,6 +185,9 @@ async function clearOtherSessions(
185
185
  export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
186
186
  const toolView = await installToolViewController(pi);
187
187
  let mcpResources: readonly ResourceEntry[] = [];
188
+ let liveMcpResources: readonly ResourceEntry[] = [];
189
+ const currentMcpResources = (cwd: string, trusted: boolean): readonly ResourceEntry[] =>
190
+ mergeMcpResourceEntries(mcpResourceEntriesFromConfig(getAgentDir(), cwd, trusted), liveMcpResources);
188
191
  let toolViewMode: ToolViewMode | undefined;
189
192
  let footer: FooterController | undefined;
190
193
  let sidebar: SidebarController | undefined;
@@ -219,7 +222,8 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
219
222
  let switchRequested = false;
220
223
  const getMcpServerNames = () => mcpResources.map((server) => server.name);
221
224
  const unsubscribeMcpStatus = pi.events.on("pi-mcp-adapter/status/v1", (value) => {
222
- mcpResources = mcpResourceEntriesFromStatus(value);
225
+ liveMcpResources = mcpResourceEntriesFromStatus(value);
226
+ mcpResources = mergeMcpResourceEntries(mcpResources, liveMcpResources);
223
227
  const branch = getCurrentBranch?.();
224
228
  if (branch) toolUsage?.resetSession(branch, getMcpServerNames(), { preserveExchange: true });
225
229
  sidebar?.refresh();
@@ -452,6 +456,7 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
452
456
  // Pi resets extension UI before starting/reloading a session. Do not call
453
457
  // stale controllers from the previous runtime after that reset.
454
458
  clearControllers();
459
+ mcpResources = currentMcpResources(ctx.cwd, ctx.isProjectTrusted());
455
460
  toolViewMode = toolView.getMode();
456
461
  getCurrentBranch = () => ctx.sessionManager.getBranch();
457
462
  const branch = ctx.sessionManager.getBranch();
@@ -529,7 +534,7 @@ export default async function piUiExtension(pi: ExtensionAPI): Promise<void> {
529
534
  ctx.ui.notify("Wait for the current run to finish before opening resources", "warning");
530
535
  return;
531
536
  }
532
- void loadResourceEntries(pi, ctx.cwd, mcpResources, ctx.isProjectTrusted(), button)
537
+ void loadResourceEntries(pi, ctx.cwd, button === "mcp" ? currentMcpResources(ctx.cwd, ctx.isProjectTrusted()) : mcpResources, ctx.isProjectTrusted(), button)
533
538
  .then((rows) => {
534
539
  if (!rows || rows.length === 0) {
535
540
  ctx.ui.notify("No resources of this kind loaded", "info");
@@ -25,7 +25,7 @@ test("entry wires early/late MCP status into restored and live usage without dou
25
25
  events: { on(name: string, fn: any) { bus.set(name, fn); return () => bus.delete(name); } },
26
26
  };
27
27
  const ctx: any = {
28
- mode: "tui", cwd: directory, getSystemPrompt: () => "", getContextUsage: () => undefined,
28
+ mode: "tui", cwd: directory, isProjectTrusted: () => true, getSystemPrompt: () => "", getContextUsage: () => undefined,
29
29
  sessionManager: { getBranch: () => branch, buildContextEntries: () => [], getSessionId: () => "test", getSessionDir: () => directory },
30
30
  ui: { setStatus() {}, setFooter(factory: any) { footer?.dispose(); footer = factory?.(tui, theme, { onBranchChange: () => () => {}, getGitBranch: () => null }); } },
31
31
  };
@@ -1,6 +1,29 @@
1
1
  import assert from "node:assert/strict";
2
2
  import test from "node:test";
3
- import { mcpResourceEntriesFromStatus } from "./resources.ts";
3
+ import { mcpResourceEntriesFromConfig, mcpResourceEntriesFromStatus, mergeMcpResourceEntries } from "./resources.ts";
4
+ import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from "node:fs";
5
+ import { tmpdir } from "node:os";
6
+ import { join } from "node:path";
7
+
8
+ test("discovers global and trusted project MCP configs without exposing secrets", () => {
9
+ const root = mkdtempSync(join(tmpdir(), "mcp-resource-"));
10
+ const agent = join(root, "agent"), cwd = join(root, "project");
11
+ mkdirSync(agent); mkdirSync(join(cwd, ".pi"), { recursive: true });
12
+ try {
13
+ writeFileSync(join(agent, "mcp.json"), JSON.stringify({ mcpServers: {
14
+ context7: { url: "https://secret.invalid", headers: { Authorization: "secret" } },
15
+ disabled: { disabled: true },
16
+ } }));
17
+ writeFileSync(join(cwd, ".pi", "mcp.json"), JSON.stringify({ mcpServers: { "chrome-devtools": { command: "secret" } } }));
18
+ const untrusted = mcpResourceEntriesFromConfig(agent, cwd, false);
19
+ assert.deepEqual(untrusted.map(x => x.name), ["context7"]);
20
+ const trusted = mcpResourceEntriesFromConfig(agent, cwd, true);
21
+ assert.deepEqual(trusted.map(x => x.name), ["context7", "chrome-devtools"]);
22
+ assert.doesNotMatch(JSON.stringify(trusted), /secret/);
23
+ assert.deepEqual(mergeMcpResourceEntries(trusted, mcpResourceEntriesFromStatus({ version: 1, servers: [{ name: "context7" }] }))
24
+ .map(x => x.description), ["MCP server loaded in the current Pi session", "Configured MCP server (connection not verified)"]);
25
+ } finally { rmSync(root, { recursive: true, force: true }); }
26
+ });
4
27
 
5
28
  test("builds MCP resource rows from the adapter status snapshot", () => {
6
29
  assert.deepEqual(
@@ -1,8 +1,40 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+
1
4
  export type McpResourceEntry = {
2
5
  readonly name: string;
3
6
  readonly description: string;
4
7
  };
5
8
 
9
+ export function mcpResourceEntriesFromConfig(agentDir: string, cwd: string, trusted: boolean): readonly McpResourceEntry[] {
10
+ const paths = [join(agentDir, "mcp.json")];
11
+ if (trusted) paths.push(join(cwd, ".pi", "mcp.json"));
12
+ const rows = new Map<string, McpResourceEntry>();
13
+ for (const path of paths) {
14
+ try {
15
+ const config: unknown = JSON.parse(readFileSync(path, "utf8"));
16
+ if (!config || typeof config !== "object" || Array.isArray(config)) continue;
17
+ const servers = (config as { mcpServers?: unknown }).mcpServers;
18
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) continue;
19
+ for (const [name, value] of Object.entries(servers)) {
20
+ if (!name.trim() || !value || typeof value !== "object" || Array.isArray(value)) continue;
21
+ if ((value as { disabled?: unknown }).disabled === true) {
22
+ rows.delete(name);
23
+ continue;
24
+ }
25
+ rows.set(name, { name, description: "Configured MCP server (connection not verified)" });
26
+ }
27
+ } catch { /* Missing or invalid configuration is not fatal. */ }
28
+ }
29
+ return [...rows.values()];
30
+ }
31
+
32
+ export function mergeMcpResourceEntries(configured: readonly McpResourceEntry[], live: readonly McpResourceEntry[]): readonly McpResourceEntry[] {
33
+ const rows = new Map(configured.map((entry) => [entry.name, entry]));
34
+ for (const entry of live) rows.set(entry.name, entry);
35
+ return [...rows.values()];
36
+ }
37
+
6
38
  type McpStatusServer = {
7
39
  readonly name?: unknown;
8
40
  readonly disabled?: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-ui-extension",
3
- "version": "1.1.5",
3
+ "version": "1.1.7",
4
4
  "type": "module",
5
5
  "description": "Pi terminal UI extension with session management, context inspection, Tool/MCP usage tracking, sidebar panels, and a compact footer",
6
6
  "keywords": [
@@ -17,7 +17,7 @@
17
17
  "extensions": [
18
18
  "./extensions/index.ts"
19
19
  ],
20
- "image": "https://github.com/user-attachments/assets/ddea5dc9-8796-433a-abfc-c7ea2adec089"
20
+ "image": "https://github.com/1993yihuan/pi-ui-extension/releases/download/v1.1.6/preview.png"
21
21
  },
22
22
  "scripts": {
23
23
  "typecheck": "tsc -p tsconfig.check.json",