From c1f551f3092e653c63391741574e9b0acec4909c Mon Sep 17 00:00:00 2001 From: Your Name Date: Mon, 9 Mar 2026 03:14:19 +0000 Subject: [PATCH] logs --- packages/core/src/core/geminiChat.ts | 39 +++++++++++++++++++++ packages/core/src/core/sideEffectService.ts | 7 ++++ packages/core/src/tools/checkpoint-state.ts | 2 ++ packages/core/src/tools/compress.ts | 2 ++ packages/core/src/tools/distill-result.ts | 39 +++++++++------------ 5 files changed, 67 insertions(+), 22 deletions(-) diff --git a/packages/core/src/core/geminiChat.ts b/packages/core/src/core/geminiChat.ts index a09cf171be..21f97d5fdf 100644 --- a/packages/core/src/core/geminiChat.ts +++ b/packages/core/src/core/geminiChat.ts @@ -4,6 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ +import * as fs from 'node:fs'; +import * as path from 'node:path'; + // DISCLAIMER: This is a copied version of https://github.com/googleapis/js-genai/blob/main/src/chats.ts with the intention of working around a key bug // where function responses are not treated as "valid" responses: https://b.corp.google.com/issues/420354090 @@ -167,6 +170,39 @@ export class GeminiChat { ); } + /** + * Logs the projected history sent to the API to a side-channel file for anomaly analysis. + */ + private logRequestHistory( + requestContents: Content[], + promptId: string, + ): void { + try { + const logDir = path.join( + this.config.storage.getProjectTempDir(), + 'request_history_logs', + this.config.getSessionId(), + ); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + + const timestamp = Date.now(); + const logFile = path.join( + logDir, + `request_${timestamp}_${promptId}.json`, + ); + fs.writeFileSync(logFile, JSON.stringify(requestContents, null, 2)); + debugLogger.debug( + `[PROJECT CLARITY] Request history logged to ${logFile}`, + ); + } catch (error) { + debugLogger.warn( + `[PROJECT CLARITY] Failed to log request history: ${error}`, + ); + } + } + /** * Marks a specific tool call ID for elision from the history. */ @@ -242,6 +278,9 @@ export class GeminiChat { const requestContents = this.historyManager.getHistoryForRequest(userContent); + // PROJECT CLARITY: Side-channel request logging. + this.logRequestHistory(requestContents, prompt_id); + const stream = async function* ( this: GeminiChat, ): AsyncGenerator { diff --git a/packages/core/src/core/sideEffectService.ts b/packages/core/src/core/sideEffectService.ts index 3199b64c3a..d03fad5f08 100644 --- a/packages/core/src/core/sideEffectService.ts +++ b/packages/core/src/core/sideEffectService.ts @@ -5,6 +5,7 @@ */ import type { Content } from '@google/genai'; +import { debugLogger } from '../utils/debugLogger.js'; /** * Types of side-effects that can be triggered by tools or the system. @@ -74,6 +75,12 @@ export class SideEffectService { * Queues a side-effect for later application. */ queueSideEffect(effect: SideEffect): void { + debugLogger.debug(`[PROJECT CLARITY] Queuing side-effect: ${effect.type}`, { + payload: + effect.type === SideEffectType.REPLACE_HISTORY + ? '' + : effect.payload, + }); this.pendingSideEffects.push(effect); } diff --git a/packages/core/src/tools/checkpoint-state.ts b/packages/core/src/tools/checkpoint-state.ts index 351f3d1b72..4729aa9a86 100644 --- a/packages/core/src/tools/checkpoint-state.ts +++ b/packages/core/src/tools/checkpoint-state.ts @@ -18,6 +18,7 @@ import { import { CHECKPOINT_STATE_DEFINITION } from './definitions/coreTools.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; import type { Config } from '../config/config.js'; +import { debugLogger } from '../utils/debugLogger.js'; interface CheckpointStateParams { [CHECKPOINT_STATE_PARAM_SUMMARY]: string; @@ -43,6 +44,7 @@ class CheckpointStateInvocation extends BaseToolInvocation< override async execute(): Promise { const summary = this.params[CHECKPOINT_STATE_PARAM_SUMMARY]; + debugLogger.debug(`[PROJECT CLARITY] Executing CheckpointStateTool with summary length: ${summary.length}`); const chat = this.config.getGeminiClient().getChat(); const previousSummary = chat.getContinuityAnchor(); diff --git a/packages/core/src/tools/compress.ts b/packages/core/src/tools/compress.ts index 775f500924..216fce165a 100644 --- a/packages/core/src/tools/compress.ts +++ b/packages/core/src/tools/compress.ts @@ -20,6 +20,7 @@ import type { Config } from '../config/config.js'; import type { GeminiChat } from '../core/geminiChat.js'; import { CompressionStatus } from '../core/compression-status.js'; import type { ShellExecutionConfig } from 'src/services/shellExecutionService.js'; +import { debugLogger } from '../utils/debugLogger.js'; class CompressInvocation extends BaseToolInvocation< Record, @@ -50,6 +51,7 @@ class CompressInvocation extends BaseToolInvocation< if (!callId) { throw new Error('Critical error: callId is required for context compression elision.'); } + debugLogger.debug(`[PROJECT CLARITY] Executing CompressTool (callId: ${callId})`); try { const continuityService = this.config.getContinuityCompressionService(); const snapshot = await continuityService.generateSnapshot( diff --git a/packages/core/src/tools/distill-result.ts b/packages/core/src/tools/distill-result.ts index 382086dfe7..3bf9d67a7b 100644 --- a/packages/core/src/tools/distill-result.ts +++ b/packages/core/src/tools/distill-result.ts @@ -9,22 +9,19 @@ import { BaseToolInvocation, Kind, type ToolInvocation, - type ToolResult, type ToolLiveOutput, + type ToolResult, } from './tools.js'; -import { - DISTILL_RESULT_TOOL_NAME, - DISTILL_RESULT_PARAM_REVISED_TEXT, -} from './tool-names.js'; +import { DISTILL_RESULT_TOOL_NAME } from './tool-names.js'; import { DISTILL_RESULT_DEFINITION } from './definitions/coreTools.js'; import type { MessageBus } from '../confirmation-bus/message-bus.js'; import type { Config } from '../config/config.js'; import type { GeminiChat } from '../core/geminiChat.js'; -import { saveTruncatedToolOutput } from '../utils/fileUtils.js'; -import type { ShellExecutionConfig } from '../index.js'; +import { debugLogger } from '../utils/debugLogger.js'; +import { saveTruncatedToolOutput } from '../utils/tool-output-helper.js'; interface DistillResultParams { - [DISTILL_RESULT_PARAM_REVISED_TEXT]: string; + revised_text: string; } class DistillResultInvocation extends BaseToolInvocation< @@ -43,30 +40,24 @@ class DistillResultInvocation extends BaseToolInvocation< } override getDescription(): string { - return 'Distills the last tool output to reduce context entropy.'; + return 'Distills the most recent tool output in the history.'; } override async execute( _signal: AbortSignal, _updateOutput?: (output: ToolLiveOutput) => void, - _shellExecutionConfig?: ShellExecutionConfig, + _shellExecutionConfig?: any, ownCallId?: string, ): Promise { - if (!ownCallId) { - throw new Error('Critical error: ownCallId is required for distill_result elision.'); - } - const revisedText = this.params[DISTILL_RESULT_PARAM_REVISED_TEXT]; - const sideEffects = this.config.getSideEffectService(); - const history = this.chat.getComprehensiveHistory(); + const revisedText = this.params.revised_text; - // 1. Find the last tool response (user message with functionResponse parts) + debugLogger.debug(`[PROJECT CLARITY] Executing DistillResultTool (ownCallId: ${ownCallId})`); + + // 1. Find the target: the last function response in history. + const history = this.chat.getHistory(); let lastToolResponseIndex = -1; for (let i = history.length - 1; i >= 0; i--) { - const content = history[i]; - if ( - content.role === 'user' && - content.parts?.some((p) => p.functionResponse) - ) { + if (history[i].parts?.some((p) => p.functionResponse)) { lastToolResponseIndex = i; break; } @@ -91,6 +82,10 @@ class DistillResultInvocation extends BaseToolInvocation< throw new Error('Target call ID missing from tool response.'); } + debugLogger.debug(`[PROJECT CLARITY] Distill target identified: ${targetCallId} at index ${lastToolResponseIndex}`); + + const sideEffects = this.config.getSideEffectService(); + // 2. Elide all turns between that tool response and the current turn. if (ownCallId) { sideEffects.elideBetween(targetCallId, ownCallId);