diff --git a/packages/cli/src/ui/commands/agentsCommand.ts b/packages/cli/src/ui/commands/agentsCommand.ts index d904e8ca78..690396b798 100644 --- a/packages/cli/src/ui/commands/agentsCommand.ts +++ b/packages/cli/src/ui/commands/agentsCommand.ts @@ -65,6 +65,14 @@ const agentsRefreshCommand: SlashCommand = { }; } + context.ui.addItem( + { + type: MessageType.INFO, + text: 'Refreshing agent registry...', + }, + Date.now(), + ); + await agentRegistry.reload(); return { diff --git a/packages/core/src/agents/agentLoader.test.ts b/packages/core/src/agents/agentLoader.test.ts index 0d6acf7de0..eb642a7eb5 100644 --- a/packages/core/src/agents/agentLoader.test.ts +++ b/packages/core/src/agents/agentLoader.test.ts @@ -198,6 +198,32 @@ agent_card_url: https://example.com/card agent_card_url: 'https://example.com/2', }); }); + + it('should parse frontmatter without a trailing newline', async () => { + const filePath = await writeAgentMarkdown(`--- +kind: remote +name: no-trailing-newline +agent_card_url: https://example.com/card +---`); + const result = await parseAgentMarkdown(filePath); + expect(result).toHaveLength(1); + expect(result[0]).toEqual({ + kind: 'remote', + name: 'no-trailing-newline', + agent_card_url: 'https://example.com/card', + }); + }); + + it('should throw AgentLoadError if agent name is not a valid slug', async () => { + const filePath = await writeAgentMarkdown(`--- +name: Invalid Name With Spaces +description: Test +--- +Body`); + await expect(parseAgentMarkdown(filePath)).rejects.toThrow( + /Name must be a valid slug/, + ); + }); }); describe('markdownToAgentDefinition', () => { diff --git a/packages/core/src/agents/cli-help-agent.ts b/packages/core/src/agents/cli-help-agent.ts index f774469ac3..cf65819252 100644 --- a/packages/core/src/agents/cli-help-agent.ts +++ b/packages/core/src/agents/cli-help-agent.ts @@ -80,7 +80,9 @@ export const CliHelpAgent = ( ? '### Sub-Agents (Local & Remote)\n' + "User defined sub-agents are defined in `.gemini/agents/` or `~/.gemini/agents/` as .md files. These files contain YAML frontmatter for metadata, and the Markdown body becomes the agent's system prompt (`system_prompt`). Always reference the types and properties outlined here directly when answering questions about sub-agents.\n" + '- **Local Agent:** `kind = "local"`, `name`, `description`, `system_prompt`, and optional `tools`, `model`, `temperate`, `max_turns`, `timeout_mins`.\n' + - '- **Remote Agent (A2A):** `kind = "remote"`, `name`, `agent_card_url`. Remote Agents do not use `system_prompt`. Multiple remote agents can be defined by using a YAML array at the top level of the frontmatter. **Note:** When users ask about "remote agents", they are referring to this Agent2Agent functionality, which is completely distinct from MCP servers.\n\n' + '- **Remote Agent (A2A):** `kind = "remote"`, `name`, `agent_card_url`. Remote Agents do not use `system_prompt`. Multiple remote agents can be defined by using a YAML array at the top level of the frontmatter. **Note:** When users ask about "remote agents", they are referring to this Agent2Agent functionality, which is completely distinct from MCP servers.\n' + + '- **Agent Names:** Must be valid slugs (lowercase letters, numbers, hyphens, and underscores only).\n' + + '- **User Commands:** The user can manage agents using `/agents list` to see all available agents and `/agents refresh` to reload the registry after modifying definition files. You (the agent) cannot run these commands.\n\n' : '') + '### Instructions\n' + "1. **Explore Documentation**: Use the `get_internal_docs` tool to find answers. If you don't know where to start, call `get_internal_docs()` without arguments to see the full list of available documentation files.\n" + diff --git a/packages/core/src/skills/skillLoader.ts b/packages/core/src/skills/skillLoader.ts index 354467734b..995d8160f0 100644 --- a/packages/core/src/skills/skillLoader.ts +++ b/packages/core/src/skills/skillLoader.ts @@ -29,7 +29,8 @@ export interface SkillDefinition { isBuiltin?: boolean; } -export const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)/; +export const FRONTMATTER_REGEX = + /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?/; /** * Discovers and loads all skills in the provided directory.