@herbertgao/pi-extensions 2026.9.11 → 2026.9.13

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 (192) hide show
  1. package/README.md +4 -4
  2. package/THIRD_PARTY_NOTICES.md +1 -1
  3. package/node_modules/@herbertgao/pi-cc-extensions/package.json +1 -1
  4. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +8 -0
  5. package/node_modules/@herbertgao/pi-subagents/package.json +1 -1
  6. package/node_modules/@herbertgao/pi-subagents/src/agent-manager.ts +5 -4
  7. package/node_modules/@herbertgao/pi-subagents/src/mention-clone.ts +39 -16
  8. package/node_modules/@juicesharp/rpiv-ask-user-question/package.json +2 -2
  9. package/node_modules/@narumitw/pi-btw/README.md +21 -4
  10. package/node_modules/@narumitw/pi-btw/dist/index.ts +1668 -958
  11. package/node_modules/@narumitw/pi-btw/dist/index.ts.map +4 -4
  12. package/node_modules/@narumitw/pi-btw/docs/workflows.md +10 -2
  13. package/node_modules/@narumitw/pi-btw/package.json +1 -1
  14. package/node_modules/@narumitw/pi-btw/src/btw.ts +17 -79
  15. package/node_modules/@narumitw/pi-btw/src/conversation-context.ts +74 -0
  16. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +196 -7
  17. package/node_modules/@narumitw/pi-btw/src/main-thread-updates.ts +40 -0
  18. package/node_modules/@narumitw/pi-btw/src/menu.ts +34 -2
  19. package/node_modules/@narumitw/pi-btw/src/settings.ts +49 -0
  20. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +9 -1
  21. package/node_modules/@narumitw/pi-btw/src/workspace-layout.ts +559 -0
  22. package/node_modules/pi-mcp-adapter/CHANGELOG.md +62 -0
  23. package/node_modules/pi-mcp-adapter/README.md +75 -7
  24. package/node_modules/pi-mcp-adapter/bearer-command-resolver.ts +193 -0
  25. package/node_modules/pi-mcp-adapter/cli.js +56 -5
  26. package/node_modules/pi-mcp-adapter/commands.ts +96 -5
  27. package/node_modules/pi-mcp-adapter/config.ts +73 -6
  28. package/node_modules/pi-mcp-adapter/direct-tool-surface.ts +22 -28
  29. package/node_modules/pi-mcp-adapter/direct-tools.ts +13 -5
  30. package/node_modules/pi-mcp-adapter/dist/abort.d.ts +2 -0
  31. package/node_modules/pi-mcp-adapter/dist/abort.js +36 -0
  32. package/node_modules/pi-mcp-adapter/dist/abort.js.map +1 -0
  33. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.d.ts +6 -0
  34. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js +194 -0
  35. package/node_modules/pi-mcp-adapter/dist/bearer-command-resolver.js.map +1 -0
  36. package/node_modules/pi-mcp-adapter/dist/config.d.ts +5 -0
  37. package/node_modules/pi-mcp-adapter/dist/config.js +75 -6
  38. package/node_modules/pi-mcp-adapter/dist/config.js.map +1 -1
  39. package/node_modules/pi-mcp-adapter/dist/consent-manager.d.ts +18 -0
  40. package/node_modules/pi-mcp-adapter/dist/consent-manager.js +88 -0
  41. package/node_modules/pi-mcp-adapter/dist/consent-manager.js.map +1 -0
  42. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.d.ts +17 -0
  43. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js +316 -0
  44. package/node_modules/pi-mcp-adapter/dist/elicitation-handler.js.map +1 -0
  45. package/node_modules/pi-mcp-adapter/dist/errors.d.ts +131 -0
  46. package/node_modules/pi-mcp-adapter/dist/errors.js +278 -0
  47. package/node_modules/pi-mcp-adapter/dist/errors.js.map +1 -0
  48. package/node_modules/pi-mcp-adapter/dist/http-ca.d.ts +8 -0
  49. package/node_modules/pi-mcp-adapter/dist/http-ca.js +88 -0
  50. package/node_modules/pi-mcp-adapter/dist/http-ca.js.map +1 -0
  51. package/node_modules/pi-mcp-adapter/dist/jev-client.d.ts +15 -0
  52. package/node_modules/pi-mcp-adapter/dist/jev-client.js +340 -0
  53. package/node_modules/pi-mcp-adapter/dist/jev-client.js.map +1 -0
  54. package/node_modules/pi-mcp-adapter/dist/jev-contracts.d.ts +77 -0
  55. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js +2 -0
  56. package/node_modules/pi-mcp-adapter/dist/jev-contracts.js.map +1 -0
  57. package/node_modules/pi-mcp-adapter/dist/jev-key-store.d.ts +22 -0
  58. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js +85 -0
  59. package/node_modules/pi-mcp-adapter/dist/jev-key-store.js.map +1 -0
  60. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.d.ts +2 -0
  61. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js +55 -0
  62. package/node_modules/pi-mcp-adapter/dist/json-schema-validator.js.map +1 -0
  63. package/node_modules/pi-mcp-adapter/dist/lifecycle.d.ts +56 -0
  64. package/node_modules/pi-mcp-adapter/dist/lifecycle.js +410 -0
  65. package/node_modules/pi-mcp-adapter/dist/lifecycle.js.map +1 -0
  66. package/node_modules/pi-mcp-adapter/dist/logger.d.ts +51 -0
  67. package/node_modules/pi-mcp-adapter/dist/logger.js +131 -0
  68. package/node_modules/pi-mcp-adapter/dist/logger.js.map +1 -0
  69. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.d.ts +26 -0
  70. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js +103 -0
  71. package/node_modules/pi-mcp-adapter/dist/mcp-auth-fetch.js.map +1 -0
  72. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.d.ts +118 -0
  73. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js +1098 -0
  74. package/node_modules/pi-mcp-adapter/dist/mcp-auth-flow.js.map +1 -0
  75. package/node_modules/pi-mcp-adapter/dist/mcp-auth.d.ts +168 -0
  76. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js +1117 -0
  77. package/node_modules/pi-mcp-adapter/dist/mcp-auth.js.map +1 -0
  78. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js +7 -140
  79. package/node_modules/pi-mcp-adapter/dist/mcp-bearer-store.js.map +1 -1
  80. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.d.ts +53 -0
  81. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js +441 -0
  82. package/node_modules/pi-mcp-adapter/dist/mcp-callback-server.js.map +1 -0
  83. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.d.ts +148 -0
  84. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js +689 -0
  85. package/node_modules/pi-mcp-adapter/dist/mcp-oauth-provider.js.map +1 -0
  86. package/node_modules/pi-mcp-adapter/dist/mcp-probe.d.ts +6 -0
  87. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js +166 -0
  88. package/node_modules/pi-mcp-adapter/dist/mcp-probe.js.map +1 -0
  89. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.d.ts +102 -0
  90. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js +369 -0
  91. package/node_modules/pi-mcp-adapter/dist/mcp-tasks.js.map +1 -0
  92. package/node_modules/pi-mcp-adapter/dist/mcp-trace.d.ts +95 -0
  93. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js +242 -0
  94. package/node_modules/pi-mcp-adapter/dist/mcp-trace.js.map +1 -0
  95. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js +2 -11
  96. package/node_modules/pi-mcp-adapter/dist/metadata-cache.js.map +1 -1
  97. package/node_modules/pi-mcp-adapter/dist/npx-resolver.d.ts +6 -0
  98. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js +505 -0
  99. package/node_modules/pi-mcp-adapter/dist/npx-resolver.js.map +1 -0
  100. package/node_modules/pi-mcp-adapter/dist/request-headers-command.d.ts +10 -0
  101. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js +312 -0
  102. package/node_modules/pi-mcp-adapter/dist/request-headers-command.js.map +1 -0
  103. package/node_modules/pi-mcp-adapter/dist/runtime-owner.d.ts +13 -0
  104. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js +97 -0
  105. package/node_modules/pi-mcp-adapter/dist/runtime-owner.js.map +1 -0
  106. package/node_modules/pi-mcp-adapter/dist/sampling-handler.d.ts +17 -0
  107. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js +203 -0
  108. package/node_modules/pi-mcp-adapter/dist/sampling-handler.js.map +1 -0
  109. package/node_modules/pi-mcp-adapter/dist/secure-keyring.d.ts +11 -0
  110. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js +172 -0
  111. package/node_modules/pi-mcp-adapter/dist/secure-keyring.js.map +1 -0
  112. package/node_modules/pi-mcp-adapter/dist/server-manager.d.ts +157 -0
  113. package/node_modules/pi-mcp-adapter/dist/server-manager.js +1743 -0
  114. package/node_modules/pi-mcp-adapter/dist/server-manager.js.map +1 -0
  115. package/node_modules/pi-mcp-adapter/dist/session-approvals.d.ts +34 -0
  116. package/node_modules/pi-mcp-adapter/dist/session-approvals.js +140 -0
  117. package/node_modules/pi-mcp-adapter/dist/session-approvals.js.map +1 -0
  118. package/node_modules/pi-mcp-adapter/dist/session-recovery.d.ts +26 -0
  119. package/node_modules/pi-mcp-adapter/dist/session-recovery.js +142 -0
  120. package/node_modules/pi-mcp-adapter/dist/session-recovery.js.map +1 -0
  121. package/node_modules/pi-mcp-adapter/dist/state.d.ts +73 -0
  122. package/node_modules/pi-mcp-adapter/dist/state.js +2 -0
  123. package/node_modules/pi-mcp-adapter/dist/state.js.map +1 -0
  124. package/node_modules/pi-mcp-adapter/dist/types.d.ts +36 -0
  125. package/node_modules/pi-mcp-adapter/dist/types.js +18 -0
  126. package/node_modules/pi-mcp-adapter/dist/types.js.map +1 -1
  127. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.d.ts +17 -0
  128. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js +219 -0
  129. package/node_modules/pi-mcp-adapter/dist/ui-resource-handler.js.map +1 -0
  130. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.d.ts +15 -0
  131. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js +85 -0
  132. package/node_modules/pi-mcp-adapter/dist/unix-socket-transport.js.map +1 -0
  133. package/node_modules/pi-mcp-adapter/dist/utils.d.ts +1 -0
  134. package/node_modules/pi-mcp-adapter/dist/utils.js +13 -0
  135. package/node_modules/pi-mcp-adapter/dist/utils.js.map +1 -1
  136. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +29 -19
  137. package/node_modules/pi-mcp-adapter/examples/jev-accessibility-loop.mjs +85 -0
  138. package/node_modules/pi-mcp-adapter/examples/jev-semantic-filter.mjs +34 -0
  139. package/node_modules/pi-mcp-adapter/index.ts +129 -6
  140. package/node_modules/pi-mcp-adapter/init.ts +6 -19
  141. package/node_modules/pi-mcp-adapter/jev-client.ts +273 -0
  142. package/node_modules/pi-mcp-adapter/jev-contracts.ts +53 -0
  143. package/node_modules/pi-mcp-adapter/jev-key-store.ts +79 -0
  144. package/node_modules/pi-mcp-adapter/lifecycle.ts +14 -3
  145. package/node_modules/pi-mcp-adapter/mcp-bearer-store.ts +7 -142
  146. package/node_modules/pi-mcp-adapter/mcp-code.ts +119 -11
  147. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +4 -0
  148. package/node_modules/pi-mcp-adapter/mcp-references.ts +5 -3
  149. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +5 -0
  150. package/node_modules/pi-mcp-adapter/mcp-tasks.ts +467 -0
  151. package/node_modules/pi-mcp-adapter/metadata-cache.ts +2 -13
  152. package/node_modules/pi-mcp-adapter/namespace-tools.ts +1 -1
  153. package/node_modules/pi-mcp-adapter/package.json +17 -9
  154. package/node_modules/pi-mcp-adapter/proxy-modes.ts +319 -166
  155. package/node_modules/pi-mcp-adapter/request-headers-command.ts +6 -3
  156. package/node_modules/pi-mcp-adapter/search-ranking.ts +38 -7
  157. package/node_modules/pi-mcp-adapter/secure-keyring.ts +170 -0
  158. package/node_modules/pi-mcp-adapter/semantic-search.ts +186 -0
  159. package/node_modules/pi-mcp-adapter/server-manager.ts +293 -50
  160. package/node_modules/pi-mcp-adapter/session-approvals.ts +14 -0
  161. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +2 -0
  162. package/node_modules/pi-mcp-adapter/state.ts +3 -1
  163. package/node_modules/pi-mcp-adapter/tool-approval.ts +26 -4
  164. package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -14
  165. package/node_modules/pi-mcp-adapter/tool-registrar.ts +8 -6
  166. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +4 -1
  167. package/node_modules/pi-mcp-adapter/types.ts +53 -0
  168. package/node_modules/pi-mcp-adapter/ui-server.ts +17 -9
  169. package/node_modules/pi-mcp-adapter/utils.ts +16 -0
  170. package/node_modules/pi-multi-account/CHANGELOG.md +23 -0
  171. package/node_modules/pi-multi-account/README.md +38 -11
  172. package/node_modules/pi-multi-account/index.ts +371 -96
  173. package/node_modules/pi-multi-account/package.json +5 -4
  174. package/node_modules/pi-multi-account/provider-payload-stream.ts +36 -28
  175. package/node_modules/pi-multi-account/usage.ts +31 -2
  176. package/node_modules/pi-typesafe/README.md +3 -1
  177. package/node_modules/pi-typesafe/dist/auth.d.ts +10 -3
  178. package/node_modules/pi-typesafe/dist/auth.js +13 -7
  179. package/node_modules/pi-typesafe/dist/backends.d.ts +31 -0
  180. package/node_modules/pi-typesafe/dist/backends.js +33 -0
  181. package/node_modules/pi-typesafe/dist/client.d.ts +3 -9
  182. package/node_modules/pi-typesafe/dist/client.js +63 -39
  183. package/node_modules/pi-typesafe/dist/credentials.d.ts +11 -5
  184. package/node_modules/pi-typesafe/dist/credentials.js +15 -8
  185. package/node_modules/pi-typesafe/dist/extension.js +4 -1
  186. package/node_modules/pi-typesafe/dist/index.d.ts +1 -1
  187. package/node_modules/pi-typesafe/dist/index.js +1 -1
  188. package/node_modules/pi-typesafe/dist/login.d.ts +13 -7
  189. package/node_modules/pi-typesafe/dist/login.js +17 -8
  190. package/node_modules/pi-typesafe/dist/schema.js +19 -13
  191. package/node_modules/pi-typesafe/package.json +1 -1
  192. package/package.json +11 -10
