mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-10 22:21:22 -07:00
fix: properly use systemMessage for hooks in UI (#16250)
This commit is contained in:
@@ -94,15 +94,15 @@ If the hook exits with `0`, the CLI attempts to parse `stdout` as JSON.
|
||||
|
||||
### Common Output Fields
|
||||
|
||||
| Field | Type | Description |
|
||||
| :------------------- | :-------- | :----------------------------------------------------------------------- |
|
||||
| `decision` | `string` | One of: `allow`, `deny`, `block`, `ask`, `approve`. |
|
||||
| `reason` | `string` | Explanation shown to the **agent** when a decision is `deny` or `block`. |
|
||||
| `systemMessage` | `string` | Message displayed to the **user** in the CLI terminal. |
|
||||
| `continue` | `boolean` | If `false`, immediately terminates the agent loop for this turn. |
|
||||
| `stopReason` | `string` | Message shown to the user when `continue` is `false`. |
|
||||
| `suppressOutput` | `boolean` | If `true`, the hook execution is hidden from the CLI transcript. |
|
||||
| `hookSpecificOutput` | `object` | Container for event-specific data (see below). |
|
||||
| Field | Type | Description |
|
||||
| :------------------- | :-------- | :------------------------------------------------------------------------------------- |
|
||||
| `decision` | `string` | One of: `allow`, `deny`, `block`, `ask`, `approve`. |
|
||||
| `reason` | `string` | Explanation shown to the **agent** when a decision is `deny` or `block`. |
|
||||
| `systemMessage` | `string` | Message displayed in Gemini CLI terminal to provide warning or context to the **user** |
|
||||
| `continue` | `boolean` | If `false`, immediately terminates the agent loop for this turn. |
|
||||
| `stopReason` | `string` | Message shown to the user when `continue` is `false`. |
|
||||
| `suppressOutput` | `boolean` | If `true`, the hook execution is hidden from the CLI transcript. |
|
||||
| `hookSpecificOutput` | `object` | Container for event-specific data (see below). |
|
||||
|
||||
### `hookSpecificOutput` Reference
|
||||
|
||||
|
||||
@@ -349,7 +349,7 @@ export async function runNonInteractive({
|
||||
} else if (event.type === GeminiEventType.Error) {
|
||||
throw event.value.error;
|
||||
} else if (event.type === GeminiEventType.AgentExecutionStopped) {
|
||||
const stopMessage = `Agent execution stopped: ${event.value.reason}`;
|
||||
const stopMessage = `Agent execution stopped: ${event.value.systemMessage?.trim() || event.value.reason}`;
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`${stopMessage}\n`);
|
||||
}
|
||||
@@ -369,7 +369,7 @@ export async function runNonInteractive({
|
||||
}
|
||||
return;
|
||||
} else if (event.type === GeminiEventType.AgentExecutionBlocked) {
|
||||
const blockMessage = `Agent execution blocked: ${event.value.reason}`;
|
||||
const blockMessage = `Agent execution blocked: ${event.value.systemMessage?.trim() || event.value.reason}`;
|
||||
if (config.getOutputFormat() === OutputFormat.TEXT) {
|
||||
process.stderr.write(`[WARNING] ${blockMessage}\n`);
|
||||
}
|
||||
|
||||
@@ -2800,7 +2800,38 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
|
||||
describe('Agent Execution Events', () => {
|
||||
it('should handle AgentExecutionStopped event', async () => {
|
||||
it('should handle AgentExecutionStopped event with systemMessage', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.AgentExecutionStopped,
|
||||
value: {
|
||||
reason: 'hook-reason',
|
||||
systemMessage: 'Custom stop message',
|
||||
},
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderTestHook();
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('test stop');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: 'Agent execution stopped: Custom stop message',
|
||||
},
|
||||
expect.any(Number),
|
||||
);
|
||||
expect(result.current.streamingState).toBe(StreamingState.Idle);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle AgentExecutionStopped event by falling back to reason when systemMessage is missing', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
@@ -2828,7 +2859,37 @@ describe('useGeminiStream', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle AgentExecutionBlocked event', async () => {
|
||||
it('should handle AgentExecutionBlocked event with systemMessage', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
type: ServerGeminiEventType.AgentExecutionBlocked,
|
||||
value: {
|
||||
reason: 'hook-reason',
|
||||
systemMessage: 'Custom block message',
|
||||
},
|
||||
};
|
||||
})(),
|
||||
);
|
||||
|
||||
const { result } = renderTestHook();
|
||||
|
||||
await act(async () => {
|
||||
await result.current.submitQuery('test block');
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockAddItem).toHaveBeenCalledWith(
|
||||
{
|
||||
type: MessageType.WARNING,
|
||||
text: 'Agent execution blocked: Custom block message',
|
||||
},
|
||||
expect.any(Number),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle AgentExecutionBlocked event by falling back to reason when systemMessage is missing', async () => {
|
||||
mockSendMessageStream.mockReturnValue(
|
||||
(async function* () {
|
||||
yield {
|
||||
|
||||
@@ -794,7 +794,7 @@ export const useGeminiStream = (
|
||||
);
|
||||
|
||||
const handleAgentExecutionStoppedEvent = useCallback(
|
||||
(reason: string, userMessageTimestamp: number) => {
|
||||
(reason: string, userMessageTimestamp: number, systemMessage?: string) => {
|
||||
if (pendingHistoryItemRef.current) {
|
||||
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
|
||||
setPendingHistoryItem(null);
|
||||
@@ -802,7 +802,7 @@ export const useGeminiStream = (
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.INFO,
|
||||
text: `Agent execution stopped: ${reason}`,
|
||||
text: `Agent execution stopped: ${systemMessage?.trim() || reason}`,
|
||||
},
|
||||
userMessageTimestamp,
|
||||
);
|
||||
@@ -812,7 +812,7 @@ export const useGeminiStream = (
|
||||
);
|
||||
|
||||
const handleAgentExecutionBlockedEvent = useCallback(
|
||||
(reason: string, userMessageTimestamp: number) => {
|
||||
(reason: string, userMessageTimestamp: number, systemMessage?: string) => {
|
||||
if (pendingHistoryItemRef.current) {
|
||||
addItem(pendingHistoryItemRef.current, userMessageTimestamp);
|
||||
setPendingHistoryItem(null);
|
||||
@@ -820,7 +820,7 @@ export const useGeminiStream = (
|
||||
addItem(
|
||||
{
|
||||
type: MessageType.WARNING,
|
||||
text: `Agent execution blocked: ${reason}`,
|
||||
text: `Agent execution blocked: ${systemMessage?.trim() || reason}`,
|
||||
},
|
||||
userMessageTimestamp,
|
||||
);
|
||||
@@ -861,12 +861,14 @@ export const useGeminiStream = (
|
||||
handleAgentExecutionStoppedEvent(
|
||||
event.value.reason,
|
||||
userMessageTimestamp,
|
||||
event.value.systemMessage,
|
||||
);
|
||||
break;
|
||||
case ServerGeminiEventType.AgentExecutionBlocked:
|
||||
handleAgentExecutionBlockedEvent(
|
||||
event.value.reason,
|
||||
userMessageTimestamp,
|
||||
event.value.systemMessage,
|
||||
);
|
||||
break;
|
||||
case ServerGeminiEventType.ChatCompressed:
|
||||
|
||||
@@ -68,11 +68,11 @@ const MAX_TURNS = 100;
|
||||
type BeforeAgentHookReturn =
|
||||
| {
|
||||
type: GeminiEventType.AgentExecutionStopped;
|
||||
value: { reason: string };
|
||||
value: { reason: string; systemMessage?: string };
|
||||
}
|
||||
| {
|
||||
type: GeminiEventType.AgentExecutionBlocked;
|
||||
value: { reason: string };
|
||||
value: { reason: string; systemMessage?: string };
|
||||
}
|
||||
| { additionalContext: string | undefined }
|
||||
| undefined;
|
||||
@@ -146,6 +146,7 @@ export class GeminiClient {
|
||||
type: GeminiEventType.AgentExecutionStopped,
|
||||
value: {
|
||||
reason: hookOutput.getEffectiveReason(),
|
||||
systemMessage: hookOutput.systemMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -155,6 +156,7 @@ export class GeminiClient {
|
||||
type: GeminiEventType.AgentExecutionBlocked,
|
||||
value: {
|
||||
reason: hookOutput.getEffectiveReason(),
|
||||
systemMessage: hookOutput.systemMessage,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -811,6 +813,7 @@ export class GeminiClient {
|
||||
type: GeminiEventType.AgentExecutionStopped,
|
||||
value: {
|
||||
reason: hookOutput.getEffectiveReason(),
|
||||
systemMessage: hookOutput.systemMessage,
|
||||
},
|
||||
};
|
||||
return turn;
|
||||
@@ -822,6 +825,7 @@ export class GeminiClient {
|
||||
type: GeminiEventType.AgentExecutionBlocked,
|
||||
value: {
|
||||
reason: continueReason,
|
||||
systemMessage: hookOutput.systemMessage,
|
||||
},
|
||||
};
|
||||
const continueRequest = [{ text: continueReason }];
|
||||
|
||||
@@ -78,6 +78,7 @@ export type ServerGeminiAgentExecutionStoppedEvent = {
|
||||
type: GeminiEventType.AgentExecutionStopped;
|
||||
value: {
|
||||
reason: string;
|
||||
systemMessage?: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -85,6 +86,7 @@ export type ServerGeminiAgentExecutionBlockedEvent = {
|
||||
type: GeminiEventType.AgentExecutionBlocked;
|
||||
value: {
|
||||
reason: string;
|
||||
systemMessage?: string;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user