feat(coding-agent): prioritize project resources over global

This commit is contained in:
Mario Zechner 2026-02-24 23:50:55 +01:00
parent 380236a003
commit f0379384fe
8 changed files with 271 additions and 63 deletions

View file

@ -173,7 +173,7 @@ function createExtensionAPI(
options: { description?: string; type: "boolean" | "string"; default?: boolean | string },
): void {
extension.flags.set(name, { name, extensionPath: extension.path, ...options });
if (options.default !== undefined) {
if (options.default !== undefined && !runtime.flagValues.has(name)) {
runtime.flagValues.set(name, options.default);
}
},
@ -486,14 +486,14 @@ export async function discoverAndLoadExtensions(
}
};
// 1. Global extensions: agentDir/extensions/
const globalExtDir = path.join(agentDir, "extensions");
addPaths(discoverExtensionsInDir(globalExtDir));
// 2. Project-local extensions: cwd/.pi/extensions/
// 1. Project-local extensions: cwd/.pi/extensions/
const localExtDir = path.join(cwd, ".pi", "extensions");
addPaths(discoverExtensionsInDir(localExtDir));
// 2. Global extensions: agentDir/extensions/
const globalExtDir = path.join(agentDir, "extensions");
addPaths(discoverExtensionsInDir(globalExtDir));
// 3. Explicitly configured paths
for (const p of configuredPaths) {
const resolved = resolvePath(p, cwd);

View file

@ -301,15 +301,17 @@ export class ExtensionRunner {
return this.extensions.map((e) => e.path);
}
/** Get all registered tools from all extensions. */
/** Get all registered tools from all extensions (first registration per name wins). */
getAllRegisteredTools(): RegisteredTool[] {
const tools: RegisteredTool[] = [];
const toolsByName = new Map<string, RegisteredTool>();
for (const ext of this.extensions) {
for (const tool of ext.tools.values()) {
tools.push(tool);
if (!toolsByName.has(tool.definition.name)) {
toolsByName.set(tool.definition.name, tool);
}
}
}
return tools;
return Array.from(toolsByName.values());
}
/** Get a tool definition by name. Returns undefined if not found. */
@ -327,7 +329,9 @@ export class ExtensionRunner {
const allFlags = new Map<string, ExtensionFlag>();
for (const ext of this.extensions) {
for (const [name, flag] of ext.flags) {
allFlags.set(name, flag);
if (!allFlags.has(name)) {
allFlags.set(name, flag);
}
}
}
return allFlags;
@ -425,6 +429,7 @@ export class ExtensionRunner {
this.commandDiagnostics = [];
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)) {
@ -436,6 +441,17 @@ export class ExtensionRunner {
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()) {
console.warn(message);
}
continue;
}
commandOwners.set(command.name, ext.path);
commands.push(command);
}
}