@herbertgao/pi-extensions 2026.8.1 → 2026.8.2

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.
Files changed (162) hide show
  1. package/README.md +5 -2
  2. package/node_modules/@czottmann/pi-automode/README.md +16 -2
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
  6. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
  9. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
  10. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
  11. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
  12. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
  15. package/node_modules/@czottmann/pi-automode/package.json +4 -4
  16. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
  17. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
  18. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
  19. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
  20. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
  21. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
  22. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
  23. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
  24. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
  25. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
  26. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
  27. package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
  28. package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
  29. package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
  30. package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
  31. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
  32. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
  33. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
  34. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
  35. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
  36. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
  37. package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
  38. package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
  39. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
  40. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
  41. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
  42. package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
  43. package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
  44. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
  45. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
  46. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
  47. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
  48. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
  49. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
  50. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
  51. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
  52. package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
  53. package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
  54. package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
  55. package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
  56. package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
  57. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
  58. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
  59. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
  60. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
  61. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
  62. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
  63. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
  64. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
  65. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
  66. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
  67. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
  68. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
  69. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
  70. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
  71. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
  72. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
  73. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
  74. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
  75. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
  76. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
  77. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
  78. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
  79. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
  80. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
  81. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  82. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
  83. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
  84. package/node_modules/@herbertgao/pi-stash/README.md +9 -9
  85. package/node_modules/@herbertgao/pi-stash/package.json +1 -1
  86. package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
  87. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
  88. package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
  89. package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
  90. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
  91. package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
  92. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
  93. package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
  94. package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
  95. package/node_modules/@narumitw/pi-btw/README.md +210 -0
  96. package/node_modules/@narumitw/pi-btw/package.json +53 -0
  97. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
  98. package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
  99. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
  100. package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
  101. package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
  102. package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
  103. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
  104. package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
  105. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
  106. package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
  107. package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
  108. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
  109. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
  110. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
  111. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
  112. package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
  113. package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
  114. package/node_modules/pi-mcp-adapter/README.md +62 -13
  115. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
  116. package/node_modules/pi-mcp-adapter/commands.ts +9 -7
  117. package/node_modules/pi-mcp-adapter/config.ts +110 -49
  118. package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
  119. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
  120. package/node_modules/pi-mcp-adapter/errors.ts +21 -17
  121. package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
  122. package/node_modules/pi-mcp-adapter/index.ts +37 -14
  123. package/node_modules/pi-mcp-adapter/init.ts +9 -7
  124. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
  125. package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
  126. package/node_modules/pi-mcp-adapter/logger.ts +1 -1
  127. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
  128. package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
  129. package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
  130. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
  131. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
  132. package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
  133. package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
  134. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
  135. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
  136. package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
  137. package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
  138. package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
  139. package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
  140. package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
  141. package/node_modules/pi-mcp-adapter/package.json +13 -8
  142. package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
  143. package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
  144. package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
  145. package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
  146. package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
  147. package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
  148. package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
  149. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
  150. package/node_modules/pi-mcp-adapter/state.ts +3 -1
  151. package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
  152. package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
  153. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
  154. package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
  155. package/node_modules/pi-mcp-adapter/types.ts +109 -22
  156. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
  157. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
  158. package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
  159. package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
  160. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
  161. package/node_modules/pi-mcp-adapter/utils.ts +23 -9
  162. package/package.json +22 -7
@@ -2,12 +2,12 @@ import http, { type IncomingMessage, type ServerResponse } from "node:http";
2
2
  import fs from "node:fs/promises";
3
3
  import path from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
- import { buildAllowAttribute } from "@modelcontextprotocol/ext-apps/app-bridge";
5
+ import { buildAllowAttribute } from "./ui-app-bridge-helpers.ts";
6
6
  import {
7
- ContentBlockSchema,
8
7
  type CallToolRequest,
9
8
  type CallToolResult,
10
- } from "@modelcontextprotocol/sdk/types.js";
9
+ } from "@modelcontextprotocol/client";
10
+ import { ContentBlockSchema } from "@modelcontextprotocol/core";
11
11
  import type { ConsentManager } from "./consent-manager.ts";
12
12
  import { ServerError, wrapError } from "./errors.ts";
13
13
  import { formatAuthRequiredMessage } from "./utils.ts";
