@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
@@ -22,7 +22,16 @@ import { resolveConfiguredOAuthDir } from './config.ts';
22
22
  const require = createRequire(import.meta.url);
23
23
  const AUTH_SECRET_SERVICE = 'pi-mcp-adapter.oauth';
24
24
  const TEST_AUTH_STORE_ENV = 'PI_MCP_ADAPTER_TEST_AUTH_STORE';
25
- const AUTH_SECRET_CHUNK_SIZE = 1800;
25
+ /**
26
+ * Windows Credential Manager caps one value at CRED_MAX_CREDENTIAL_BLOB_SIZE
27
+ * (2560 bytes) and stores it as UTF-16, so the real ceiling is
28
+ * AUTH_SECRET_VALUE_LIMIT characters. Chunks must stay below that, and so must
29
+ * the threshold that decides whether to chunk at all, or oversized records still
30
+ * fail to persist on Windows.
31
+ */
32
+ const AUTH_SECRET_CHUNK_SIZE = 1000;
33
+ /** Largest single value the strictest supported credential store accepts. */
34
+ const AUTH_SECRET_VALUE_LIMIT = 1280;
26
35
  const KEYRING_RECOVERY_DISABLED_ENV = 'PI_MCP_ADAPTER_DISABLE_KEYRING_RECOVERY';
27
36
  const KEYRING_RECOVERY_KEYCTL_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_KEYCTL';
28
37
  const KEYRING_RECOVERY_NODE_ENV = 'PI_MCP_ADAPTER_KEYRING_RECOVERY_NODE';
@@ -163,6 +172,22 @@ const keyringAuthSecretStore: AuthSecretStore = {
163
172
  },
164
173
  };
165
174
 