@@ -1,7 +1,7 @@
1
1
  import { getToolUiResourceUri } from "./ui-app-bridge-helpers.ts";
2
2
  import type { McpExtensionState } from "./state.ts";
3
3
  import type { ToolMetadata, McpTool, McpResource, ServerEntry, ToolPrefix } from "./types.ts";
4
- import { createToolSelectorCandidateIndex, formatToolName, getToolNameCandidates, isToolAllowed, resolveToolPrefix } from "./types.ts";
4
+ import { createToolSelectorCandidateIndex, formatToolName, getToolNameCandidates, isToolAllowed, resolveToolPrefix, resolveUniqueNameOwnership } from "./types.ts";
5
5
  import { resourceNameToToolName } from "./resource-tools.ts";
6
6
  import { extractToolUiStreamMode } from "./utils.ts";
7
7
  import { extractUiToolVisibility, isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
@@ -18,7 +18,6 @@ export function buildToolMetadata(
18
18
  ): { metadata: ToolMetadata[]; failedTools: string[] } {
19
19
  const metadata: ToolMetadata[] = [];
20
20
  const failedTools: string[] = [];
21
- const seenNames = new Set<string>();
22
21
  const effectivePrefix = resolveToolPrefix(definition, prefix);
23
22
  const hasToolFilters =
24
23
  (Array.isArray(definition.includeTools) && definition.includeTools.length > 0) ||
@@ -86,16 +85,10 @@ export function buildToolMetadata(
86
85
  }
87
86
 
88
87
  const name = formatToolName(tool.name, serverName, effectivePrefix);
89
- if (seenNames.has(name)) {
90
- continue;
91
- }
92
-
93
88
  const uiVisibility = extractUiToolVisibility(tool._meta);
94
89
  if (!isUiToolVisibleToModel(uiVisibility)) {
95
90
  continue;
96
91
  }
97
- seenNames.add(name);
98
-
99
92
  let uiResourceUri: string | undefined;
100
93
  try {
101
94
  uiResourceUri = getToolUiResourceUri({ _meta: tool._meta });
@@ -123,11 +116,6 @@ export function buildToolMetadata(
123
116
  }
124
117
 
125
118
  const name = formatToolName(baseName, serverName, effectivePrefix);
126
- if (seenNames.has(name)) {
127
- continue;
128
- }
129
- seenNames.add(name);
130
-
131
119
  metadata.push({
132
120
  name,
133
121
  originalName: baseName,
@@ -137,7 +125,11 @@ export function buildToolMetadata(
137
125
  }
138
126
  }
139
127
 
140
- return { metadata, failedTools };
128
+ const ownership = resolveUniqueNameOwnership(metadata, (tool) => tool.name);
129
+ for (const colliding of ownership.collisions.values()) {
130
+ failedTools.push(...colliding.map((tool) => tool.originalName));
131
+ }
132
+ return { metadata: ownership.unique, failedTools };
141
133
  }
142
134
 
143
135
  export function getToolNames(state: McpExtensionState, serverName: string): string[] {
@@ -230,18 +230,20 @@ export function transformMcpContent(content: McpContent[], scope?: object): Cont
230
230
  }
231
231
 
232
232
  /**
233
- * Resolve a tool result's content blocks, falling back to structuredContent
234
- * when content is empty.
233
+ * Resolve a tool result's content blocks, appending structuredContent when it
234
+ * coexists with ordinary content and falling back to it when content is empty.
235
235
  */
236
236
  export function resolveMcpResultContent(result: Record<string, unknown>, scope?: object): ContentBlock[] {
237
237
  const blocks = transformMcpContent((Array.isArray(result.content) ? result.content : []) as McpContent[], scope);
238
- if (blocks.length > 0) return blocks;
239
-
240
238
  if (result.structuredContent !== undefined && result.structuredContent !== null) {
241
- return [{ type: "text" as const, text: stringifyStructuredContent(result.structuredContent) }];
239
+ const structured = stringifyStructuredContent(result.structuredContent);
240
+ if (blocks.length > 0) {
241
+ return [...blocks, { type: "text" as const, text: `structuredContent:\n${structured}` }];
242
+ }
243
+ return [{ type: "text" as const, text: structured }];
242
244
  }
243
245
 
244
- return [];
246
+ return blocks;
245
247
  }
246
248
 
247
249
  function stringifyStructuredContent(value: unknown): string {
@@ -225,7 +225,10 @@ export function formatMcpProxyToolCallLines(
225
225
  }
226
226
 
227
227
  if (args.connect) return [`mcp connect ${args.connect}`];
228
- if (args.describe) return [`mcp describe ${args.describe}`];
228
+ if (args.describe) {
229
+ const target = args.server ? `${args.describe} @ ${args.server}` : args.describe;
230
+ return [`mcp describe ${target}`];
231
+ }
229
232
 
230
233
  if (args.search) {
231
234
  let line = `mcp search ${args.search}`;
@@ -498,6 +498,15 @@ export interface ServerEntry {
498
498
  * with no fallback. `auto` and `2026-07-28` must be set explicitly.
499
499
  */
500
500
  protocolVersion?: "legacy" | "auto" | "2026-07-28";
501
+ /**
502
+ * MCP Tasks extension (io.modelcontextprotocol/tasks, SEP-2663) support.
503
+ * On 2026-07-28 connections where the server advertises the extension, tool
504
+ * calls that return a task handle are transparently polled to completion,
505
+ * task-time elicitation is routed through the normal elicitation UI, and
506
+ * aborting a call cancels the remote task. Enabled by default; set to
507
+ * false to keep the plain synchronous call path.
508
+ */
509
+ tasks?: boolean;
501
510
  // Keep configuration visible without allowing connections or execution.
502
511
  disabled?: boolean;
503
512
  }
@@ -576,6 +585,8 @@ export interface McpToolApprovalRequest {
576
585
  claim(handler: McpToolApprovalHandler): boolean;
577
586
  }
578
587
 
588
+ export type { JevAnswer, JevErrorCode, JevEvaluateInput, JevEvaluationData, JevEvaluationEnvelope, JevJson, JevQuestion } from "./jev-contracts.ts";
589
+
579
590
  export interface McpSettings {
580
591
  toolPrefix?: ToolPrefix;
581
592
  /** Show the plug prefix in MCP status and connection text (default: true). Set to false to disable it. */
@@ -593,6 +604,8 @@ export interface McpSettings {
593
604
  idleTimeout?: number; // minutes, default 10, 0 to disable
594
605
  requestTimeoutMs?: number; // milliseconds, overrides the SDK request timeout when > 0
595
606
  directTools?: boolean | "search";
607
+ /** Register per-server mcp__<server> namespace proxies. Defaults to true. */
608
+ namespaceProxyTools?: boolean;
596
609
  /**
597
610
  * Validate direct-tool inputs against the advertised schema after recovering
598
611
  * one JSON string layer for object and array properties. Defaults to false.
@@ -607,6 +620,25 @@ export interface McpSettings {
607
620
  warnOnLargeDirectTools?: boolean;
608
621
  /** Register the trusted MCP-only JavaScript scripting tool. Defaults to true; set false to hide it. */
609
622
  scriptMode?: boolean;
623
+ /** Optional TypeSafe Jev integrations. A valid key enables semantic search; script evaluation remains disabled by default. */
624
+ jev?: false | {
625
+ semanticSearch?: boolean;
626
+ scriptEvaluation?: boolean;
627
+ /** Restrict semantic-search metadata and allow script-evaluation sources. Semantic search defaults to every enabled server. */
628
+ allowedServers?: string[];
629
+ model?: string;
630
+ requestTimeoutMs?: number;
631
+ maxRetries?: number;
632
+ maxStateBytes?: number;
633
+ maxQuestionsPerRequest?: number;
634
+ maxEvaluationsPerScript?: number;
635
+ maxEvaluationBytesPerScript?: number;
636
+ /** Cumulative provider-reported input plus output tokens per script. Defaults to 32768. */
637
+ maxEvaluationTokensPerScript?: number;
638
+ /** Maximum semantic candidates per request. Defaults to 127; range 2..127. */
639
+ semanticCandidateLimit?: number;
640
+ semanticMinProbability?: number;
641
+ };
610
642
  /** Render MCP tool results as compact self-rendered rows by default, or as the legacy boxed row. */
611
643
  toolResultRendering?: "compact" | "boxed";
612
644
  /** Number of result text lines to show before expansion. Supports 1, 2, or 3. Defaults to 1 in compact mode and 3 in boxed mode. */
@@ -815,6 +847,9 @@ export function formatToolName(
815
847
  ): string {
816
848
  const p = getServerPrefix(serverName, prefix);
817
849
  const sanitized = toolName.replace(/\./g, "_");
850
+ if (p && sanitized.startsWith(`${p}_`) && sanitized.length > p.length + 1) {
851
+ return sanitized;
852
+ }
818
853
  return p ? `${p}_${sanitized}` : sanitized;
819
854
  }
820
855
 
@@ -825,6 +860,24 @@ export function resolveToolPrefix(
825
860
  return definition?.toolPrefix ?? globalPrefix ?? "server";
826
861
  }
827
862
 
863
+ /** A canonical name has an owner only when exactly one eligible entry produces it. */
864
+ export function resolveUniqueNameOwnership<T>(
865
+ entries: readonly T[],
866
+ getName: (entry: T) => string,
867
+ ): { unique: T[]; collisions: Map<string, T[]> } {
868
+ const owners = new Map<string, T[]>();
869
+ for (const entry of entries) {
870
+ const name = getName(entry);
871
+ const named = owners.get(name) ?? [];
872
+ named.push(entry);
873
+ owners.set(name, named);
874
+ }
875
+ const collisions = new Map([...owners].filter(([, named]) => named.length > 1));
876
+ return {
877
+ unique: entries.filter((entry) => !collisions.has(getName(entry))),
878
+ collisions,
879
+ };
880
+ }
828
881
 
829
882
  /**
830
883
  * Resolve a configured MCP server name from a prefixed tool name.
@@ -19,10 +19,11 @@ import {
19
19
  SANDBOX_RESOURCE_PATH_PREFIX,
20
20
  } from "./sandbox-proxy-template.ts";
21
21
  import { logger } from "./logger.ts";
22
- import type { McpServerManager } from "./server-manager.ts";
22
+ import type { McpServerManager, ServerConnection } from "./server-manager.ts";
23
23
  import type { McpExtensionState } from "./state.ts";
24
24
  import { SessionRecoveryAuthRequiredError, withSessionRecovery, type SessionRecoveryDeps } from "./session-recovery.ts";
25
25
  import { ensureToolCallApproved, isToolCallApprovalRequired } from "./tool-approval.ts";
26
+ import { callToolViaTaskSession } from "./mcp-tasks.ts";
26
27
  import { extractUiToolVisibility, isUiToolCallableByApp, isUiToolVisibleToModel } from "./ui-tool-visibility.ts";
27
28
  import { resourceNameToToolName } from "./resource-tools.ts";
28
29
  import {
@@ -530,6 +531,19 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
530
531
  try {
531
532
  options.manager.touch(options.serverName);
532
533
  options.manager.incrementInFlight(options.serverName);
534
+ const callTool = async (conn: ServerConnection) => {
535
+ await options.manager.ensureListen?.(options.serverName, conn);
536
+ const requestOptions = options.manager.getRequestOptions?.(options.serverName);
537
+ if (conn.taskSession) {
538
+ return callToolViaTaskSession(conn.taskSession, {
539
+ name: callArgs.name,
540
+ args: callArgs.arguments,
541
+ requestTimeoutMs: requestOptions?.timeout,
542
+ signal: requestOptions?.signal,
543
+ });
544
+ }
545
+ return conn.client.callTool(callArgs, requestOptions);
546
+ };
533
547
  const result = options.config
534
548
  ? await withSessionRecovery(
535
549
  {
@@ -538,15 +552,9 @@ export async function startUiServer(options: UiServerOptions): Promise<UiServerH
538
552
  ...(options.onNeedsAuth ? { onNeedsAuth: options.onNeedsAuth } : {}),
539
553
  },
540
554
  options.serverName,
541
- async (conn) => {
542
- await options.manager.ensureListen?.(options.serverName, conn);
543
- return conn.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
544
- },
555
+ callTool,
545
556
  )
546
- : await (async () => {
547
- await options.manager.ensureListen?.(options.serverName, connection);
548
- return connection.client.callTool(callArgs, options.manager.getRequestOptions?.(options.serverName));
549
- })();
557
+ : await callTool(connection);
550
558
  sendJson(res, 200, { ok: true, result });
551
559
  } finally {
552
560
  options.manager.decrementInFlight(options.serverName);
@@ -426,6 +426,22 @@ export function formatMcpStatus(config: Pick<McpConfig, "settings">, message: st
426
426
  return `${config.settings?.showStatusIcon === false ? "MCP: " : "🔌 MCP: "}${message}`;
427
427
  }
428
428
 
429
+ export function formatMcpFooterStatus(
430
+ config: Pick<McpConfig, "settings">,
431
+ enabledCount: number,
432
+ disabledCount: number,
433
+ connectedCount: number,
434
+ ): string | undefined {
435
+ if (enabledCount + disabledCount === 0 || config.settings?.mcpFooterStatus === "off") return undefined;
436
+ const footerStatus = config.settings?.mcpFooterStatus ?? "full";
437
+ if (footerStatus === "compact") return `MCP ${connectedCount}/${enabledCount}`;
438
+
439
+ let status = `${enabledCount} ${enabledCount === 1 ? "server" : "servers"} enabled`;
440
+ if (connectedCount > 0) status += ` (${connectedCount} connected)`;
441
+ if (disabledCount > 0) status += ` (${disabledCount} disabled)`;
442
+ return formatMcpStatus(config, status);
443
+ }
444
+
429
445
  /**
430
446
  * Extract the adapter-owned UI stream mode from tool metadata.
431
447
  */
@@ -5,6 +5,29 @@ All notable changes to this project are documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.23.0] — 2026-09-23
9
+
10
+ ### Added
11
+
12
+ - `/multi-account pick` offers only the current account's models without filtering the shared registry or changing Pi core. `/multi-account save-default` saves the current model and effective thinking level together for new sessions, without making automatic failover overwrite startup preferences.
13
+ - Added `resumeAfterAllAccountsRecover` (default `true`) as a separate live-session liveness control. A real quota/rate-limit wall across every compatible account remains armed even when immediate `autoContinue` is disabled, polls recovery independently of footer visibility, and resumes on the first account that is genuinely usable.
14
+
15
+ ### Compatibility
16
+
17
+ - Pi and pi-ai are peer dependencies in the tested range >=0.85.1, <0.88.0. Transport imports bind to the running host rather than an extension-local 0.85 copy. CI covers Pi 0.85.1, 0.86.1 and 0.87.1; standalone SDK callers must use their installed pi-ai context format.
18
+
19
+ ### Fixed
20
+
21
+ - Native provider wrappers preserve system instructions and tool definitions on Pi's transcript-based context, avoiding tool-free Anthropic requests and old token-estimator crashes (#65, #66).
22
+ - Partial Codex usage headers preserve account/plan display metadata for the same credential without refreshing old serviceability, credits or missing quota windows. Includes a routing regression for stale `serviceable: true` plus fresh 100% quota (adapted from #67, thanks @HerbertGao).
23
+ - Kimi OAuth and unauthenticated spare slots remain in the login picker but are no longer published as unusable static aliases. Only authenticated API-key aliases are published; existing user models.json entries remain untouched (#68).
24
+ - Updated the Anthropic billing-header Claude Code version to 2.1.280 (#64).
25
+ - A continuation that exhausted its newly selected fallback no longer stops after one hop: the next quota-driven switch receives its own continuation.
26
+ - Asynchronous rejection of Pi's injected follow-up no longer loses the interrupted task; the selected fallback remains armed for a bounded retry.
27
+ - `neverFailoverProviders` now also bypasses foreground startup/input preflights for unmanaged providers. Stale cooldowns, invalidations, or unknown auth no longer silently replace an opted-out route before its request. Automatic switch and pending-resume boundaries enforce the same exemption; explicit manual switches remain available.
28
+ - A bodyless `429 status code (no body)` from an unmanaged provider now retries the same route instead of being treated as provider-wide credit exhaustion. The retry honors `Retry-After` when available and otherwise uses the transient delay, avoiding both an unrelated-provider failover and a false six-hour bench for providers such as Cerebras.
29
+ - Numbered Codex OAuth slots no longer fall back to the public ChatGPT endpoint while the child proxy is enabled. A numbered alias is always registered against the loopback route that swaps in the real credential, from extension load onward, so the child-facing placeholder can never be sent to a provider that cannot read it (`Could not parse your authentication token`). Registration now requires an explicit route, so a future call site cannot silently reintroduce the public fallback.
30
+
8
31
  ## [1.22.0] — 2026-09-17
9
32
 
10
33
  ### Compatibility
@@ -11,9 +11,9 @@ When the account you are using hits a quota or rate limit, `pi-multi-account` tr
11
11
  - **Auto-discovers new Codex models per account.** At session start (and on `reload` / `rediscover`) it reads OpenAI's authenticated model catalog, mirrors each account's actually available models onto its Pi alias, and follows OpenAI's server priority. A new flagship can therefore win immediately without an extension release or a hard-coded model id.
12
12
  - **Handles auth failures without poisoning healthy OAuth accounts.** A generic final 401 briefly cools down a refreshable account and moves the current task forward. Explicit provider verdicts such as `authentication token has been invalidated` force an early refresh; if the refresh token is dead too, the slot is removed and Pi prints the interactive `/login` recovery steps.
13
13
  - **Fails over on quota / rate-limit** (429 / 402 / 403 and friends): the exhausted account goes on cooldown (parsed from the provider's own reset metadata when available) and Pi first tries another account with the same model. If it must leave the family, it preserves the model's quality band — Sol/Opus/other frontier flagships stay frontier; Terra/Sonnet stay balanced; Luna/Haiku stay fast — and keeps the session's thinking level. A fresh provider verdict of `blocked` or 100% is skipped automatically instead of wasting the turn; manual `next` remains an explicit one-attempt override for stale telemetry.
14
- - **Optional auto-continue**: resumes the interrupted turn after a switch from the last safe point. Temporary 5xx/overload errors and Cursor stalls retry the exact selected provider/account/model, without marking its quota exhausted. After four failed attempts (or an earlier recovery breaker), automatic retries stop with an explicit explanation; temporary errors never authorize a switch. Same-route retries are labelled as retries, not fake switches. A failed or cancelled automatic compaction still continues the task instead of leaving the session parked in Working.
14
+ - **Optional immediate auto-continue**: resumes the interrupted turn after a switch from the last safe point. A continuation that exhausts its newly selected account keeps advancing instead of stopping after the first hop. Temporary 5xx/overload errors and Cursor stalls retry the exact selected provider/account/model, without marking its quota exhausted. After four failed attempts (or an earlier recovery breaker), automatic retries stop with an explicit explanation; temporary errors never authorize a switch. Same-route retries are labelled as retries, not fake switches. A rejected Pi follow-up remains armed instead of losing the task. A failed or cancelled automatic compaction still continues the task instead of leaving the session parked in Working.
15
15
  - **Never hides a fresh user message in a private cooldown queue.** If no account is ready, the message remains in Pi's visible transcript and Pi owns its normal delivery/retry path.
16
- - **Session-bound overnight resume**: if every account is cooling down, the live Pi session waits for the earliest recovery and continues automatically. A new user message, `/multi-account stop`, session exit, or Esc during a running turn cancels the chain.
16
+ - **Session-bound overnight resume, independently configurable**: if every compatible account is cooling down, the live Pi session keeps the task armed by default even when ordinary post-switch `autoContinue` is off. It polls every account (quota monitoring remains active when the footer is hidden), reacts to fresh provider recovery evidence, and continues on the first genuinely usable route. A new user message, `/multi-account stop`, session exit, or Esc during a running turn cancels the chain.
17
17
  - **Deduplicates provably identical accounts** so duplicate Codex workspace memberships and identical credentials do not consume multiple rotation slots or get separate cooldowns. Different users in one Team/Business workspace remain distinct. New provable duplicate logins are rejected before the redundant slot is saved.
18
18
  - **Keeps YOUR reasoning level across automatic switches.** Whatever the session runs at — your Pi default, `/thinking`, or a per-agent `--thinking low` — is preserved and restored after every account/model switch, so it never drifts downward when a weaker fallback model clamps it. Manual `/model` selections adopt Pi's per-model thinking default in `auto` mode; an explicit CLI `--thinking` or forced `reasoningLevel` remains authoritative. The extension does not otherwise override your level, and extreme levels such as `xhigh` / Max / Ultra are never forced.
19
19
  - **Shows live limits for the active account** in Pi's footer: remaining 5-hour/session and weekly allowance plus reset countdowns for Codex, Anthropic, and Ollama Cloud accounts, SuperGrok / xAI subscription credit usage for `/login xai` OAuth (not Cursor Grok, not `XAI_API_KEY`), and GLM Coding Plan CN 5-hour/weekly credit quota for `zai-coding-cn`. GLM CN uses the raw Coding Plan API key only at `https://open.bigmodel.cn/api/monitor/usage/quota/limit`; global `zai` keys are not sent there. Missing or malformed quota is unknown, never assumed unused.
@@ -26,11 +26,17 @@ pi install npm:pi-multi-account
26
26
 
27
27
  Restart Pi or run `/reload` after installation.
28
28
 
29
- Requires Node 22+ and `@earendil-works/pi-ai` 0.78 or newer — it is installed automatically as a
30
- dependency. Both the pre-0.80 OAuth API and the 0.80+ provider-factory API are supported, so the
31
- extension keeps working across pi-ai upgrades. If a pi-ai it cannot adapt is ever encountered, the
32
- extension still loads and API-key accounts keep rotating; only subscription login is unavailable,
33
- and it says so at session start.
29
+ Requires Node 22+ and Agent Pi / pi-ai **>=0.85.1, <0.88.0** (CI covers 0.85.1, 0.86.1 and 0.87.1).
30
+ Pi core packages are peers, not a private pinned transport dependency. Provider wrappers use Pi's
31
+ host-bound imports, so the request adapter and host context format stay together; a stale nested
32
+ pi-ai cannot silently remove tools or system instructions. Standalone SDK callers must pair their
33
+ context with the installed pi-ai version. The OAuth/catalog bridge remains a separate, best-effort
34
+ compatibility layer: unavailable subscription login does not prevent API-key account discovery.
35
+
36
+ Kimi OAuth spares remain available in `/login`, but are not written to `models.json`: without a
37
+ Kimi child OAuth proxy those aliases would be resolvable but unauthenticated. Only real API-key
38
+ Kimi slots are published for extension-free children. Existing user entries are not deleted;
39
+ a previously generated unused Kimi alias can be removed from `models.json` and will not reappear.
34
40
 
35
41
  > **Anthropic (Claude Pro/Max) works out of the box.** OAuth login and request
36
42
  > shaping for the base `anthropic` provider and every `anthropic-account-*` alias
@@ -98,7 +104,9 @@ All three names are aliases for the same command: `/multi-account`, `/provider-f
98
104
  | `add [anthropic\|codex\|kimi\|cursor\|ollama\|qwen]` | Print the next free account slot to select from the interactive `/login` picker. Subscription families (Anthropic, Codex, Kimi, Cursor) are logged in through `/login`; API-key families are filled in `auth.json`. |
99
105
  | `remove [anthropic\|codex\|kimi\|cursor\|ollama\|qwen\|<provider-id>]` | Remove an account from `auth.json` and rotation. Family name drops the highest numbered alias slot; a full provider id removes that exact slot. Aliases: `rm`, `delete`. |
100
106
  | `next` | Manually switch to the next compatible-quality fallback, deliberately overriding recorded cooldowns for one attempt. Use explicit `switch` to select a different tier. |
101
- | `only-active [on\|off]` | Legacy picker preference; the complete Pi model registry remains available to all clients. Pi currently has no separate picker-only filter. Alias: `focus`. |
107
+ | `pick` | Open a model picker for the current account only. Uses native model selection and its thinking default; never removes models from the shared registry. Requires an idle interactive session. Built-in `/model` remains unchanged. |
108
+ | `save-default` | Save the current model and effective thinking level together as global startup defaults for new sessions. Preserves other models' thinking preferences. Project overrides, explicit CLI options, and resumed-session settings still take precedence. Automatic rotation never invokes this. |
109
+ | `only-active [on\|off]` | Legacy picker preference; use `pick` for a current-account-only menu. The complete Pi model registry remains available to all clients. Alias: `focus`. |
102
110
  | `stop` | Abort and cancel automatic failover/resume for the current task. |
103
111
  | `reset` | Clear all cooldowns, invalidations and any pending auto-resume. |
104
112
  | `reload` | Reload config from disk and re-discover accounts. |
@@ -125,12 +133,13 @@ A default config is created at `~/.pi/agent/provider-failover.json` on first run
125
133
  | Key | Default | Description |
126
134
  |---|---|---|
127
135
  | `enabled` | `true` | Master switch. |
128
- | `autoContinue` | `true` | Queue a continuation prompt after a switch. |
136
+ | `autoContinue` | `true` | Immediately continue after a successful account/model switch or same-route temporary retry. This does not control the all-accounts-limited wait. |
137
+ | `resumeAfterAllAccountsRecover` | `true` | When a real quota/rate limit leaves every compatible account cooling, keep the interrupted task armed in the current live session and resume on the first account that becomes genuinely usable. Independent of `autoContinue`; set `false` to opt out. |
129
138
  | `autoDiscover` | `true` | Auto-discover accounts from `auth.json`. |
130
139
  | `autoDiscoverModels` | `true` | Fetch OpenAI's authenticated model catalog for every Codex account and register new models on that account's alias automatically. |
131
140
  | `includeQwen` | `true` | Include Qwen / Alibaba accounts. |
132
141
  | `includeOllama` | `true` | Include Ollama (local) accounts. |
133
- | `neverFailoverProviders` | `[]` | Provider ids to never fail away from, e.g. `["my-provider"]`. For **unmanaged** providers that run their own retry logic (typically a companion extension owning retries for that provider) — switching accounts underneath it would fight those retries. Managed accounts still cool and rotate normally. |
142
+ | `neverFailoverProviders` | `[]` | Provider ids to never automatically fail away from, e.g. `["my-provider"]`. Foreground startup/input preflights, pending retries, and compaction leave these routes to Pi even with stale cooldowns. Manual switches remain allowed. For **unmanaged** providers that run their own retry logic (typically a companion extension owning retries for that provider) — switching accounts underneath it would fight those retries. Managed accounts still cool and rotate normally. |
134
143
  | `includeCursor` | `true` | Include Cursor subscription accounts. The Cursor provider is a separate, optional repo — until it is cloned this setting does nothing at all: no cursor login slot is offered and no warning is printed. Run `/multi-account add cursor` to get the install instructions. |
135
144
  | `childProxy` | `true` | Serve OAuth rotation slots to processes that do not load this extension (a memory extension consolidating its notes, an external CLI, any `pi -p --no-extensions` call) through a loopback route this process owns. A bare child launched without `--model` inherits Pi's **saved global default**, which since Pi 0.84.3 intentionally may differ from this session's live rotation; an explicitly pinned child uses its requested slot. Without a usable publication either form can fail authentication and fall back to whichever provider Pi finds first. The published route carries a non-secret placeholder; the real credential never leaves the parent. |
136
145
  | `providerOrder` | `["anthropic","openai-codex","qwen","ollama"]` | Preferred family order in the rotation. |
@@ -159,7 +168,7 @@ State (cooldowns, invalidations, recent switches, credential-free Codex model ca
159
168
 
160
169
  ### Session model ownership
161
170
 
162
- Requires Agent Pi **0.85.1 or newer**. Pi's session branch and explicit SDK/CLI launch model are authoritative. Shared account telemetry and legacy `lastUserModel` / `lastUserThinkingLevel` state cannot replace another pane's live selection, and shutdown does not publish that selection as a global default. Cold-catalog repair uses this session's model history once at startup. Configure Pi's saved default explicitly for new sessions.
171
+ Requires the supported Agent Pi versions listed above. Pi's session branch and explicit SDK/CLI launch model are authoritative. Shared account telemetry and legacy `lastUserModel` / `lastUserThinkingLevel` state cannot replace another pane's live selection, and shutdown does not publish that selection as a global default. Cold-catalog repair uses this session's model history once at startup. Configure Pi's saved default explicitly for new sessions.
163
172
 
164
173
  ### Host-owned background completions
165
174
 
@@ -187,6 +196,24 @@ A failover is only useful if the agent actually keeps working afterward. These g
187
196
 
188
197
  `pi-multi-account` reads credentials through Pi and its account store. Account removal, OAuth refresh and parent-owned proxy publication can update the credential files under Pi-compatible locks. Proxy publication keeps a private recovery copy before replacing a credential with a loopback placeholder, and restores the real credential before deleting that copy. Credentials are never stored in rotation state. Account/token values are reduced to a short irreversible SHA-256 fingerprint for re-login detection and deduplication. Credentials are sent only to their own provider endpoints: usage/account probes (`chatgpt.com/backend-api/wham/usage`, `api.anthropic.com/api/oauth/usage`, `cli-chat-proxy.grok.com/v1/billing`, `open.bigmodel.cn/api/monitor/usage/quota/limit` (CN Coding Plan keys only), Ollama Cloud's `/api/me` and `/api/usage`, or Ollama's loopback-only `http://127.0.0.1:11434/api/me` fallback), OpenAI's authenticated `chatgpt.com/backend-api/codex/models` catalog, and provider OAuth token endpoints when Pi's authentication implementation refreshes a login (for xAI, `auth.x.ai/oauth2/token`). Cached state contains percentages, reset times, plan/credit metadata, model metadata, provider-reported account email/alias, and the fingerprint, never the token. Config, state, and the debug log are written with `0600` permissions. The debug log records only provider/model ids, decisions, and truncated reasons — token-shaped material is redacted defensively — Review logs for private project details before sharing an issue. Disable it with `"debugLog": false` or `/multi-account log off`.
189
198
 
199
+ ## Compatibility validation
200
+
201
+ `npm run release:check` runs TypeScript, the full suite, and package/privacy checks. CI also runs
202
+ the suite on the supported newer Pi versions. Host-binding tests load the real extension wrapper
203
+ through Pi with a deliberately incompatible nested pi-ai, inspect native request bodies, and
204
+ complete a streamed tool-call/result cycle without real credentials or provider traffic.
205
+
206
+ The optional companion Goal integration is selected explicitly; it never assumes a personal path:
207
+
208
+ ```bash
209
+ PI_GOAL_TEST_ENTRY=/path/to/pi-goal/dist/index.ts node --test test/goal-quota-recovery.integration.test.ts
210
+ PI_GOAL_TEST_ENTRY=/path/to/pi-goal/dist/index.ts PI_GOAL_EXHAUST_ALL=1 node --test test/goal-quota-recovery.integration.test.ts
211
+ PI_GOAL_TEST_ENTRY=/path/to/pi-goal/dist/index.ts PI_GOAL_EXHAUST_ALL=1 PI_GOAL_RECOVER_ALL=1 node --test test/goal-quota-recovery.integration.test.ts
212
+ ```
213
+
214
+ These exercise successful multi-hop continuation, quiet all-account waiting with explicit pause,
215
+ and resumption after quota recovery. The test is explicitly skipped when no companion path is supplied.
216
+
190
217
  ## License
191
218
 
192
219
  [MIT](./LICENSE)