diff --git a/docs/hooks/reference.md b/docs/hooks/reference.md index c534922818..828d2faa7f 100644 --- a/docs/hooks/reference.md +++ b/docs/hooks/reference.md @@ -198,9 +198,8 @@ request format. - `hookSpecificOutput.llm_response`: A **Synthetic Response** object. If provided, the CLI skips the LLM call entirely and uses this as the response. - `hookSpecificOutput.additionalContext`: (`string`) Text that is **appended** - to the end of the user message (wrapped in `` tags). If - `llm_request` also modifies contents, it will be appended to the modified - contents. + to the end of the user message. If `llm_request` also modifies contents, it + will be appended to the modified contents. - `decision`: Set to `"deny"` to block the request and abort the turn. - **Exit Code 2 (Block Turn)**: Aborts the turn and skips the LLM call. Uses `stderr` as the error message. diff --git a/packages/cli/src/gemini.tsx b/packages/cli/src/gemini.tsx index 4b43d7d81b..e4b589a540 100644 --- a/packages/cli/src/gemini.tsx +++ b/packages/cli/src/gemini.tsx @@ -665,8 +665,9 @@ export async function main() { const additionalContext = result.getAdditionalContext(); if (additionalContext) { // Prepend context to input (System Context -> Stdin -> Question) - const wrappedContext = `${additionalContext}`; - input = input ? `${wrappedContext}\n\n${input}` : wrappedContext; + input = input + ? `${additionalContext}\n\n${input}` + : additionalContext; } } } diff --git a/packages/cli/src/ui/AppContainer.tsx b/packages/cli/src/ui/AppContainer.tsx index d58ed45d89..cc6c035433 100644 --- a/packages/cli/src/ui/AppContainer.tsx +++ b/packages/cli/src/ui/AppContainer.tsx @@ -432,13 +432,11 @@ export const AppContainer = (props: AppContainerProps) => { } const additionalContext = result.getAdditionalContext(); - const geminiClient = config.getGeminiClient(); + if (additionalContext && geminiClient) { await geminiClient.addHistory({ role: 'user', - parts: [ - { text: `${additionalContext}` }, - ], + parts: [{ text: `\n\n${additionalContext}` }], }); } } diff --git a/packages/core/src/core/client.ts b/packages/core/src/core/client.ts index 8922c977f2..2590ca4784 100644 --- a/packages/core/src/core/client.ts +++ b/packages/core/src/core/client.ts @@ -913,10 +913,7 @@ export class GeminiClient { const additionalContext = hookResult.additionalContext; if (additionalContext) { const requestArray = Array.isArray(request) ? request : [request]; - request = [ - ...requestArray, - { text: `${additionalContext}` }, - ]; + request = [...requestArray, { text: `\n\n${additionalContext}` }]; } } } diff --git a/packages/core/src/core/coreToolHookTriggers.ts b/packages/core/src/core/coreToolHookTriggers.ts index c2748cbd0a..b480ec848a 100644 --- a/packages/core/src/core/coreToolHookTriggers.ts +++ b/packages/core/src/core/coreToolHookTriggers.ts @@ -220,7 +220,7 @@ export async function executeToolWithHooks( // Add additional context from hooks to the tool result const additionalContext = afterOutput?.getAdditionalContext(); if (additionalContext) { - const wrappedContext = `\n\n${additionalContext}`; + const wrappedContext = `\n\n${additionalContext}`; if (typeof toolResult.llmContent === 'string') { toolResult.llmContent += wrappedContext; } else if (Array.isArray(toolResult.llmContent)) { diff --git a/packages/core/src/hooks/hookAggregator.test.ts b/packages/core/src/hooks/hookAggregator.test.ts index 8f22919656..279148239c 100644 --- a/packages/core/src/hooks/hookAggregator.test.ts +++ b/packages/core/src/hooks/hookAggregator.test.ts @@ -467,7 +467,9 @@ describe('HookAggregator', () => { expect( aggregated.finalOutput?.hookSpecificOutput?.['additionalContext'], - ).toBe('Context 1\nContext 2'); + ).toBe( + '\nContext 1\n\n\nContext 2\n', + ); }); }); @@ -516,7 +518,40 @@ describe('HookAggregator', () => { expect(aggregated.success).toBe(true); expect( aggregated.finalOutput?.hookSpecificOutput?.['additionalContext'], - ).toBe('Context from hook 1\nContext from hook 2'); + ).toBe( + '\nContext from hook 1\n\n\nContext from hook 2\n', + ); + }); + + it('should sanitize additional context by escaping < and > tags', () => { + const results: HookExecutionResult[] = [ + { + hookConfig: { + type: HookType.Command, + command: 'test-hook', + }, + eventName: HookEventName.AfterTool, + success: true, + output: { + hookSpecificOutput: { + hookEventName: 'AfterTool', + additionalContext: 'context with bold and