175
+ /** Mimics the Windows Credential Manager per-value ceiling for tests. */
176
+ const sizeLimitedAuthSecretStore: AuthSecretStore = {
177
+ read(account) {
178
+ return memoryAuthEntries.get(account);
179
+ },
180
+ write(account, payload) {
181
+ if (payload.length > AUTH_SECRET_VALUE_LIMIT) {
182
+ throw new Error(`Value of 'password encoded as UTF-16' is longer than the platform limit of ${AUTH_SECRET_VALUE_LIMIT * 2} chars`);
183
+ }
184
+ memoryAuthEntries.set(account, payload);
185
+ },
186
+ remove(account) {
187
+ memoryAuthEntries.delete(account);
188
+ },
189
+ };
190
+
166
191
  const unavailableAuthSecretStore: AuthSecretStore = {
167
192
  read() {
168
193
  throw new Error('simulated secure credential store unavailable');
@@ -205,6 +230,7 @@ export function removeTestAuthSecretStoreEntry(account: string): void {
205
230
 
206
231
  function getAuthSecretStore(): AuthSecretStore {
207
232
  if (process.env[TEST_AUTH_STORE_ENV] === 'memory') return memoryAuthSecretStore;
233
+ if (process.env[TEST_AUTH_STORE_ENV] === 'sizelimited') return sizeLimitedAuthSecretStore;
208
234
  if (process.env[TEST_AUTH_STORE_ENV] === 'unavailable') return unavailableAuthSecretStore;
209
235
  if (process.env[TEST_AUTH_STORE_ENV] === 'keyrevoked') return keyRevokedAuthSecretStore;
210
236
  return keyringAuthSecretStore;
@@ -401,7 +427,99 @@ function parseJsonPayload(serverName: string, payload: string, source: string):
401
427
  }
402
428
 
403
429
  function parseAuthEntryPayload(serverName: string, payload: string, source: string): AuthEntry {
404
- return parseJsonPayload(serverName, payload, source) as AuthEntry;
430
+ const parsed = parseJsonPayload(serverName, payload, source);
431
+ const entry = toAuthEntry(parsed);
432
+ if (!entry) {
433
+ throw new Error(`Failed to parse OAuth credentials for ${serverName} from ${source}: invalid credential shape`);
434
+ }
435
+ return entry;
436
+ }
437
+
438
+ function toAuthEntry(value: unknown): AuthEntry | undefined {
439
+ const entry = toRecord(value);
440
+ if (!entry) return undefined;
441
+
442
+ const codeVerifier = optionalString(entry.codeVerifier);
443
+ const oauthState = optionalString(entry.oauthState);
444
+ const serverUrl = optionalString(entry.serverUrl);
445
+ if (codeVerifier === null || oauthState === null || serverUrl === null) return undefined;
446
+
447
+ const tokens = entry.tokens === undefined ? undefined : toStoredTokens(entry.tokens);
448
+ const clientInfo = entry.clientInfo === undefined ? undefined : toStoredClientInfo(entry.clientInfo);
449
+ if ((entry.tokens !== undefined && !tokens) || (entry.clientInfo !== undefined && !clientInfo)) return undefined;
450
+
451
+ const authEntry: AuthEntry = {};
452
+ if (tokens) authEntry.tokens = tokens;
453
+ if (clientInfo) authEntry.clientInfo = clientInfo;
454
+ if (codeVerifier !== undefined) authEntry.codeVerifier = codeVerifier;
455
+ if (oauthState !== undefined) authEntry.oauthState = oauthState;
456
+ if (serverUrl !== undefined) authEntry.serverUrl = serverUrl;
457
+ return authEntry;
458
+ }
459
+
460
+ function toStoredTokens(value: unknown): StoredTokens | undefined {
461
+ const tokens = toRecord(value);
462
+ if (!tokens || typeof tokens.accessToken !== 'string') return undefined;
463
+
464
+ const refreshToken = optionalString(tokens.refreshToken);
465
+ const scope = optionalString(tokens.scope);
466
+ const issuer = optionalString(tokens.issuer);
467
+ const expiresAt = optionalNumber(tokens.expiresAt);
468
+ if (refreshToken === null || scope === null || issuer === null || expiresAt === null) return undefined;
469
+
470
+ const storedTokens: StoredTokens = { accessToken: tokens.accessToken };
471
+ if (refreshToken !== undefined) storedTokens.refreshToken = refreshToken;
472
+ if (expiresAt !== undefined) storedTokens.expiresAt = expiresAt;
473
+ if (scope !== undefined) storedTokens.scope = scope;
474
+ if (issuer !== undefined) storedTokens.issuer = issuer;
475
+ return storedTokens;
476
+ }
477
+
478
+ function toStoredClientInfo(value: unknown): StoredClientInfo | undefined {
479
+ const clientInfo = toRecord(value);
480
+ if (!clientInfo || typeof clientInfo.clientId !== 'string') return undefined;
481
+
482
+ const clientSecret = optionalString(clientInfo.clientSecret);
483
+ const issuer = optionalString(clientInfo.issuer);
484
+ const clientIdIssuedAt = optionalNumber(clientInfo.clientIdIssuedAt);
485
+ const clientSecretExpiresAt = optionalNumber(clientInfo.clientSecretExpiresAt);
486
+ const configPreRegistered = optionalBoolean(clientInfo.configPreRegistered);
487
+ if (clientSecret === null || issuer === null || clientIdIssuedAt === null || clientSecretExpiresAt === null || configPreRegistered === null) return undefined;
488
+
489
+ const storedClient: StoredClientInfo = { clientId: clientInfo.clientId };
490
+ const redirectUris = stringArray(clientInfo.redirectUris);
491
+ if (clientSecret !== undefined) storedClient.clientSecret = clientSecret;
492
+ if (clientIdIssuedAt !== undefined) storedClient.clientIdIssuedAt = clientIdIssuedAt;
493
+ if (clientSecretExpiresAt !== undefined) storedClient.clientSecretExpiresAt = clientSecretExpiresAt;
494
+ if (redirectUris !== undefined) storedClient.redirectUris = redirectUris;
495
+ if (issuer !== undefined) storedClient.issuer = issuer;
496
+ if (configPreRegistered !== undefined) storedClient.configPreRegistered = configPreRegistered;
497
+ return storedClient;
498
+ }
499
+
500
+ function toRecord(value: unknown): Record<string, unknown> | undefined {
501
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
502
+ ? value as Record<string, unknown>
503
+ : undefined;
504
+ }
505
+
506
+ function optionalString(value: unknown): string | null | undefined {
507
+ if (value === undefined) return undefined;
508
+ return typeof value === 'string' ? value : null;
509
+ }
510
+
511
+ function optionalNumber(value: unknown): number | null | undefined {
512
+ if (value === undefined) return undefined;
513
+ return typeof value === 'number' ? value : null;
514
+ }
515
+
516
+ function optionalBoolean(value: unknown): boolean | null | undefined {
517
+ if (value === undefined) return undefined;
518
+ return typeof value === 'boolean' ? value : null;
519
+ }
520
+
521
+ function stringArray(value: unknown): string[] | undefined {
522
+ return Array.isArray(value) && value.every(uri => typeof uri === 'string') ? value : undefined;
405
523
  }
406
524
 
407
525
  function isAuthEntryChunkManifest(value: unknown): value is AuthEntryChunkManifest {
@@ -6,7 +6,7 @@ import { executeCall } from "./proxy-modes.ts";
6
6
  import { combineAbortSignals } from "./runtime-owner.ts";
7
7
  import { paginate, rankSuggestions, rankToolMatches } from "./search-ranking.ts";
8
8
  import type { McpExtensionState } from "./state.ts";
9
- import { findToolByName } from "./tool-metadata.ts";
9
+ import { findToolByName, formatSchema } from "./tool-metadata.ts";
10
10
  import { renderTsShape } from "./ts-shape.ts";
11
11
  import type { ContentBlock } from "./types.ts";
12
12
 
@@ -14,7 +14,7 @@ export const DEFAULT_MCP_SCRIPT_TIMEOUT_MS = 30_000;
14
14
 
15
15
  class McpScriptTimeoutError extends Error {
16
16
  constructor(timeoutMs: number) {
17
- super(`mcp_script timed out after ${timeoutMs}ms`);
17
+ super(`mcpScript timed out after ${timeoutMs}ms`);
18
18
  this.name = "McpScriptTimeoutError";
19
19
  }
20
20
  }
@@ -139,7 +139,7 @@ export async function runMcpScript(
139
139
  // Record before dispatch so calls still in flight at timeout/abort appear in the trace.
140
140
  const startedAt = Date.now();
141
141
  const index = calls.push({ operation: "call", path, ok: false, error: "incomplete", durationMs: 0, startedAt }) - 1;
142
- const result = await executeCall(state, path, args, undefined, getPiTools, callSignal);
142
+ const result = await executeCall(state, path, args, undefined, getPiTools, callSignal, "script");
143
143
  const details = result.details;
144
144
  if (details.error !== undefined) {
145
145
  const errorCode = String(details.error);
@@ -147,7 +147,7 @@ export async function runMcpScript(
147
147
  ? details.suggestions.filter((suggestion): suggestion is string => typeof suggestion === "string")
148
148
  : [];
149
149
  const message = errorCode === "tool_not_found"
150
- ? `Tool "${path}" not found. Use await tools.search({ query: "..." }) inside mcp_script.${suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : ""}`
150
+ ? `Tool "${path}" not found. Use await tools.search({ query: "..." }) inside mcpScript.${suggestions.length > 0 ? ` Did you mean: ${suggestions.join(", ")}` : ""}`
151
151
  : typeof details.message === "string"
152
152
  ? details.message
153
153
  : textFromContent(result.content);
@@ -172,9 +172,9 @@ export async function runMcpScript(
172
172
  if (query.trim() === "") {
173
173
  return { items: [], total: 0, hasMore: false, nextOffset: null };
174
174
  }
175
- const server = typeof input.server === "string" ? input.server : undefined;
176
- const limit = typeof input.limit === "number" ? input.limit : 12;
177
- const offset = typeof input.offset === "number" ? input.offset : 0;
175
+ const server = typeof input?.server === "string" ? input.server : undefined;
176
+ const limit = typeof input?.limit === "number" ? input.limit : 12;
177
+ const offset = typeof input?.offset === "number" ? input.offset : 0;
178
178
  const page = paginate(rankToolMatches(state, query, server), offset, limit);
179
179
  return {
180
180
  ...page,
@@ -204,7 +204,9 @@ export async function runMcpScript(
204
204
  for (const [server, metadata] of state.toolMetadata) {
205
205
  const tool = findToolByName(metadata, path);
206
206
  if (!tool) continue;
207
- const inputTypeScript = tool.inputSchema ? renderTsShape(tool.inputSchema) : null;
207
+ const inputTypeScript = tool.inputSchema
208
+ ? renderTsShape(tool.inputSchema) ?? formatSchema(tool.inputSchema)
209
+ : null;
208
210
  return {
209
211
  path: tool.name,
210
212
  name: tool.originalName,
@@ -285,7 +287,7 @@ export async function runMcpScript(
285
287
  });
286
288
  activeWorker.once("error", reject);
287
289
  activeWorker.once("exit", (code) => {
288
- if (!completed && code !== 0) reject(new Error(`mcp_script worker exited with code ${code}`));
290
+ if (!completed && code !== 0) reject(new Error(`mcpScript worker exited with code ${code}`));
289
291
  });
290
292
  });
291
293
  const timeoutError = new McpScriptTimeoutError(resolvedTimeoutMs);
@@ -313,7 +315,7 @@ export async function runMcpScript(
313
315
  } catch (error) {
314
316
  if (error instanceof McpScriptTimeoutError) {
315
317
  errorCode = "timeout";
316
- errorMessage = `mcp_script timed out after ${resolvedTimeoutMs}ms`;
318
+ errorMessage = `mcpScript timed out after ${resolvedTimeoutMs}ms`;
317
319
  } else if (externalSignal?.aborted) {
318
320
  errorCode = "aborted";
319
321
  errorMessage = error instanceof Error ? error.message : String(error);
@@ -330,7 +332,7 @@ export async function runMcpScript(
330
332
  callsSnapshot ??= snapshotCalls();
331
333
  // A script may finish without awaiting every call; abort leftovers so
332
334
  // parent-side dispatches do not outlive the script.
333
- timeoutController.abort(new Error("mcp_script finished"));
335
+ timeoutController.abort(new Error("mcpScript finished"));
334
336
  await worker?.terminate();
335
337
  }
336
338
 
@@ -10,12 +10,12 @@ import {
10
10
  type AddClientAuthentication,
11
11
  type OAuthClientProvider,
12
12
  type OAuthDiscoveryState,
13
- } from "@modelcontextprotocol/sdk/client/auth.js"
13
+ } from "@modelcontextprotocol/client"
14
14
  import type {
15
15
  OAuthClientInformationMixed,
16
16
  OAuthClientMetadata,
17
17
  OAuthTokens,
18
- } from "@modelcontextprotocol/sdk/shared/auth.js"
18
+ } from "@modelcontextprotocol/client"
19
19
  import {
20
20
  getAuthForUrl,
21
21
  updateTokens,
@@ -86,6 +86,7 @@ export interface McpOAuthConfig {
86
86
  redirectUri?: string
87
87
  clientName?: string
88
88
  clientUri?: string
89
+ skipIssuerMetadataValidation?: boolean
89
90
  }
90
91
 
91
92
  const reservedAuthorizationParams = new Set([
@@ -288,7 +289,7 @@ export class McpOAuthProvider implements OAuthClientProvider {
288
289
  updateClientInfo(this.serverName, clientInfo, this.serverUrl, this.storageOptions)
289
290
  }
290
291
  // Return all registration metadata and the local issuer extension.
291
- // This keeps the SDK v1 view and the stored issuer binding consistent.
292
+ // This keeps the SDK OAuth view and the stored issuer binding consistent.
292
293
  return {
293
294
  client_id: clientInfo.clientId,
294
295
  client_secret: clientInfo.clientSecret,
@@ -318,7 +319,11 @@ export class McpOAuthProvider implements OAuthClientProvider {
318
319
  if (this.config.clientId && info.client_id === this.config.clientId) {
319
320
  updateClientInfo(
320
321
  this.serverName,
321
- { clientId: info.client_id, issuer, configPreRegistered: true },
322
+ {
323
+ clientId: info.client_id,
324
+ ...(issuer !== undefined ? { issuer } : {}),
325
+ configPreRegistered: true,
326
+ },
322
327
  this.serverUrl,
323
328
  this.storageOptions,
324
329
  )
@@ -329,11 +334,11 @@ export class McpOAuthProvider implements OAuthClientProvider {
329
334
  ?? (this.redirectUrl ? [this.redirectUrl] : undefined)
330
335
  const clientInfo: StoredClientInfo = {
331
336
  clientId: info.client_id,
332
- clientSecret: info.client_secret,
333
- clientIdIssuedAt: info.client_id_issued_at,
334
- clientSecretExpiresAt: info.client_secret_expires_at,
335
- redirectUris,
336
- issuer,
337
+ ...(info.client_secret !== undefined ? { clientSecret: info.client_secret } : {}),
338
+ ...(info.client_id_issued_at !== undefined ? { clientIdIssuedAt: info.client_id_issued_at } : {}),
339
+ ...(info.client_secret_expires_at !== undefined ? { clientSecretExpiresAt: info.client_secret_expires_at } : {}),
340
+ ...(redirectUris !== undefined ? { redirectUris } : {}),
341
+ ...(issuer !== undefined ? { issuer } : {}),
337
342
  }
338
343
  this.flowClientInfo = clientInfo
339
344
  updateClientInfo(this.serverName, clientInfo, this.serverUrl, this.storageOptions)
@@ -370,15 +375,16 @@ export class McpOAuthProvider implements OAuthClientProvider {
370
375
  * Save OAuth tokens.
371
376
  */
372
377
  async saveTokens(tokens: OAuthTokens): Promise<void> {
378
+ const issuer = this.discoveredIssuer ?? (tokens as IssuerBoundTokens).issuer
373
379
  const storedTokens: StoredTokens = {
374
380
  accessToken: tokens.access_token,
375
- refreshToken: tokens.refresh_token,
381
+ ...(tokens.refresh_token !== undefined ? { refreshToken: tokens.refresh_token } : {}),
376
382
  // Preserve expiry even when expires_in is 0 (e.g. the SDK re-saving an
377
383
  // already-expired token) so expired tokens stay expired instead of
378
384
  // being persisted as never-expiring.
379
- expiresAt: tokens.expires_in !== undefined ? Date.now() / 1000 + tokens.expires_in : undefined,
380
- scope: tokens.scope,
381
- issuer: this.discoveredIssuer ?? (tokens as IssuerBoundTokens).issuer,
385
+ ...(tokens.expires_in !== undefined ? { expiresAt: Date.now() / 1000 + tokens.expires_in } : {}),
386
+ ...(tokens.scope !== undefined ? { scope: tokens.scope } : {}),
387
+ ...(issuer !== undefined ? { issuer } : {}),
382
388
  }
383
389
  this.throwIfInactive()
384
390
  updateTokens(this.serverName, storedTokens, this.serverUrl, this.storageOptions)
@@ -107,7 +107,7 @@ export async function guardMcpOutput(
107
107
  if (options.enabled === false) {
108
108
  return {
109
109
  content: addAffixes(normalizedContent, prefix, suffix),
110
- mcpResult: options.rawMcpResult,
110
+ ...(options.rawMcpResult !== undefined ? { mcpResult: options.rawMcpResult } : {}),
111
111
  };
112
112
  }
113
113
 
@@ -138,8 +138,8 @@ export async function guardMcpOutput(
138
138
  originalLines: stats.lines,
139
139
  returnedLines: finalStats.lines,
140
140
  ...(imageBlocks.length > 0 ? { imageBlocksPassedThrough: imageBlocks.length } : {}),
141
- fullOutputPath,
142
- writeError,
141
+ ...(fullOutputPath !== undefined ? { fullOutputPath } : {}),
142
+ ...(writeError !== undefined ? { writeError } : {}),
143
143
  };
144
144
  }
145
145
 
@@ -147,7 +147,11 @@ export async function guardMcpOutput(
147
147
  ? undefined
148
148
  : await boundMcpResult(options.rawMcpResult, detailsMaxBytes);
149
149
 
150
- return { content: guardedContent, outputGuard, mcpResult };
150
+ return {
151
+ content: guardedContent,
152
+ ...(outputGuard ? { outputGuard } : {}),
153
+ ...(mcpResult !== undefined ? { mcpResult } : {}),
154
+ };
151
155
  }
152
156
 
153
157
  function sanitizeContent(content: ContentBlock[]): ContentBlock[] {
@@ -177,7 +181,7 @@ function addAffixes(content: ContentBlock[], prefix: string, suffix: string): Co
177
181
  if (prefix) {
178
182
  const index = next.findIndex((block) => block.type === "text");
179
183
  const block = next[index];
180
- if (index >= 0 && block.type === "text") {
184
+ if (block?.type === "text") {
181
185
  next[index] = { ...block, text: `${prefix}${block.text}` };
182
186
  } else {
183
187
  next.unshift({ type: "text", text: prefix });
@@ -187,13 +191,13 @@ function addAffixes(content: ContentBlock[], prefix: string, suffix: string): Co
187
191
  if (suffix) {
188
192
  let index = -1;
189
193
  for (let i = next.length - 1; i >= 0; i--) {
190
- if (next[i].type === "text") {
194
+ if (next[i]?.type === "text") {
191
195
  index = i;
192
196
  break;
193
197
  }
194
198
  }
195
199
  const block = next[index];
196
- if (index >= 0 && block.type === "text") {
200
+ if (block?.type === "text") {
197
201
  next[index] = { ...block, text: `${block.text}${suffix}` };
198
202
  } else {
199
203
  next.push({ type: "text", text: suffix });
@@ -239,8 +243,8 @@ function truncateHead(text: string, maxBytes: number, maxLines: number): { conte
239
243
  function truncateStringToBytes(value: string, maxBytes: number): string {
240
244
  if (byteLength(value) <= maxBytes) return value;
241
245
  const buffer = Buffer.from(value, "utf8");
242
- let end = Math.max(0, maxBytes);
243
- while (end > 0 && (buffer[end] & 0xc0) === 0x80) end--;
246
+ let end = Number.isFinite(maxBytes) ? Math.max(0, Math.floor(maxBytes)) : 0;
247
+ while (end > 0 && (buffer.readUInt8(end) & 0xc0) === 0x80) end--;
244
248
  return buffer.subarray(0, end).toString("utf8");
245
249
  }
246
250
 
@@ -280,8 +284,8 @@ async function summarizeMcpResult(result: unknown, raw: string, rawBytes: number
280
284
  contentBlocks: content.length,
281
285
  contentSummary: summarizeContent(content),
282
286
  rawResultBytes: rawBytes,
283
- fullResultPath,
284
- resultWriteError,
287
+ ...(fullResultPath !== undefined ? { fullResultPath } : {}),
288
+ ...(resultWriteError !== undefined ? { resultWriteError } : {}),
285
289
  };
286
290
 
287
291
  if (record && "structuredContent" in record) {
@@ -52,6 +52,7 @@ function rainbowProgress(filled: number, total: number): string {
52
52
  const dots: string[] = [];
53
53
  for (let i = 0; i < total; i++) {
54
54
  const color = RAINBOW_COLORS[i % RAINBOW_COLORS.length];
55
+ if (!color) continue;
55
56
  dots.push(fg(color, i < filled ? "●" : "○"));
56
57
  }
57
58
  return dots.join(" ");
@@ -220,7 +221,10 @@ class McpPanel {
220
221
  }
221
222
 
222
223
  const isDirect = toolFilter === true || (Array.isArray(toolFilter) && toolFilter.includes(baseName));
223
- const ct: CachedTool = { name: baseName, description: resource.description };
224
+ const ct: CachedTool = {
225
+ name: baseName,
226
+ ...(resource.description !== undefined ? { description: resource.description } : {}),
227
+ };
224
228
  tools.push({
225
229
  name: baseName,
226
230
  description: resource.description ?? `Read resource: ${resource.uri}`,
@@ -239,9 +243,9 @@ class McpPanel {
239
243
  name: serverName,
240
244
  expanded: false,
241
245
  source: prov?.kind ?? "user",
242
- importKind: prov?.importKind,
243
- includeTools: definition.includeTools,
244
- excludeTools: definition.excludeTools,
246
+ ...(prov?.importKind !== undefined ? { importKind: prov.importKind } : {}),
247
+ ...(definition.includeTools !== undefined ? { includeTools: definition.includeTools } : {}),
248
+ ...(definition.excludeTools !== undefined ? { excludeTools: definition.excludeTools } : {}),
245
249
  exposeResources: definition.exposeResources !== false,
246
250
  connectionStatus: status,
247
251
  failureMessage,
@@ -276,6 +280,7 @@ class McpPanel {
276
280
  this.visibleItems = [];
277
281
  for (let si = 0; si < this.servers.length; si++) {
278
282
  const server = this.servers[si];
283
+ if (!server) continue;
279
284
  if (query && this.authOnly) {
280
285
  const score = mode === "name" ? fuzzyScore(query, server.name) : 0;
281
286
  if (score > 0) {
@@ -288,6 +293,7 @@ class McpPanel {
288
293
  if (server.expanded || query) {
289
294
  for (let ti = 0; ti < server.tools.length; ti++) {
290
295
  const tool = server.tools[ti];
296
+ if (!tool) continue;
291
297
  if (query) {
292
298
  const score = mode === "name"
293
299
  ? Math.max(
@@ -352,7 +358,7 @@ class McpPanel {
352
358
  return;
353
359
  }
354
360
 
355
- if (matchesKey(data, "ctrl+s")) {
361
+ if (this.keys.save(data)) {
356
362
  this.cleanup();
357
363
  this.done(this.buildResult());
358
364
  return;
@@ -422,6 +428,7 @@ class McpPanel {
422
428
  const item = this.visibleItems[this.cursorIndex];
423
429
  if (!item) return;
424
430
  const server = this.servers[item.serverIndex];
431
+ if (!server) return;
425
432
  if (item.type === "server") {
426
433
  if (server.connectionStatus === "disabled") return;
427
434
  if (this.authOnly || server.connectionStatus === "needs-auth") {
@@ -433,6 +440,7 @@ class McpPanel {
433
440
  this.cursorIndex = Math.min(this.cursorIndex, Math.max(0, this.visibleItems.length - 1));
434
441
  } else if (item.toolIndex !== undefined) {
435
442
  const tool = server.tools[item.toolIndex];
443
+ if (!tool) return;
436
444
  tool.isDirect = !tool.isDirect;
437
445
  if (tool.isDirect && server.source === "import") {
438
446
  this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} — will copy to user config on save`;
@@ -451,7 +459,8 @@ class McpPanel {
451
459
  if (matchesKey(data, "ctrl+r")) {
452
460
  const item = this.visibleItems[this.cursorIndex];
453
461
  if (!item) return;
454
- this.reconnectServer(this.servers[item.serverIndex]);
462
+ const server = this.servers[item.serverIndex];
463
+ if (server) this.reconnectServer(server);
455
464
  return;
456
465
  }
457
466
 
@@ -459,7 +468,7 @@ class McpPanel {
459
468
  const item = this.visibleItems[this.cursorIndex];
460
469
  if (!item) return;
461
470
  const server = this.servers[item.serverIndex];
462
- if (server.connectionStatus !== "failed" || !server.failureMessage) return;
471
+ if (!server || server.connectionStatus !== "failed" || !server.failureMessage) return;
463
472
  const serverName = sanitizeDisplayText(server.name);
464
473
  const failureMessage = sanitizeDisplayText(server.failureMessage);
465
474
  copyToClipboard(failureMessage).then(() => {
@@ -502,7 +511,8 @@ class McpPanel {
502
511
  }
503
512
 
504
513
  private authenticateSelectedServer(item: VisibleItem): void {
505
- this.authenticateServer(this.servers[item.serverIndex]);
514
+ const server = this.servers[item.serverIndex];
515
+ if (server) this.authenticateServer(server);
506
516
  }
507
517
 
508
518
  private authenticateServer(server: ServerState): void {
@@ -574,6 +584,7 @@ class McpPanel {
574
584
  private toggleItem(item: VisibleItem): void {
575
585
  if (this.authOnly) return;
576
586
  const server = this.servers[item.serverIndex];
587
+ if (!server) return;
577
588
  if (item.type === "server") {
578
589
  const newState = !server.tools.every((t) => t.isDirect);
579
590
  if (server.source === "import" && newState) {
@@ -582,6 +593,7 @@ class McpPanel {
582
593
  for (const t of server.tools) t.isDirect = newState;
583
594
  } else if (item.toolIndex !== undefined) {
584
595
  const tool = server.tools[item.toolIndex];
596
+ if (!tool) return;
585
597
  tool.isDirect = !tool.isDirect;
586
598
  if (tool.isDirect && server.source === "import") {
587
599
  this.importNotice = `Imported from ${sanitizeDisplayText(server.importKind ?? "external")} — will copy to user config on save`;
@@ -654,7 +666,10 @@ class McpPanel {
654
666
 
655
667
  const prev = existingState.get(baseName);
656
668
  const isDirect = prev !== undefined ? prev : false;
657
- const ct: CachedTool = { name: baseName, description: resource.description };
669
+ const ct: CachedTool = {
670
+ name: baseName,
671
+ ...(resource.description !== undefined ? { description: resource.description } : {}),
672
+ };
658
673
  newTools.push({
659
674
  name: baseName,
660
675
  description: resource.description ?? `Read resource: ${resource.uri}`,
@@ -724,8 +739,10 @@ class McpPanel {
724
739
 
725
740
  for (let i = startIdx; i < endIdx; i++) {
726
741
  const item = this.visibleItems[i];
742
+ if (!item) continue;
727
743
  const isCursor = i === this.cursorIndex;
728
744
  const server = this.servers[item.serverIndex];
745
+ if (!server) continue;
729
746
 
730
747
  if (item.type === "server") {
731
748
  lines.push(row(this.renderServerRow(server, isCursor)));
@@ -735,7 +752,8 @@ class McpPanel {
735
752
  }
736
753
  }
737
754
  } else if (item.toolIndex !== undefined) {
738
- lines.push(row(this.renderToolRow(server.tools[item.toolIndex], isCursor, innerW)));
755
+ const tool = server.tools[item.toolIndex];
756
+ if (tool) lines.push(row(this.renderToolRow(tool, isCursor, innerW)));
739
757
  }
740
758
  }
741
759
 
@@ -784,6 +802,7 @@ class McpPanel {
784
802
  }
785
803
 
786
804
  lines.push(emptyRow());
805
+ const saveLabel = this.keys.saveLabel();
787
806
  const hints = this.authOnly
788
807
  ? [
789
808
  italic("↑↓") + " navigate",
@@ -800,7 +819,7 @@ class McpPanel {
800
819
  italic("ctrl+r") + " reconnect",
801
820
  ...(this.selectedServerHasFailureMessage() ? [italic("ctrl+y") + " copy error"] : []),
802
821
  italic("?") + " desc search",
803
- italic("ctrl+s") + " save",
822
+ ...(saveLabel ? [italic(saveLabel) + " save"] : []),
804
823
  italic("esc") + " clear/close",
805
824
  italic("ctrl+c") + " quit",
806
825
  ];
@@ -871,7 +890,7 @@ class McpPanel {
871
890
  const item = this.visibleItems[this.cursorIndex];
872
891
  if (!item) return false;
873
892
  const server = this.servers[item.serverIndex];
874
- return server.connectionStatus === "failed" && !!server.failureMessage;
893
+ return server?.connectionStatus === "failed" && !!server.failureMessage;
875
894
  }
876
895
 
877
896
  private wrapText(text: string, width: number): string[] {
@@ -884,11 +903,11 @@ class McpPanel {
884
903
  while (visibleWidth(rest) > max) {
885
904
  let take = "";
886
905
  let index = 0;
887
- while (index < rest.length && visibleWidth(take + rest[index]) <= max) {
888
- take += rest[index];
906
+ while (index < rest.length && visibleWidth(take + rest.charAt(index)) <= max) {
907
+ take += rest.charAt(index);
889
908
  index++;
890
909
  }
891
- if (!take) take = rest[0];
910
+ if (!take) take = rest.charAt(0);
892
911
  lines.push(take);
893
912
  rest = rest.slice(take.length);
894
913
  }