Agent Skills: Status Bar Integration for Skill Counts (#15741)

This commit is contained in:
N. Taylor Mullen
2026-01-02 15:01:31 -08:00
committed by GitHub
parent c0ccb22460
commit e78c3fe4f0
4 changed files with 36 additions and 17 deletions
@@ -144,10 +144,16 @@ const createMockConfig = (overrides = {}) => ({
getDebugMode: vi.fn(() => false), getDebugMode: vi.fn(() => false),
getAccessibility: vi.fn(() => ({})), getAccessibility: vi.fn(() => ({})),
getMcpServers: vi.fn(() => ({})), getMcpServers: vi.fn(() => ({})),
getMcpClientManager: vi.fn().mockImplementation(() => ({ getToolRegistry: () => ({
getBlockedMcpServers: vi.fn(), getTool: vi.fn(),
getMcpServers: vi.fn(), }),
})), getSkillManager: () => ({
getSkills: () => [],
}),
getMcpClientManager: () => ({
getMcpServers: () => ({}),
getBlockedMcpServers: () => [],
}),
...overrides, ...overrides,
}); });
@@ -120,6 +120,7 @@ export const Composer = () => {
blockedMcpServers={ blockedMcpServers={
config.getMcpClientManager()?.getBlockedMcpServers() ?? [] config.getMcpClientManager()?.getBlockedMcpServers() ?? []
} }
skillCount={config.getSkillManager().getSkills().length}
/> />
) )
)} )}
@@ -34,14 +34,16 @@ describe('<ContextSummaryDisplay />', () => {
openFiles: [{ path: '/a/b/c', timestamp: Date.now() }], openFiles: [{ path: '/a/b/c', timestamp: Date.now() }],
}, },
}, },
skillCount: 1,
}; };
it('should render on a single line on a wide screen', () => { it('should render on a single line on a wide screen', () => {
const { lastFrame, unmount } = renderWithWidth(120, baseProps); const { lastFrame, unmount } = renderWithWidth(120, baseProps);
const output = lastFrame()!; const output = lastFrame()!;
expect(output).toContain( expect(output).toContain(
'Using: 1 open file (ctrl+g to view) | 1 GEMINI.md file | 1 MCP server', '1 open file (ctrl+g to view) | 1 GEMINI.md file | 1 MCP server | 1 skill',
); );
expect(output).not.toContain('Using:');
// Check for absence of newlines // Check for absence of newlines
expect(output.includes('\n')).toBe(false); expect(output.includes('\n')).toBe(false);
unmount(); unmount();
@@ -51,10 +53,10 @@ describe('<ContextSummaryDisplay />', () => {
const { lastFrame, unmount } = renderWithWidth(60, baseProps); const { lastFrame, unmount } = renderWithWidth(60, baseProps);
const output = lastFrame()!; const output = lastFrame()!;
const expectedLines = [ const expectedLines = [
' Using:', ' - 1 open file (ctrl+g to view)',
' - 1 open file (ctrl+g to view)', ' - 1 GEMINI.md file',
' - 1 GEMINI.md file', ' - 1 MCP server',
' - 1 MCP server', ' - 1 skill',
]; ];
const actualLines = output.split('\n'); const actualLines = output.split('\n');
expect(actualLines).toEqual(expectedLines); expect(actualLines).toEqual(expectedLines);
@@ -86,9 +88,10 @@ describe('<ContextSummaryDisplay />', () => {
geminiMdFileCount: 0, geminiMdFileCount: 0,
contextFileNames: [], contextFileNames: [],
mcpServers: {}, mcpServers: {},
skillCount: 0,
}; };
const { lastFrame, unmount } = renderWithWidth(60, props); const { lastFrame, unmount } = renderWithWidth(60, props);
const expectedLines = [' Using:', ' - 1 open file (ctrl+g to view)']; const expectedLines = [' - 1 open file (ctrl+g to view)'];
const actualLines = lastFrame()!.split('\n'); const actualLines = lastFrame()!.split('\n');
expect(actualLines).toEqual(expectedLines); expect(actualLines).toEqual(expectedLines);
unmount(); unmount();
@@ -17,6 +17,7 @@ interface ContextSummaryDisplayProps {
mcpServers?: Record<string, MCPServerConfig>; mcpServers?: Record<string, MCPServerConfig>;
blockedMcpServers?: Array<{ name: string; extensionName: string }>; blockedMcpServers?: Array<{ name: string; extensionName: string }>;
ideContext?: IdeContext; ideContext?: IdeContext;
skillCount: number;
} }
export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
@@ -25,6 +26,7 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
mcpServers, mcpServers,
blockedMcpServers, blockedMcpServers,
ideContext, ideContext,
skillCount,
}) => { }) => {
const { columns: terminalWidth } = useTerminalSize(); const { columns: terminalWidth } = useTerminalSize();
const isNarrow = isNarrowWidth(terminalWidth); const isNarrow = isNarrowWidth(terminalWidth);
@@ -36,7 +38,8 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
geminiMdFileCount === 0 && geminiMdFileCount === 0 &&
mcpServerCount === 0 && mcpServerCount === 0 &&
blockedMcpServerCount === 0 && blockedMcpServerCount === 0 &&
openFileCount === 0 openFileCount === 0 &&
skillCount === 0
) { ) {
return <Text> </Text>; // Render an empty space to reserve height return <Text> </Text>; // Render an empty space to reserve height
} }
@@ -83,15 +86,23 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
return parts.join(', '); return parts.join(', ');
})(); })();
const summaryParts = [openFilesText, geminiMdText, mcpText].filter(Boolean); const skillText = (() => {
if (skillCount === 0) {
return '';
}
return `${skillCount} skill${skillCount > 1 ? 's' : ''}`;
})();
const summaryParts = [openFilesText, geminiMdText, mcpText, skillText].filter(
Boolean,
);
if (isNarrow) { if (isNarrow) {
return ( return (
<Box flexDirection="column" paddingX={1}> <Box flexDirection="column" paddingX={1}>
<Text color={theme.text.secondary}>Using:</Text>
{summaryParts.map((part, index) => ( {summaryParts.map((part, index) => (
<Text key={index} color={theme.text.secondary}> <Text key={index} color={theme.text.secondary}>
{' '}- {part} - {part}
</Text> </Text>
))} ))}
</Box> </Box>
@@ -100,9 +111,7 @@ export const ContextSummaryDisplay: React.FC<ContextSummaryDisplayProps> = ({
return ( return (
<Box paddingX={1}> <Box paddingX={1}>
<Text color={theme.text.secondary}> <Text color={theme.text.secondary}>{summaryParts.join(' | ')}</Text>
Using: {summaryParts.join(' | ')}
</Text>
</Box> </Box>
); );
}; };