@@ -17,7 +17,7 @@ import type { McpServerManager } from "./server-manager.ts";
17
17
  import type { McpExtensionState } from "./state.ts";
18
18
  import { SessionRecoveryAuthRequiredError, withSessionRecovery, type SessionRecoveryDeps } from "./session-recovery.ts";
19
19
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
20
- import { extractUiToolVisibility, isUiToolCallableByApp } from "./ui-tool-visibility.ts";
20
+ import { extractUiToolVisibility, isUiToolCallableByApp, isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
21
21
  import {
22
22
  createUiModelContextUpdate,
23
23
  extractUiPromptText,
@@ -95,6 +95,7 @@ export interface UiServerHandle {
95
95
 
96
96
  export async function startUiServer(options: UiServerOptions): Promise<UiServerHandle> {
97
97
  const sessionToken = options.sessionToken ?? randomUUID();
98
+ const uiResourceToken = randomUUID();
98
99
  const log = logger.child({
99
100
  component: "UiServer",
100
101
  server: options.serverName,
@@ -144,6 +145,41 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
144
145
  }
145
146
  }
146
147
 
148
+ const isAppOnlyTool = (toolName: string): boolean => {
149
+ const toolDefinition = options.manager.getConnection(options.serverName)?.tools?.find((tool) => tool.name === toolName);
150
+ if (!toolDefinition) return false;
151
+ const visibility = extractUiToolVisibility(toolDefinition._meta);
152
+ return isUiToolCallableByApp(visibility) && !isUiToolVisibleToModel(visibility);
153
+ };
154
+
155
+ const recordUiMessage = async (msgParams: UiMessageParams): Promise<void> => {
156
+ const promptText = extractUiPromptText(msgParams);
157
+
158
+ // Track messages by type (order: prompt → intent → notify)
159
+ // Must match the order in index.ts onMessage handler
160
+ if (promptText) {
161
+ sessionMessages.prompts.push(promptText);
162
+ log.debug("UI prompt received", { prompt: promptText.slice(0, 100) });
163
+ } else if (msgParams.type === "intent" || msgParams.intent) {
164
+ const intentName = msgParams.intent ?? "";
165
+ if (intentName) {
166
+ sessionMessages.intents.push({
167
+ intent: intentName,
168
+ ...(msgParams.params !== undefined ? { params: msgParams.params } : {}),
169
+ });
170
+ log.debug("UI intent received", { intent: intentName });
171
+ }
172
+ } else if (msgParams.type === "notify" || msgParams.message) {
173
+ const notifyText = msgParams.message ?? "";
174
+ if (notifyText) {
175
+ sessionMessages.notifications.push(notifyText);
176
+ log.debug("UI notification", { message: notifyText.slice(0, 100) });
177
+ }
178
+ }
179
+
180
+ await options.onMessage?.(msgParams);
181
+ };
182
+
147
183
  const touchHeartbeat = () => {
148
184
  lastHeartbeatAt = Date.now();
149
185
  };
@@ -164,7 +200,8 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
164
200
  streamSummary.phases.push(envelope.phase);
165
201
  }
166
202
  streamSummary.finalStatus = envelope.status;
167
- streamSummary.lastMessage = envelope.message;
203
+ if (envelope.message !== undefined) streamSummary.lastMessage = envelope.message;
204
+ else delete streamSummary.lastMessage;
168
205
  };
169
206
 
170
207
  const serializeEvent = (eventId: number, name: string, payload: unknown): string => {
@@ -174,6 +211,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
174
211
  const getLatestCheckpointIndex = () => {
175
212
  for (let index = eventLog.length - 1; index >= 0; index -= 1) {
176
213
  const entry = eventLog[index];
214
+ if (!entry) continue;
177
215
  const envelope = getVisualizationStreamEnvelope((entry.payload as { structuredContent?: unknown } | null)?.structuredContent);
178
216
  if (envelope?.frameType === "checkpoint" || envelope?.frameType === "final") {
179
217
  return index;
@@ -271,13 +309,11 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
271
309
  }
272
310
 
273
311
  if (method === "GET" && url.pathname === "/") {
274
- if (!url.searchParams.has("session") && isLoopbackAddress(req.socket.remoteAddress)) {
275
- const dest = `/?session=${encodeURIComponent(sessionToken)}`;
312
+ if (!url.searchParams.has("session")) {
276
313
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" });
277
314
  res.end(
278
- `<!doctype html><html><head><meta charset="utf-8"><title>${escapeHtml(`MCP UI - ${options.serverName} / ${options.toolName}`)}</title>` +
279
- `<noscript><meta http-equiv="refresh" content="0;url=${dest}"></noscript></head>` +
280
- `<body><script>location.replace(${JSON.stringify(dest)});</script></body></html>`,
315
+ "<!doctype html><html><head><meta charset=\"utf-8\"><title>MCP UI</title></head>" +
316
+ "<body><p>Open the authenticated MCP UI URL shown by Pi.</p></body></html>",
281
317
  );
282
318
  return;
283
319
  }
@@ -286,6 +322,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
286
322
 
287
323
  const html = buildHostHtmlTemplate({
288
324
  sessionToken,
325
+ uiResourceToken,
289
326
  serverName: options.serverName,
290
327
  toolName: options.toolName,
291
328
  toolArgs: options.toolArgs,
@@ -330,7 +367,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
330
367
  }
331
368
 
332
369
  if (method === "GET" && url.pathname === "/ui-app") {
333
- if (!validateTokenQuery(url, sessionToken, res)) return;
370
+ if (!validateTokenQuery(url, uiResourceToken, res, "resource")) return;
334
371
  touchHeartbeat();
335
372
  // Enforce host metadata independently of where app HTML places its document head.
336
373
  const cspContent = buildCspMetaContent(options.resource.meta.csp);
@@ -411,8 +448,8 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
411
448
  name: callParams.name,
412
449
  originalName: callParams.name,
413
450
  description: toolDefinition?.description ?? "",
414
- inputSchema: toolDefinition?.inputSchema,
415
- uiVisibility,
451
+ ...(toolDefinition?.inputSchema !== undefined ? { inputSchema: toolDefinition.inputSchema } : {}),
452
+ ...(uiVisibility !== undefined ? { uiVisibility } : {}),
416
453
  };
417
454
  const approval = options.state
418
455
  ? await ensureToolCallApproved(
@@ -421,6 +458,7 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
421
458
  toolMeta,
422
459
  callArgs.arguments,
423
460
  options.state.owner?.signal,
461
+ "iframe",
424
462
  )
425
463
  : options.config && isToolCallApprovalRequired(options.config, options.serverName, toolMeta)
426
464
  ? { ok: false as const, reason: "approval_required_headless" as const }
@@ -449,11 +487,15 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
449
487
  options.manager.incrementInFlight(options.serverName);
450
488
  const result = options.config
451
489
  ? await withSessionRecovery(
452
- { manager: options.manager, config: options.config, onNeedsAuth: options.onNeedsAuth },
490
+ {
491
+ manager: options.manager,
492
+ config: options.config,
493
+ ...(options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}),
494
+ },
453
495
  options.serverName,
454
- (conn) => conn.client.callTool(callArgs, undefined, options.manager.getRequestOptions?.(options.serverName)),
496
+ (conn) => conn.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName)),
455
497
  )
456
- : await connection.client.callTool(callArgs, undefined, options.manager.getRequestOptions?.(options.serverName));
498
+ : await connection.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
457
499
  sendJson(res, 200, { ok: true, result });
458
500
  } finally {
459
501
  options.manager.decrementInFlight(options.serverName);
@@ -469,33 +511,27 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
469
511
  return;
470
512
  }
471
513
 
472
- if (url.pathname === "/proxy/ui/message") {
473
- const msgParams = params as UiMessageParams;
474
- const promptText = extractUiPromptText(msgParams);
475
-
476
- // Track messages by type (order: prompt → intent → notify)
477
- // Must match the order in index.ts onMessage handler
478
- if (promptText) {
479
- sessionMessages.prompts.push(promptText);
480
- log.debug("UI prompt received", { prompt: promptText.slice(0, 100) });
481
- } else if (msgParams.type === "intent" || msgParams.intent) {
482
- const intentName = msgParams.intent ?? "";
483
- if (intentName) {
484
- sessionMessages.intents.push({
485
- intent: intentName,
486
- params: msgParams.params
487
- });
488
- log.debug("UI intent received", { intent: intentName });
489
- }
490
- } else if (msgParams.type === "notify" || msgParams.message) {
491
- const notifyText = msgParams.message ?? "";
492
- if (notifyText) {
493
- sessionMessages.notifications.push(notifyText);
494
- log.debug("UI notification", { message: notifyText.slice(0, 100) });
495
- }
514
+ if (url.pathname === "/proxy/ui/generated-tool-call-intent") {
515
+ const tool = typeof params.tool === "string" ? params.tool : undefined;
516
+ if (tool && isAppOnlyTool(tool)) {
517
+ log.debug("Ignored generated app-only tool call intent", { tool });
518
+ } else {
519
+ await recordUiMessage({
520
+ type: "intent",
521
+ intent: "call_tool",
522
+ params: {
523
+ ...(tool !== undefined ? { tool } : {}),
524
+ ...(params.arguments !== undefined ? { arguments: params.arguments } : {}),
525
+ ...(params.isError !== undefined ? { isError: params.isError } : {}),
526
+ },
527
+ });
496
528
  }
497
-
498
- await options.onMessage?.(msgParams);
529
+ sendJson(res, 200, { ok: true, result: {} });
530
+ return;
531
+ }
532
+
533
+ if (url.pathname === "/proxy/ui/message") {
534
+ await recordUiMessage(params as UiMessageParams);
499
535
  sendJson(res, 200, { ok: true, result: {} });
500
536
  return;
501
537
  }
@@ -639,7 +675,11 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
639
675
  return;
640
676
  }
641
677
  log.error("Failed to start server", error);
642
- reject(new ServerError(error.message, { port: candidates[candidateIndex], cause: error }));
678
+ const port = candidates[candidateIndex];
679
+ reject(new ServerError(error.message, {
680
+ ...(port !== undefined ? { port } : {}),
681
+ cause: error,
682
+ }));
643
683
  };
644
684
 
645
685
  const onListening = () => {
@@ -760,21 +800,13 @@ function isAllowedHost(hostname: string): boolean {
760
800
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]";
761
801
  }
762
802
 
763
- function isLoopbackAddress(address: string | undefined): boolean {
764
- return address === "127.0.0.1" || address === "::1" || address === "::ffff:127.0.0.1";
765
- }
766
-
767
- function escapeHtml(value: string): string {
768
- return value
769
- .replace(/&/g, "&amp;")
770
- .replace(/</g, "&lt;")
771
- .replace(/>/g, "&gt;")
772
- .replace(/"/g, "&quot;")
773
- .replace(/'/g, "&#39;");
774
- }
775
-
776
- function validateTokenQuery(url: URL, expected: string, res: ServerResponse): boolean {
777
- const token = url.searchParams.get("session");
803
+ function validateTokenQuery(
804
+ url: URL,
805
+ expected: string,
806
+ res: ServerResponse,
807
+ parameter = "session",
808
+ ): boolean {
809
+ const token = url.searchParams.get(parameter);
778
810
  if (token !== expected) {
779
811
  sendJson(res, 403, { ok: false, error: "Invalid session" });
780
812
  return false;
@@ -1,7 +1,7 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import net from "node:net";
4
- import { UrlElicitationRequiredError, type CallToolResult } from "@modelcontextprotocol/sdk/types.js";
4
+ import { UrlElicitationRequiredError, type CallToolResult } from "@modelcontextprotocol/client";
5
5
  import type { McpExtensionState } from "./state.ts";
6
6
  import {
7
7
  createUiModelContextUpdate,
@@ -99,8 +99,10 @@ function withStreamEnvelope(
99
99
  return result;
100
100
  }
101
101
 
102
- const structuredContent = result.structuredContent && typeof result.structuredContent === "object" && !Array.isArray(result.structuredContent)
103
- ? { ...result.structuredContent }
102
+ const structuredContent: Record<string, unknown> = result.structuredContent
103
+ && typeof result.structuredContent === "object"
104
+ && !Array.isArray(result.structuredContent)
105
+ ? { ...(result.structuredContent as Record<string, unknown>) }
104
106
  : {};
105
107
 
106
108
  const rawEnvelope = structuredContent[UI_STREAM_STRUCTURED_CONTENT_KEY];
@@ -239,10 +241,10 @@ export async function maybeStartUiSession(
239
241
  serverName: request.serverName,
240
242
  toolName: request.toolName,
241
243
  reused: true,
242
- streamId,
243
- streamToken,
244
- streamMode,
245
- requestMeta: streamToken ? { [UI_STREAM_REQUEST_META_KEY]: streamToken } : undefined,
244
+ ...(streamId !== undefined ? { streamId } : {}),
245
+ ...(streamToken !== undefined ? { streamToken } : {}),
246
+ ...(streamMode !== undefined ? { streamMode } : {}),
247
+ ...(streamToken ? { requestMeta: { [UI_STREAM_REQUEST_META_KEY]: streamToken } } : {}),
246
248
  url: existingHandle.url,
247
249
  viewer: existingHandle.viewer ?? "browser",
248
250
  windowOpen: existingHandle.windowOpen ?? true,
@@ -326,9 +328,9 @@ export async function maybeStartUiSession(
326
328
  manager: state.manager,
327
329
  config: state.config,
328
330
  state,
329
- onNeedsAuth: request.onNeedsAuth,
331
+ ...(request.onNeedsAuth ? { onNeedsAuth: request.onNeedsAuth } : {}),
330
332
  consentManager: state.consentManager,
331
- hostContext,
333
+ ...(hostContext !== undefined ? { hostContext } : {}),
332
334
 
333
335
  onMessage: (params: UiMessageParams) => {
334
336
  const prompt = extractUiPromptText(params);
@@ -410,7 +412,7 @@ export async function maybeStartUiSession(
410
412
  completedAt: new Date(),
411
413
  reason,
412
414
  messages,
413
- stream,
415
+ ...(stream !== undefined ? { stream } : {}),
414
416
  });
415
417
 
416
418
  while (state.completedUiSessions.length > MAX_COMPLETED_SESSIONS) {
@@ -527,10 +529,10 @@ export async function maybeStartUiSession(
527
529
  serverName: request.serverName,
528
530
  toolName: request.toolName,
529
531
  reused: false,
530
- streamId,
531
- streamToken,
532
- streamMode,
533
- requestMeta: streamToken ? { [UI_STREAM_REQUEST_META_KEY]: streamToken } : undefined,
532
+ ...(streamId !== undefined ? { streamId } : {}),
533
+ ...(streamToken !== undefined ? { streamToken } : {}),
534
+ ...(streamMode !== undefined ? { streamMode } : {}),
535
+ ...(streamToken ? { requestMeta: { [UI_STREAM_REQUEST_META_KEY]: streamToken } } : {}),
534
536
  url: handle.url,
535
537
  viewer,
536
538
  windowOpen,
@@ -1,11 +1,11 @@
1
1
  import { createConnection, type Socket } from "node:net";
2
- import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/sdk/shared/stdio.js";
3
- import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
- import type { JSONRPCMessage } from "@modelcontextprotocol/sdk/types.js";
2
+ import { ReadBuffer, serializeMessage } from "@modelcontextprotocol/client";
3
+ import type { Transport } from "@modelcontextprotocol/client";
4
+ import type { JSONRPCMessage } from "@modelcontextprotocol/client";
5
5
 
6
6
  /** MCP JSONL transport for an explicitly configured Unix-domain socket. */
7
7
  export class UnixSocketClientTransport implements Transport {
8
- private socket?: Socket;
8
+ private socket: Socket | undefined;
9
9
  private readonly readBuffer = new ReadBuffer();
10
10
 
11
11
  onclose?: () => void;
@@ -1,21 +1,30 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import { homedir, platform } from "node:os";
4
- import { join } from "node:path";
4
+ import { extname, isAbsolute, join } from "node:path";
5
5
  import type { McpConfig, ServerEntry } from "./types.ts";
6
6
 
7
7
  async function execOpen(pi: ExtensionAPI, target: string, browser?: string, signal?: AbortSignal) {
8
8
  const os = platform();
9
9
 
10
10
  if (os === "darwin") {
11
- return browser ? pi.exec("open", ["-a", browser, target], { signal }) : pi.exec("open", [target], { signal });
11
+ if (browser) {
12
+ // An absolute executable path (e.g. from $BROWSER) names a launcher
13
+ // binary. App bundles still go through `open -a`, which handles them.
14
+ return isAbsolute(browser) && extname(browser).toLowerCase() !== ".app"
15
+ ? pi.exec(browser, [target], signal ? { signal } : {})
16
+ : pi.exec("open", ["-a", browser, target], signal ? { signal } : {});
17
+ }
18
+ return pi.exec("open", [target], signal ? { signal } : {});
12
19
  }
13
20
  if (os === "win32") {
14
21
  return browser
15
- ? pi.exec("cmd", ["/c", "start", "", browser, target], { signal })
16
- : pi.exec("cmd", ["/c", "start", "", target], { signal });
22
+ ? pi.exec("cmd", ["/c", "start", "", browser, target], signal ? { signal } : {})
23
+ : pi.exec("cmd", ["/c", "start", "", target], signal ? { signal } : {});
17
24
  }
18
- return browser ? pi.exec(browser, [target], { signal }) : pi.exec("xdg-open", [target], { signal });
25
+ return browser
26
+ ? pi.exec(browser, [target], signal ? { signal } : {})
27
+ : pi.exec("xdg-open", [target], signal ? { signal } : {});
19
28
  }
20
29
 
21
30
  export async function openUrl(pi: ExtensionAPI, url: string, browser?: string, signal?: AbortSignal): Promise<void> {
@@ -38,12 +47,14 @@ export async function parallelLimit<T, R>(
38
47
  fn: (item: T) => Promise<R>
39
48
  ): Promise<R[]> {
40
49
  const results: R[] = [];
41
- let index = 0;
50
+ const iterator = items.entries();
42
51
 
43
52
  async function worker() {
44
- while (index < items.length) {
45
- const i = index++;
46
- results[i] = await fn(items[i]);
53
+ while (true) {
54
+ const next = iterator.next();
55
+ if (next.done) return;
56
+ const [index, item] = next.value;
57
+ results[index] = await fn(item);
47
58
  }
48
59
  }
49
60
 
@@ -106,6 +117,9 @@ const COMMAND_SECRET_TIMEOUT_MS = 10_000;
106
117
  const COMMAND_SECRET_MAX_OUTPUT_BYTES = 1024 * 1024;
107
118
 
108
119
  /** Resolve a secret value, executing only a single leading `!` command marker. */
120
+ export function resolveCommandSecret(value: string, context: string): string;
121
+ export function resolveCommandSecret(value: undefined, context: string): undefined;
122
+ export function resolveCommandSecret(value: string | undefined, context: string): string | undefined;
109
123
  export function resolveCommandSecret(value: string | undefined, context: string): string | undefined {
110
124
  if (value === undefined) return undefined;
111
125
  if (value.startsWith("!!")) return interpolateEnvVars(value.slice(1));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-extensions",
3
- "version": "2026.8.1",
3
+ "version": "2026.8.2",
4
4
  "description": "Aggregate installer for HerbertGao-maintained Pi extensions.",
5
5
  "keywords": [
6
6
  "extensions",
@@ -35,9 +35,12 @@
35
35
  "dependencies": {
36
36
  "@ast-grep/cli": "^0.45.0",
37
37
  "@ast-grep/napi": "^0.45.0",
38
- "@czottmann/pi-automode": "1.9.0",
38
+ "@czottmann/pi-automode": "1.11.0",
39
+ "@dietrichgebert/ponytail": "4.9.0",
39
40
  "@earendil-works/pi-tui": "^0.82.1",
40
- "@herbertgao/pi-cc-extensions": "0.8.51",
41
+ "@effect/platform-node": "4.0.0-beta.103",
42
+ "@effect/platform-node-shared": "4.0.0-beta.103",
43
+ "@herbertgao/pi-cc-extensions": "0.8.52",
41
44
  "@herbertgao/pi-copy-response": "0.2.6",
42
45
  "@herbertgao/pi-handoff": "1.1.2",
43
46
  "@herbertgao/pi-inline-skills": "1.0.5",
@@ -45,21 +48,26 @@
45
48
  "@herbertgao/pi-preferred-thinking": "0.3.0",
46
49
  "@herbertgao/pi-recap": "0.4.4",
47
50
  "@herbertgao/pi-rename": "0.4.2",
48
- "@herbertgao/pi-stash": "0.1.0",
49
- "@herbertgao/pi-subagents": "0.15.1",
51
+ "@herbertgao/pi-stash": "0.1.1",
52
+ "@herbertgao/pi-subagents": "0.15.2",
50
53
  "@herbertgao/pi-titlebar-spinner": "0.1.3",
51
54
  "@juicesharp/rpiv-ask-user-question": "2.4.0",
52
55
  "@juicesharp/rpiv-config": "^2.4.0",
56
+ "@modelcontextprotocol/client": "2.0.0",
57
+ "@modelcontextprotocol/core": "2.0.0",
53
58
  "@modelcontextprotocol/ext-apps": "^1.2.2",
54
- "@modelcontextprotocol/sdk": "^1.30.0",
55
59
  "@mozilla/readability": "^0.6.0",
56
60
  "@napi-rs/keyring": "^1.3.0",
61
+ "@narumitw/pi-btw": "0.50.0",
62
+ "@narumitw/pi-tui-kit": "^0.49.1",
63
+ "@pi-plugins/fast-mode": "0.1.9",
57
64
  "@shikijs/cli": "^4.0.2",
58
65
  "@sinclair/typebox": "^0.34.49",
59
66
  "ajv": "^8.17.1",
60
67
  "ajv-formats": "^3.0.1",
61
68
  "croner": "^10.0.1",
62
69
  "cross-spawn": "^7.0.6",
70
+ "effect": "4.0.0-beta.103",
63
71
  "grok-mermaid": "^0.2.2",
64
72
  "jiti": "^2.7.0",
65
73
  "js-yaml": "^5.2.2",
@@ -69,7 +77,7 @@
69
77
  "open": "^10.2.0",
70
78
  "p-limit": "^6.1.0",
71
79
  "pi-lens": "3.8.74",
72
- "pi-mcp-adapter": "2.19.0",
80
+ "pi-mcp-adapter": "2.21.2",
73
81
  "pi-web-access": "0.18.0",
74
82
  "pidusage": "^4.0.1",
75
83
  "promise.try": "^2.0.1",
@@ -87,6 +95,7 @@
87
95
  },
88
96
  "bundledDependencies": [
89
97
  "@czottmann/pi-automode",
98
+ "@dietrichgebert/ponytail",
90
99
  "@herbertgao/pi-cc-extensions",
91
100
  "@herbertgao/pi-copy-response",
92
101
  "@herbertgao/pi-handoff",
@@ -99,6 +108,8 @@
99
108
  "@herbertgao/pi-subagents",
100
109
  "@herbertgao/pi-titlebar-spinner",
101
110
  "@juicesharp/rpiv-ask-user-question",
111
+ "@narumitw/pi-btw",
112
+ "@pi-plugins/fast-mode",
102
113
  "pi-lens",
103
114
  "pi-mcp-adapter",
104
115
  "pi-web-access"
@@ -108,6 +119,7 @@
108
119
  },
109
120
  "pi": {
110
121
  "extensions": [
122
+ "./node_modules/@dietrichgebert/ponytail/pi-extension/index.js",
111
123
  "./node_modules/@herbertgao/pi-cc-extensions/extensions/index.ts",
112
124
  "./node_modules/@herbertgao/pi-copy-response/src/index.ts",
113
125
  "./node_modules/@herbertgao/pi-handoff/src/index.ts",
@@ -121,6 +133,8 @@
121
133
  "./node_modules/@herbertgao/pi-subagents/src/index.ts",
122
134
  "./node_modules/@herbertgao/pi-titlebar-spinner/src/index.ts",
123
135
  "./node_modules/@juicesharp/rpiv-ask-user-question/index.ts",
136
+ "./node_modules/@narumitw/pi-btw/src/index.ts",
137
+ "./node_modules/@pi-plugins/fast-mode/dist/index.mjs",
124
138
  "./node_modules/pi-mcp-adapter/index.ts",
125
139
  "./node_modules/pi-lens/dist/index.js",
126
140
  "./node_modules/pi-web-access/index.ts",
@@ -131,6 +145,7 @@
131
145
  "./node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json"
132
146
  ],
133
147
  "skills": [
148
+ "./node_modules/@dietrichgebert/ponytail/skills",
134
149
  "./node_modules/pi-mcp-adapter/skills",
135
150
  "./node_modules/pi-lens/skills"
136
151
  ]