diff --git a/.github/workflows/chained_e2e.yml b/.github/workflows/chained_e2e.yml index 23f5a41983..1c3fb5123c 100644 --- a/.github/workflows/chained_e2e.yml +++ b/.github/workflows/chained_e2e.yml @@ -274,7 +274,7 @@ jobs: UV_THREADPOOL_SIZE: '32' NODE_ENV: 'test' shell: 'pwsh' - run: 'npx vitest run --root integration-tests hooks-system.test.ts' + run: 'npx vitest run --root integration-tests hooks-system.test.ts hooks-agent-flow.test.ts --test-timeout 600000' evals: name: 'Evals (ALWAYS_PASSING)' diff --git a/integration-tests/hooks-agent-flow.test.ts b/integration-tests/hooks-agent-flow.test.ts index 6876ce84bc..efe886b1a8 100644 --- a/integration-tests/hooks-agent-flow.test.ts +++ b/integration-tests/hooks-agent-flow.test.ts @@ -113,10 +113,9 @@ describe('Hooks Agent Flow', () => { } `; - const scriptPath = join(rig.testDir!, 'after_agent_verify.cjs'); - writeFileSync(scriptPath, hookScript); + const scriptPath = rig.createScript('after_agent_verify.cjs', hookScript); - await rig.setup('should receive prompt and response in AfterAgent hook', { + rig.setup('should receive prompt and response in AfterAgent hook', { settings: { hooksConfig: { enabled: true, @@ -127,7 +126,7 @@ describe('Hooks Agent Flow', () => { hooks: [ { type: 'command', - command: `node "${scriptPath}"`, + command: normalizePath(`node "${scriptPath}"`)!, timeout: 5000, }, ], @@ -157,7 +156,7 @@ describe('Hooks Agent Flow', () => { }); it('should process clearContext in AfterAgent hook output', async () => { - await rig.setup('should process clearContext in AfterAgent hook output', { + rig.setup('should process clearContext in AfterAgent hook output', { fakeResponsesPath: join( import.meta.dirname, 'hooks-system.after-agent.responses', @@ -176,11 +175,10 @@ describe('Hooks Agent Flow', () => { fs.writeFileSync(${JSON.stringify(messageCountFile)}, JSON.stringify(counts)); console.log(JSON.stringify({ decision: 'allow' })); `; - const beforeModelScriptPath = join( - rig.testDir!, + const beforeModelScriptPath = rig.createScript( 'before_model_counter.cjs', + beforeModelScript, ); - writeFileSync(beforeModelScriptPath, beforeModelScript); const afterAgentScript = ` console.log(JSON.stringify({ @@ -192,10 +190,12 @@ describe('Hooks Agent Flow', () => { } })); `; - const afterAgentScriptPath = join(rig.testDir!, 'after_agent_clear.cjs'); - writeFileSync(afterAgentScriptPath, afterAgentScript); + const afterAgentScriptPath = rig.createScript( + 'after_agent_clear.cjs', + afterAgentScript, + ); - await rig.setup('should process clearContext in AfterAgent hook output', { + rig.setup('should process clearContext in AfterAgent hook output', { settings: { hooks: { enabled: true, @@ -204,7 +204,7 @@ describe('Hooks Agent Flow', () => { hooks: [ { type: 'command', - command: `node "${beforeModelScriptPath}"`, + command: normalizePath(`node "${beforeModelScriptPath}"`)!, timeout: 5000, }, ], @@ -215,7 +215,7 @@ describe('Hooks Agent Flow', () => { hooks: [ { type: 'command', - command: `node "${afterAgentScriptPath}"`, + command: normalizePath(`node "${afterAgentScriptPath}"`)!, timeout: 5000, }, ], diff --git a/packages/core/src/core/coreToolScheduler.ts b/packages/core/src/core/coreToolScheduler.ts index ea2cdb7015..0bcaa5c3ed 100644 --- a/packages/core/src/core/coreToolScheduler.ts +++ b/packages/core/src/core/coreToolScheduler.ts @@ -965,6 +965,10 @@ export class CoreToolScheduler { // The active tool is finished. Move it to the completed batch. const completedCall = activeCall as CompletedToolCall; + + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + } + this.completedToolCallsForBatch.push(completedCall); logToolCall(this.config, new ToolCallEvent(completedCall)); diff --git a/packages/core/src/hooks/hookRegistry.ts b/packages/core/src/hooks/hookRegistry.ts index 1e5515a6a0..7d3402d434 100644 --- a/packages/core/src/hooks/hookRegistry.ts +++ b/packages/core/src/hooks/hookRegistry.ts @@ -233,6 +233,13 @@ please review the project settings (.gemini/settings.json) and remove them.`; } as HookRegistryEntry); const isDisabled = disabledHooks.includes(hookName); + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + if (isDisabled) { + } + if (disabledHooks.length > 0) { + } + } + // Add source to hook config hookConfig.source = source; diff --git a/packages/core/src/hooks/hookRunner.ts b/packages/core/src/hooks/hookRunner.ts index 298d09c32e..7f6ef5f339 100644 --- a/packages/core/src/hooks/hookRunner.ts +++ b/packages/core/src/hooks/hookRunner.ts @@ -268,6 +268,9 @@ export class HookRunner { shellConfig.shell, ); + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + } + // Set up environment variables const env = { ...sanitizeEnvironment(process.env, this.config.sanitizationConfig), @@ -332,11 +335,19 @@ export class HookRunner { stderr += data.toString(); }); + child.on('exit', (code, signal) => { + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + } + }); + // Handle process exit child.on('close', (exitCode) => { clearTimeout(timeoutHandle); const duration = Date.now() - startTime; + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + } + if (timedOut) { resolve({ hookConfig, @@ -428,6 +439,8 @@ export class HookRunner { text: string, exitCode: number, ): HookOutput { + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + } if (exitCode === EXIT_CODE_SUCCESS) { // Success - treat as system message or additional context return { diff --git a/packages/core/src/policy/policy-engine.ts b/packages/core/src/policy/policy-engine.ts index 3f386edd8f..0801125614 100644 --- a/packages/core/src/policy/policy-engine.ts +++ b/packages/core/src/policy/policy-engine.ts @@ -360,9 +360,11 @@ export class PolicyEngine { ); if (match) { - debugLogger.debug( - `[PolicyEngine.check] MATCHED rule: toolName=${rule.toolName}, decision=${rule.decision}, priority=${rule.priority}, argsPattern=${rule.argsPattern?.source || 'none'}`, - ); + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + console.log( + `[PolicyEngine.check] MATCHED rule: toolName=${rule.toolName}, decision=${rule.decision}, priority=${rule.priority}, source=${rule.source}`, + ); + } if (isShellCommand && toolName) { const shellResult = await this.checkShellCommand( @@ -389,9 +391,11 @@ export class PolicyEngine { // Default if no rule matched if (decision === undefined) { - debugLogger.debug( - `[PolicyEngine.check] NO MATCH - using default decision: ${this.defaultDecision}`, - ); + if (process.env['CI'] === 'true' || process.env['VERBOSE'] === 'true') { + console.log( + `[PolicyEngine.check] NO MATCH - using default decision: ${this.defaultDecision}`, + ); + } if (toolName && SHELL_TOOL_NAMES.includes(toolName)) { const shellResult = await this.checkShellCommand( toolName, diff --git a/packages/test-utils/src/test-rig.ts b/packages/test-utils/src/test-rig.ts index 4d80ce3909..5d041076b7 100644 --- a/packages/test-utils/src/test-rig.ts +++ b/packages/test-utils/src/test-rig.ts @@ -361,6 +361,9 @@ export class TestRig { this.testDir = join(testFileDir, sanitizedName); this.homeDir = join(testFileDir, sanitizedName + '-home'); + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + } + if (!this._initialized) { // Clean up existing directories from previous runs (e.g. retries) const cleanDir = (dir: string) => { @@ -584,6 +587,9 @@ export class TestRig { }); this._spawnedProcesses.push(child); + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + } + let stdout = ''; let stderr = ''; @@ -838,9 +844,19 @@ export class TestRig { } async cleanup() { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + } + // Kill any interactive runs that are still active for (const run of this._interactiveRuns) { try { + if (process.platform === 'win32') { + // @ts-ignore - access private ptyProcess + const pid = run.ptyProcess?.pid; + if (pid) { + execSync(`taskkill /F /T /PID ${pid}`, { stdio: 'ignore' }); + } + } await run.kill(); } catch (error) { if (env['VERBOSE'] === 'true') { @@ -854,6 +870,9 @@ export class TestRig { for (const child of this._spawnedProcesses) { if (child.exitCode === null && child.signalCode === null) { try { + if (process.platform === 'win32' && child.pid) { + execSync(`taskkill /F /T /PID ${child.pid}`, { stdio: 'ignore' }); + } child.kill('SIGKILL'); } catch (error) { if (env['VERBOSE'] === 'true') { @@ -876,20 +895,24 @@ export class TestRig { // Clean up test directory and home directory if (this.testDir && !env['KEEP_OUTPUT']) { try { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + } fs.rmSync(this.testDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors - if (env['VERBOSE'] === 'true') { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { console.warn('Cleanup warning:', (error as Error).message); } } } if (this.homeDir && !env['KEEP_OUTPUT']) { try { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + } fs.rmSync(this.homeDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors - if (env['VERBOSE'] === 'true') { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { console.warn('Cleanup warning:', (error as Error).message); } } @@ -1225,6 +1248,10 @@ export class TestRig { }[] = []; for (const logData of parsedLogs) { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + if (logData.attributes?.['event.name']?.includes('tool')) { + } + } // Look for tool call logs if ( logData.attributes && @@ -1377,6 +1404,10 @@ export class TestRig { }[] = []; for (const logData of parsedLogs) { + if (env['VERBOSE'] === 'true' || env['CI'] === 'true') { + if (logData.attributes?.['event.name']?.includes('hook')) { + } + } // Look for tool call logs if ( logData.attributes &&