From 3a57f1259b9a585cd6164980fdf7a5cc520b2517 Mon Sep 17 00:00:00 2001 From: Mario Zechner Date: Sat, 24 Jan 2026 02:55:31 +0100 Subject: [PATCH] feat(coding-agent): surface extension shortcut conflicts --- .../src/core/extensions/runner.ts | 24 ++++++++++++++++--- .../src/modes/interactive/interactive-mode.ts | 17 +++++++++++++ 2 files changed, 38 insertions(+), 3 deletions(-) diff --git a/packages/coding-agent/src/core/extensions/runner.ts b/packages/coding-agent/src/core/extensions/runner.ts index 289400a0..c7deee56 100644 --- a/packages/coding-agent/src/core/extensions/runner.ts +++ b/packages/coding-agent/src/core/extensions/runner.ts @@ -6,6 +6,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core"; import type { ImageContent, Model } from "@mariozechner/pi-ai"; import type { KeyId } from "@mariozechner/pi-tui"; import { type Theme, theme } from "../../modes/interactive/theme/theme.js"; +import type { ResourceDiagnostic } from "../diagnostics.js"; import type { KeyAction, KeybindingsConfig } from "../keybindings.js"; import type { ModelRegistry } from "../model-registry.js"; import type { SessionManager } from "../session-manager.js"; @@ -162,6 +163,7 @@ export class ExtensionRunner { private forkHandler: ForkHandler = async () => ({ cancelled: false }); private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false }); private shutdownHandler: ShutdownHandler = () => {}; + private shortcutDiagnostics: ResourceDiagnostic[] = []; constructor( extensions: Extension[], @@ -275,30 +277,42 @@ export class ExtensionRunner { } getShortcuts(effectiveKeybindings: Required): Map { + this.shortcutDiagnostics = []; const builtinKeybindings = buildBuiltinKeybindings(effectiveKeybindings); const extensionShortcuts = new Map(); + + const addDiagnostic = (message: string, extensionPath: string) => { + this.shortcutDiagnostics.push({ type: "warning", message, path: extensionPath }); + if (!this.hasUI()) { + console.warn(message); + } + }; + for (const ext of this.extensions) { for (const [key, shortcut] of ext.shortcuts) { const normalizedKey = key.toLowerCase() as KeyId; const builtInKeybinding = builtinKeybindings[normalizedKey]; if (builtInKeybinding?.restrictOverride === true) { - console.warn( + addDiagnostic( `Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`, + shortcut.extensionPath, ); continue; } if (builtInKeybinding?.restrictOverride === false) { - console.warn( + addDiagnostic( `Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.action} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`, + shortcut.extensionPath, ); } const existingExtensionShortcut = extensionShortcuts.get(normalizedKey); if (existingExtensionShortcut) { - console.warn( + addDiagnostic( `Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`, + shortcut.extensionPath, ); } extensionShortcuts.set(normalizedKey, shortcut); @@ -307,6 +321,10 @@ export class ExtensionRunner { return extensionShortcuts; } + getShortcutDiagnostics(): ResourceDiagnostic[] { + return this.shortcutDiagnostics; + } + onError(listener: ExtensionErrorListener): () => void { this.errorListeners.add(listener); return () => this.errorListeners.delete(listener); diff --git a/packages/coding-agent/src/modes/interactive/interactive-mode.ts b/packages/coding-agent/src/modes/interactive/interactive-mode.ts index 6ca747de..0bca4ea6 100644 --- a/packages/coding-agent/src/modes/interactive/interactive-mode.ts +++ b/packages/coding-agent/src/modes/interactive/interactive-mode.ts @@ -932,6 +932,23 @@ export class InteractiveMode { this.chatContainer.addChild(new Spacer(1)); } + const extensionDiagnostics: ResourceDiagnostic[] = []; + const extensionErrors = this.session.resourceLoader.getExtensions().errors; + if (extensionErrors.length > 0) { + for (const error of extensionErrors) { + extensionDiagnostics.push({ type: "error", message: error.error, path: error.path }); + } + } + + const shortcutDiagnostics = this.session.extensionRunner?.getShortcutDiagnostics() ?? []; + extensionDiagnostics.push(...shortcutDiagnostics); + + if (extensionDiagnostics.length > 0) { + const warningLines = this.formatDiagnostics(extensionDiagnostics, metadata); + this.chatContainer.addChild(new Text(`${theme.fg("warning", "[Extension issues]")}\n${warningLines}`, 0, 0)); + this.chatContainer.addChild(new Spacer(1)); + } + // Show loaded themes (excluding built-in) const loadedThemes = this.session.resourceLoader.getThemes().themes; const customThemes = loadedThemes.filter((t) => t.sourcePath);