From 9d1a6503521d7bba9f8a1c59fe09fa47e23ef0aa Mon Sep 17 00:00:00 2001 From: Cristina Poncela Cubeiro <140309543+cristinaponcela@users.noreply.github.com> Date: Tue, 29 Sep 2026 16:37:51 +0200 Subject: [PATCH] fix(extensions): show warning when replaceable builtin replaced (#10174) --- .../coding-agent/src/core/resource-loader.ts | 37 ++++++++++++++++--- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/packages/coding-agent/src/core/resource-loader.ts b/packages/coding-agent/src/core/resource-loader.ts index 4f61d10f8..fa877d83c 100644 --- a/packages/coding-agent/src/core/resource-loader.ts +++ b/packages/coding-agent/src/core/resource-loader.ts @@ -117,14 +117,37 @@ export function isBuiltinExtension(input: InlineExtension): input is BuiltinExte * with another extension. For example, a third-party MCP extension that registers `/mcp` replaces * the built-in MCP extension instead of both connecting the same servers. */ -function omitReplacedExtensions(extensions: Extension[]): Extension[] { +function omitReplacedExtensions( + extensions: Extension[], + warnings?: Array<{ path: string; warning: string }>, +): Extension[] { const names = (extension: Extension) => [ ...[...extension.tools.keys()].map((name) => `tool:${name}`), ...[...extension.commands.keys()].map((name) => `command:${name}`), ...[...extension.flags.keys()].map((name) => `flag:${name}`), ]; - const taken = new Set(extensions.filter((extension) => !extension.replaceable).flatMap(names)); - return extensions.filter((extension) => !extension.replaceable || !names(extension).some((name) => taken.has(name))); + const taken = new Map( + extensions + .filter((extension) => !extension.replaceable) + .flatMap((extension) => names(extension).map((name) => [name, extension] as const)), + ); + return extensions.filter((extension) => { + if (!extension.replaceable) return true; + const replacement = names(extension) + .map((name) => ({ name, extension: taken.get(name) })) + .find((value): value is { name: string; extension: Extension } => value.extension !== undefined); + if (!replacement) return true; + if (extension.path.startsWith(BUILTIN_PATH_PREFIX)) { + const builtinName = extension.path.slice(BUILTIN_PATH_PREFIX.length); + const [kind, rawName] = replacement.name.split(":", 2); + const registeredName = kind === "command" ? `/${rawName}` : kind === "flag" ? `--${rawName}` : rawName; + warnings?.push({ + path: extension.path, + warning: `Extension ${replacement.extension.path} registers ${kind} \`${registeredName}\`, so built-in extension \`${builtinName}\` was not loaded. To use \`${builtinName}\`, run \`pi config\` and make sure it is enabled under Built-in extensions, then disable or remove the existing extension. We recommend only having one or the other loaded at a time.`, + }); + } + return false; + }); } export interface ResourceLoader { @@ -670,7 +693,9 @@ export class DefaultResourceLoader implements ResourceLoader { const inlineExtensions = await this.loadExtensionFactories(extensionsResult.runtime); extensionsResult.extensions.push(...inlineExtensions.extensions); extensionsResult.errors.push(...inlineExtensions.errors); - extensionsResult.extensions = omitReplacedExtensions(extensionsResult.extensions); + const replacementWarnings: Array<{ path: string; warning: string }> = []; + extensionsResult.extensions = omitReplacedExtensions(extensionsResult.extensions, replacementWarnings); + mergeExtensionWarnings(extensionsResult, replacementWarnings); return extensionsResult; } @@ -743,12 +768,14 @@ export class DefaultResourceLoader implements ResourceLoader { .filter((extension): extension is Extension => extension !== undefined); orderedExtensions.push(...inlineExtensions.extensions); + const replacementWarnings: Array<{ path: string; warning: string }> = []; const extensionsResult: LoadExtensionsResult = { - extensions: omitReplacedExtensions(orderedExtensions), + extensions: omitReplacedExtensions(orderedExtensions, replacementWarnings), errors: [...(preTrustExtensions?.errors ?? []), ...remainingExtensions.errors, ...inlineExtensions.errors], warnings: [...(preTrustExtensions?.warnings ?? []), ...(remainingExtensions.warnings ?? [])], runtime: remainingExtensions.runtime, }; + mergeExtensionWarnings(extensionsResult, replacementWarnings); this.addExtensionConflictDiagnostics(extensionsResult); return extensionsResult; }