feat: implement Open Plugins agents support

- Add pluginRoot to AgentDefinition metadata
- Implement ${PLUGIN_ROOT} expansion in markdownToAgentDefinition
- Automatically discover and namespace agents in Open Plugins
- Update ExtensionManager to pass plugin root during agent loading
- Display sub-agents in extensions list output

Fixes https://github.com/google-gemini/maintainers-gemini-cli/issues/1594
This commit is contained in:
Taylor Mullen
2026-03-23 22:50:12 -07:00
parent f7413564ac
commit 1a3741d2aa
6 changed files with 207 additions and 23 deletions
@@ -997,6 +997,7 @@ Would you like to attempt to install via "git clone" instead?`,
const agentLoadResult = await loadAgentsFromDirectory(
path.join(effectiveExtensionPath, 'agents'),
effectiveExtensionPath,
);
agentLoadResult.agents = agentLoadResult.agents.map((agent) => ({
...recursivelyHydrateStrings(agent, hydrationContext),
@@ -1184,6 +1185,12 @@ Would you like to attempt to install via "git clone" instead?`,
output += `\n ${skill.name}: ${skill.description}`;
});
}
if (extension.agents && extension.agents.length > 0) {
output += `\n Sub-agents:`;
extension.agents.forEach((agent) => {
output += `\n ${agent.name}: ${agent.description}`;
});
}
const resolvedSettings = extension.resolvedSettings;
if (resolvedSettings && resolvedSettings.length > 0) {
output += `\n Settings:`;
+30 -1
View File
@@ -9,6 +9,7 @@ import * as path from 'node:path';
import { z } from 'zod';
import {
loadSkillsFromDir,
loadAgentsFromDirectory,
type ExtensionInstallMetadata,
type GeminiCLIExtension,
type MCPServerConfig,
@@ -246,6 +247,12 @@ export async function createOpenPlugin(
hydrationContext,
);
const agents = await resolvePluginAgents(
pluginDir,
config.name,
hydrationContext,
);
return {
name: config.name,
version: config.version,
@@ -264,7 +271,7 @@ export async function createOpenPlugin(
settings: undefined,
resolvedSettings: undefined,
skills,
agents: undefined,
agents,
themes: undefined,
};
}
@@ -290,3 +297,25 @@ async function resolvePluginSkills(
extensionName: pluginName,
}));
}
/**
* Discovers and namespaces agents for an Open Plugin.
*/
async function resolvePluginAgents(
pluginDir: string,
pluginName: string,
hydrationContext: Record<string, string>,
): Promise<GeminiCLIExtension['agents']> {
const agentsDir = path.join(pluginDir, 'agents');
const agentLoadResult = await loadAgentsFromDirectory(agentsDir, pluginDir);
if (agentLoadResult.agents.length === 0) {
return undefined;
}
return agentLoadResult.agents.map((agent) => ({
...recursivelyHydrateStrings(agent, hydrationContext),
name: `${pluginName}:${agent.name}`,
extensionName: pluginName,
}));
}