mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 13:03:42 +00:00
feat(coding-agent): prioritize project resources over global
This commit is contained in:
parent
380236a003
commit
f0379384fe
8 changed files with 271 additions and 63 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -722,14 +722,14 @@ export class DefaultPackageManager implements PackageManager {
|
|||
const globalSettings = this.settingsManager.getGlobalSettings();
|
||||
const projectSettings = this.settingsManager.getProjectSettings();
|
||||
|
||||
// Collect all packages with scope
|
||||
// Collect all packages with scope (project first so cwd resources win collisions)
|
||||
const allPackages: Array<{ pkg: PackageSource; scope: SourceScope }> = [];
|
||||
for (const pkg of globalSettings.packages ?? []) {
|
||||
allPackages.push({ pkg, scope: "user" });
|
||||
}
|
||||
for (const pkg of projectSettings.packages ?? []) {
|
||||
allPackages.push({ pkg, scope: "project" });
|
||||
}
|
||||
for (const pkg of globalSettings.packages ?? []) {
|
||||
allPackages.push({ pkg, scope: "user" });
|
||||
}
|
||||
|
||||
// Dedupe: project scope wins over global for same package identity
|
||||
const packageSources = this.dedupePackages(allPackages);
|
||||
|
|
@ -742,17 +742,6 @@ export class DefaultPackageManager implements PackageManager {
|
|||
const target = this.getTargetMap(accumulator, resourceType);
|
||||
const globalEntries = (globalSettings[resourceType] ?? []) as string[];
|
||||
const projectEntries = (projectSettings[resourceType] ?? []) as string[];
|
||||
this.resolveLocalEntries(
|
||||
globalEntries,
|
||||
resourceType,
|
||||
target,
|
||||
{
|
||||
source: "local",
|
||||
scope: "user",
|
||||
origin: "top-level",
|
||||
},
|
||||
globalBaseDir,
|
||||
);
|
||||
this.resolveLocalEntries(
|
||||
projectEntries,
|
||||
resourceType,
|
||||
|
|
@ -764,6 +753,17 @@ export class DefaultPackageManager implements PackageManager {
|
|||
},
|
||||
projectBaseDir,
|
||||
);
|
||||
this.resolveLocalEntries(
|
||||
globalEntries,
|
||||
resourceType,
|
||||
target,
|
||||
{
|
||||
source: "local",
|
||||
scope: "user",
|
||||
origin: "top-level",
|
||||
},
|
||||
globalBaseDir,
|
||||
);
|
||||
}
|
||||
|
||||
this.addAutoDiscoveredResources(accumulator, globalSettings, projectSettings, globalBaseDir, projectBaseDir);
|
||||
|
|
@ -1600,35 +1600,6 @@ export class DefaultPackageManager implements PackageManager {
|
|||
}
|
||||
};
|
||||
|
||||
addResources(
|
||||
"extensions",
|
||||
collectAutoExtensionEntries(userDirs.extensions),
|
||||
userMetadata,
|
||||
userOverrides.extensions,
|
||||
globalBaseDir,
|
||||
);
|
||||
addResources(
|
||||
"skills",
|
||||
[...collectAutoSkillEntries(userDirs.skills), ...collectAutoSkillEntries(userAgentsSkillsDir)],
|
||||
userMetadata,
|
||||
userOverrides.skills,
|
||||
globalBaseDir,
|
||||
);
|
||||
addResources(
|
||||
"prompts",
|
||||
collectAutoPromptEntries(userDirs.prompts),
|
||||
userMetadata,
|
||||
userOverrides.prompts,
|
||||
globalBaseDir,
|
||||
);
|
||||
addResources(
|
||||
"themes",
|
||||
collectAutoThemeEntries(userDirs.themes),
|
||||
userMetadata,
|
||||
userOverrides.themes,
|
||||
globalBaseDir,
|
||||
);
|
||||
|
||||
addResources(
|
||||
"extensions",
|
||||
collectAutoExtensionEntries(projectDirs.extensions),
|
||||
|
|
@ -1660,6 +1631,35 @@ export class DefaultPackageManager implements PackageManager {
|
|||
projectOverrides.themes,
|
||||
projectBaseDir,
|
||||
);
|
||||
|
||||
addResources(
|
||||
"extensions",
|
||||
collectAutoExtensionEntries(userDirs.extensions),
|
||||
userMetadata,
|
||||
userOverrides.extensions,
|
||||
globalBaseDir,
|
||||
);
|
||||
addResources(
|
||||
"skills",
|
||||
[...collectAutoSkillEntries(userDirs.skills), ...collectAutoSkillEntries(userAgentsSkillsDir)],
|
||||
userMetadata,
|
||||
userOverrides.skills,
|
||||
globalBaseDir,
|
||||
);
|
||||
addResources(
|
||||
"prompts",
|
||||
collectAutoPromptEntries(userDirs.prompts),
|
||||
userMetadata,
|
||||
userOverrides.prompts,
|
||||
globalBaseDir,
|
||||
);
|
||||
addResources(
|
||||
"themes",
|
||||
collectAutoThemeEntries(userDirs.themes),
|
||||
userMetadata,
|
||||
userOverrides.themes,
|
||||
globalBaseDir,
|
||||
);
|
||||
}
|
||||
|
||||
private collectFilesFromPaths(paths: string[], resourceType: ResourceType): string[] {
|
||||
|
|
|
|||
|
|
@ -384,13 +384,10 @@ export class DefaultResourceLoader implements ResourceLoader {
|
|||
extensionsResult.errors.push(...inlineExtensions.errors);
|
||||
|
||||
// Detect extension conflicts (tools, commands, flags with same names from different extensions)
|
||||
// Keep all extensions loaded. Conflicts are reported as diagnostics, and precedence is handled by load order.
|
||||
const conflicts = this.detectExtensionConflicts(extensionsResult.extensions);
|
||||
if (conflicts.length > 0) {
|
||||
const conflictingPaths = new Set(conflicts.map((c) => c.path));
|
||||
extensionsResult.extensions = extensionsResult.extensions.filter((ext) => !conflictingPaths.has(ext.path));
|
||||
for (const conflict of conflicts) {
|
||||
extensionsResult.errors.push({ path: conflict.path, error: conflict.message });
|
||||
}
|
||||
for (const conflict of conflicts) {
|
||||
extensionsResult.errors.push({ path: conflict.path, error: conflict.message });
|
||||
}
|
||||
|
||||
this.extensionsResult = this.extensionsOverride ? this.extensionsOverride(extensionsResult) : extensionsResult;
|
||||
|
|
|
|||
|
|
@ -729,7 +729,7 @@ export class InteractiveMode {
|
|||
}
|
||||
}
|
||||
|
||||
return [groups.user, groups.project, groups.path].filter(
|
||||
return [groups.project, groups.user, groups.path].filter(
|
||||
(group) => group.paths.length > 0 || group.packages.size > 0,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue