fix(coding-agent): attach source info to resources and commands, fixes #1734

This commit is contained in:
Mario Zechner
2026-03-23 00:52:55 +01:00
parent 0cf18f3cf2
commit d501b9ca26
21 changed files with 425 additions and 321 deletions

View File

@@ -3,6 +3,7 @@
*/
export type { SlashCommandInfo, SlashCommandLocation, SlashCommandSource } from "../slash-commands.js";
export type { SourceInfo } from "../source-info.js";
export {
createExtensionRuntime,
discoverAndLoadExtensions,

View File

@@ -179,8 +179,13 @@ function createExtensionAPI(
runtime.refreshTools();
},
registerCommand(name: string, options: Omit<RegisteredCommand, "name">): void {
extension.commands.set(name, { name, ...options });
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "extensionPath">): void {
extension.commands.set(name, {
name,
extensionPath: extension.path,
sourceInfo: extension.sourceInfo,
...options,
});
},
registerShortcut(

View File

@@ -467,27 +467,16 @@ export class ExtensionRunner {
return undefined;
}
getRegisteredCommands(reserved?: Set<string>): RegisteredCommand[] {
this.commandDiagnostics = [];
private collectRegisteredCommands(diagnostics?: ResourceDiagnostic[]): RegisteredCommand[] {
const commands: RegisteredCommand[] = [];
const commandOwners = new Map<string, string>();
for (const ext of this.extensions) {
for (const command of ext.commands.values()) {
if (reserved?.has(command.name)) {
const message = `Extension command '${command.name}' from ${ext.path} conflicts with built-in commands. Skipping.`;
this.commandDiagnostics.push({ type: "warning", message, path: ext.path });
if (!this.hasUI()) {
console.warn(message);
}
continue;
}
const existingOwner = commandOwners.get(command.name);
if (existingOwner) {
const message = `Extension command '${command.name}' from ${ext.path} conflicts with ${existingOwner}. Skipping.`;
this.commandDiagnostics.push({ type: "warning", message, path: ext.path });
if (!this.hasUI()) {
diagnostics?.push({ type: "warning", message, path: ext.path });
if (diagnostics && !this.hasUI()) {
console.warn(message);
}
continue;
@@ -500,28 +489,19 @@ export class ExtensionRunner {
return commands;
}
getRegisteredCommands(): RegisteredCommand[] {
const diagnostics: ResourceDiagnostic[] = [];
const commands = this.collectRegisteredCommands(diagnostics);
this.commandDiagnostics = diagnostics;
return commands;
}
getCommandDiagnostics(): ResourceDiagnostic[] {
return this.commandDiagnostics;
}
getRegisteredCommandsWithPaths(): Array<{ command: RegisteredCommand; extensionPath: string }> {
const result: Array<{ command: RegisteredCommand; extensionPath: string }> = [];
for (const ext of this.extensions) {
for (const command of ext.commands.values()) {
result.push({ command, extensionPath: ext.path });
}
}
return result;
}
getCommand(name: string): RegisteredCommand | undefined {
for (const ext of this.extensions) {
const command = ext.commands.get(name);
if (command) {
return command;
}
}
return undefined;
return this.collectRegisteredCommands().find((command) => command.name === name);
}
/**

View File

@@ -55,6 +55,7 @@ import type {
SessionManager,
} from "../session-manager.js";
import type { SlashCommandInfo } from "../slash-commands.js";
import type { SourceInfo } from "../source-info.js";
import type { BashOperations } from "../tools/bash.js";
import type { EditToolDetails } from "../tools/edit.js";
import type {
@@ -960,6 +961,8 @@ export type MessageRenderer<T = unknown> = (
export interface RegisteredCommand {
name: string;
extensionPath: string;
sourceInfo?: SourceInfo;
description?: string;
getArgumentCompletions?: (argumentPrefix: string) => AutocompleteItem[] | null;
handler: (args: string, ctx: ExtensionCommandContext) => Promise<void>;
@@ -1035,7 +1038,7 @@ export interface ExtensionAPI {
// =========================================================================
/** Register a custom command. */
registerCommand(name: string, options: Omit<RegisteredCommand, "name">): void;
registerCommand(name: string, options: Omit<RegisteredCommand, "name" | "extensionPath">): void;
/** Register a keyboard shortcut. */
registerShortcut(
@@ -1414,6 +1417,7 @@ export interface ExtensionRuntime extends ExtensionRuntimeState, ExtensionAction
export interface Extension {
path: string;
resolvedPath: string;
sourceInfo?: SourceInfo;
handlers: Map<string, HandlerFn[]>;
tools: Map<string, RegisteredTool>;
messageRenderers: Map<string, MessageRenderer>;