chore: update channels

This commit is contained in:
Jack Wotherspoon
2026-03-26 22:13:53 -04:00
parent 3980d90e0f
commit d695803f1f
9 changed files with 61 additions and 32 deletions
+1
View File
@@ -575,6 +575,7 @@ describe('gemini.tsx main function kitty protocol', () => {
rawOutput: undefined,
acceptRawOutputRisk: undefined,
isCommand: undefined,
channels: undefined,
});
await act(async () => {
-7
View File
@@ -285,13 +285,6 @@ export async function main() {
});
}
if (argv.channels && argv.channels.length > 0) {
coreEvents.emitFeedback(
'info',
`Channels listening for messages: ${argv.channels.join(', ')}`,
);
}
// Check for invalid input combinations early to prevent crashes
if (argv.promptInteractive && !process.stdin.isTTY) {
writeToStderr(
+7 -17
View File
@@ -37,6 +37,7 @@ import {
} from './types.js';
import { checkPermissions } from './hooks/atCommandProcessor.js';
import { MessageType, StreamingState } from './types.js';
import { theme } from './semantic-colors.js';
import { ToolActionsProvider } from './contexts/ToolActionsContext.js';
import {
type StartupWarning,
@@ -66,7 +67,6 @@ import {
flattenMemory,
type MemoryChangedPayload,
type ChannelMessagePayload,
activeChannels,
writeToStdout,
disableMouseEvents,
enterAlternateScreen,
@@ -1233,22 +1233,6 @@ Logging in with Google... Restarting Gemini CLI to continue.
};
}, [channelsEnabled, addMessage]);
// Warn about requested channels whose MCP servers did not declare capability.
const channelWarningFired = useRef(false);
useEffect(() => {
if (!isMcpReady || !channelsEnabled || channelWarningFired.current) return;
channelWarningFired.current = true;
const requested = config.getChannels();
for (const name of requested) {
if (!activeChannels.has(name)) {
coreEvents.emitFeedback(
'warning',
`Channel "${name}" was requested but the MCP server did not declare channel capability.`,
);
}
}
}, [isMcpReady, channelsEnabled, config]);
cancelHandlerRef.current = useCallback(
(shouldRestorePrompt: boolean = true) => {
if (isToolAwaitingConfirmation(pendingHistoryItems)) {
@@ -2011,10 +1995,16 @@ Logging in with Google... Restarting Gemini CLI to continue.
);
}
const isChannel = payload.style === 'channel';
historyManager.addItem(
{
type,
text: payload.message,
...(isChannel && {
icon: '» ',
color: theme.text.secondary,
marginTop: 0,
}),
},
Date.now(),
);
@@ -119,6 +119,7 @@ export const HistoryItemDisplay: React.FC<HistoryItemDisplayProps> = ({
secondaryText={itemForDisplay.secondaryText}
icon={itemForDisplay.icon}
color={itemForDisplay.color}
marginTop={itemForDisplay.marginTop}
marginBottom={itemForDisplay.marginBottom}
/>
)}
@@ -14,6 +14,7 @@ interface InfoMessageProps {
secondaryText?: string;
icon?: string;
color?: string;
marginTop?: number;
marginBottom?: number;
}
@@ -22,6 +23,7 @@ export const InfoMessage: React.FC<InfoMessageProps> = ({
secondaryText,
icon,
color,
marginTop,
marginBottom,
}) => {
color ??= theme.status.warning;
@@ -29,7 +31,11 @@ export const InfoMessage: React.FC<InfoMessageProps> = ({
const prefixWidth = prefix.length;
return (
<Box flexDirection="row" marginTop={1} marginBottom={marginBottom ?? 0}>
<Box
flexDirection="row"
marginTop={marginTop ?? 1}
marginBottom={marginBottom ?? 0}
>
<Box width={prefixWidth}>
<Text color={color}>{prefix}</Text>
</Box>
@@ -7,6 +7,7 @@
import type React from 'react';
import { Box, Text } from 'ink';
import { theme } from '../../semantic-colors.js';
import { RenderInline } from '../../utils/InlineMarkdownRenderer.js';
import type { ChannelInfo } from '../../types.js';
interface ChannelsListProps {
@@ -18,10 +19,11 @@ export const ChannelsList: React.FC<ChannelsListProps> = ({ channels }) => {
return (
<Box flexDirection="column" marginBottom={1}>
<Text color={theme.text.primary}>No active channels.</Text>
<Text color="gray">
{
"MCP servers must declare `experimental['gemini/channel']` in their capabilities to act as channels."
}
<Text color={theme.text.secondary}>
<RenderInline
text="Use `--channels <name>` to listen for channel messages from MCP servers."
defaultColor={theme.text.secondary}
/>
</Text>
</Box>
);
@@ -41,9 +43,11 @@ export const ChannelsList: React.FC<ChannelsListProps> = ({ channels }) => {
<Text bold color={theme.text.accent}>
{channel.displayName || channel.name} ({channel.name})
</Text>
<Text color="gray">
<Text color={theme.text.secondary}>
Direction:{' '}
<Text color={channel.supportsReply ? 'green' : 'gray'}>
<Text
color={channel.supportsReply ? 'green' : theme.text.secondary}
>
{channel.supportsReply ? 'two-way' : 'one-way'}
</Text>
</Text>
+1
View File
@@ -173,6 +173,7 @@ export type HistoryItemInfo = HistoryItemBase & {
secondaryText?: string;
icon?: string;
color?: string;
marginTop?: number;
marginBottom?: number;
};
+21
View File
@@ -58,6 +58,7 @@ import {
type TelemetryTarget,
} from '../telemetry/index.js';
import { coreEvents, CoreEvent } from '../utils/events.js';
import { activeChannels } from '../channels/types.js';
import { tokenLimit } from '../core/tokenLimits.js';
import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
@@ -1380,6 +1381,26 @@ export class Config implements McpContext, AgentLoopContext {
debugLogger.error('Error initializing MCP clients:', result.reason);
}
}
// Report channel status after all MCP servers have initialized.
if (this.channels.length > 0) {
const active = this.channels.filter((name) => activeChannels.has(name));
if (active.length > 0) {
coreEvents.emitFeedback(
'info',
`Channels listening for messages: ${active.join(', ')}`,
undefined,
{ style: 'channel' },
);
}
for (const name of this.channels) {
if (!activeChannels.has(name)) {
coreEvents.emitFeedback(
'warning',
`Channel "${name}" was requested but the MCP server did not declare channel capability.`,
);
}
}
}
});
if (!this.interactive || this.acpMode) {
+13 -1
View File
@@ -41,6 +41,12 @@ export interface UserFeedbackPayload {
* or verbose output, while keeping the 'message' field clean for end users.
*/
error?: unknown;
/**
* Optional semantic style hint for the UI.
* 'channel' renders with secondary text color and a » icon,
* suitable for channel status messages.
*/
style?: 'channel';
}
/**
@@ -285,8 +291,14 @@ export class CoreEventEmitter extends EventEmitter<CoreEvents> {
severity: FeedbackSeverity,
message: string,
error?: unknown,
options?: { style?: 'channel' },
): void {
const payload: UserFeedbackPayload = { severity, message, error };
const payload: UserFeedbackPayload = {
severity,
message,
error,
...options,
};
this._emitOrQueue(CoreEvent.UserFeedback, payload);
}