mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 16:05:11 +00:00
feat(coding-agent): surface extension shortcut conflicts
This commit is contained in:
parent
72de8f26a1
commit
3a57f1259b
2 changed files with 38 additions and 3 deletions
|
|
@ -6,6 +6,7 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
||||||
import type { ImageContent, Model } from "@mariozechner/pi-ai";
|
import type { ImageContent, Model } from "@mariozechner/pi-ai";
|
||||||
import type { KeyId } from "@mariozechner/pi-tui";
|
import type { KeyId } from "@mariozechner/pi-tui";
|
||||||
import { type Theme, theme } from "../../modes/interactive/theme/theme.js";
|
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 { KeyAction, KeybindingsConfig } from "../keybindings.js";
|
||||||
import type { ModelRegistry } from "../model-registry.js";
|
import type { ModelRegistry } from "../model-registry.js";
|
||||||
import type { SessionManager } from "../session-manager.js";
|
import type { SessionManager } from "../session-manager.js";
|
||||||
|
|
@ -162,6 +163,7 @@ export class ExtensionRunner {
|
||||||
private forkHandler: ForkHandler = async () => ({ cancelled: false });
|
private forkHandler: ForkHandler = async () => ({ cancelled: false });
|
||||||
private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
|
private navigateTreeHandler: NavigateTreeHandler = async () => ({ cancelled: false });
|
||||||
private shutdownHandler: ShutdownHandler = () => {};
|
private shutdownHandler: ShutdownHandler = () => {};
|
||||||
|
private shortcutDiagnostics: ResourceDiagnostic[] = [];
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
extensions: Extension[],
|
extensions: Extension[],
|
||||||
|
|
@ -275,30 +277,42 @@ export class ExtensionRunner {
|
||||||
}
|
}
|
||||||
|
|
||||||
getShortcuts(effectiveKeybindings: Required<KeybindingsConfig>): Map<KeyId, ExtensionShortcut> {
|
getShortcuts(effectiveKeybindings: Required<KeybindingsConfig>): Map<KeyId, ExtensionShortcut> {
|
||||||
|
this.shortcutDiagnostics = [];
|
||||||
const builtinKeybindings = buildBuiltinKeybindings(effectiveKeybindings);
|
const builtinKeybindings = buildBuiltinKeybindings(effectiveKeybindings);
|
||||||
const extensionShortcuts = new Map<KeyId, ExtensionShortcut>();
|
const extensionShortcuts = new Map<KeyId, ExtensionShortcut>();
|
||||||
|
|
||||||
|
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 ext of this.extensions) {
|
||||||
for (const [key, shortcut] of ext.shortcuts) {
|
for (const [key, shortcut] of ext.shortcuts) {
|
||||||
const normalizedKey = key.toLowerCase() as KeyId;
|
const normalizedKey = key.toLowerCase() as KeyId;
|
||||||
|
|
||||||
const builtInKeybinding = builtinKeybindings[normalizedKey];
|
const builtInKeybinding = builtinKeybindings[normalizedKey];
|
||||||
if (builtInKeybinding?.restrictOverride === true) {
|
if (builtInKeybinding?.restrictOverride === true) {
|
||||||
console.warn(
|
addDiagnostic(
|
||||||
`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,
|
`Extension shortcut '${key}' from ${shortcut.extensionPath} conflicts with built-in shortcut. Skipping.`,
|
||||||
|
shortcut.extensionPath,
|
||||||
);
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (builtInKeybinding?.restrictOverride === false) {
|
if (builtInKeybinding?.restrictOverride === false) {
|
||||||
console.warn(
|
addDiagnostic(
|
||||||
`Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.action} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
|
`Extension shortcut conflict: '${key}' is built-in shortcut for ${builtInKeybinding.action} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
|
||||||
|
shortcut.extensionPath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingExtensionShortcut = extensionShortcuts.get(normalizedKey);
|
const existingExtensionShortcut = extensionShortcuts.get(normalizedKey);
|
||||||
if (existingExtensionShortcut) {
|
if (existingExtensionShortcut) {
|
||||||
console.warn(
|
addDiagnostic(
|
||||||
`Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
|
`Extension shortcut conflict: '${key}' registered by both ${existingExtensionShortcut.extensionPath} and ${shortcut.extensionPath}. Using ${shortcut.extensionPath}.`,
|
||||||
|
shortcut.extensionPath,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
extensionShortcuts.set(normalizedKey, shortcut);
|
extensionShortcuts.set(normalizedKey, shortcut);
|
||||||
|
|
@ -307,6 +321,10 @@ export class ExtensionRunner {
|
||||||
return extensionShortcuts;
|
return extensionShortcuts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getShortcutDiagnostics(): ResourceDiagnostic[] {
|
||||||
|
return this.shortcutDiagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
onError(listener: ExtensionErrorListener): () => void {
|
onError(listener: ExtensionErrorListener): () => void {
|
||||||
this.errorListeners.add(listener);
|
this.errorListeners.add(listener);
|
||||||
return () => this.errorListeners.delete(listener);
|
return () => this.errorListeners.delete(listener);
|
||||||
|
|
|
||||||
|
|
@ -932,6 +932,23 @@ export class InteractiveMode {
|
||||||
this.chatContainer.addChild(new Spacer(1));
|
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)
|
// Show loaded themes (excluding built-in)
|
||||||
const loadedThemes = this.session.resourceLoader.getThemes().themes;
|
const loadedThemes = this.session.resourceLoader.getThemes().themes;
|
||||||
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
const customThemes = loadedThemes.filter((t) => t.sourcePath);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue