mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-23 00:01:24 -07:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ea49d486b | |||
| 4d4fc80f25 | |||
| dfa56c37b4 | |||
| 4249de5e23 | |||
| 536bbdf73f | |||
| 201efbd07f | |||
| 8ae078b4d7 | |||
| d84b173330 | |||
| 33d654e7d9 | |||
| 6eb100cc41 | |||
| dca1ef09cb | |||
| 616c944350 | |||
| c0f919310e | |||
| 92a09c8d8d | |||
| ecae62e25a | |||
| abb4583554 | |||
| fc7a269f0a | |||
| 9c85ef1fda | |||
| bd60ba9e68 | |||
| 2e466bd4cb | |||
| 55778583b9 | |||
| 661bd39362 | |||
| 04eb2ee9a3 | |||
| c6f6fb00c9 | |||
| 02eff4efb3 | |||
| db9eef50ef |
@@ -7,5 +7,15 @@
|
||||
},
|
||||
"general": {
|
||||
"devtools": true
|
||||
},
|
||||
"agents": {
|
||||
"overrides": {
|
||||
"browser_agent": { "enabled": true }
|
||||
},
|
||||
"browser": {
|
||||
"headless": true,
|
||||
"sessionMode": "isolated",
|
||||
"allowedDomains": ["*.com"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,16 +34,20 @@ describe('extension install', () => {
|
||||
writeFileSync(testServerPath, extension);
|
||||
try {
|
||||
const result = await rig.runCommand(
|
||||
['extensions', 'install', `${rig.testDir!}`],
|
||||
['--debug', 'extensions', 'install', `${rig.testDir!}`],
|
||||
{ stdin: 'y\n' },
|
||||
);
|
||||
expect(result).toContain('test-extension-install');
|
||||
|
||||
const listResult = await rig.runCommand(['extensions', 'list']);
|
||||
const listResult = await rig.runCommand([
|
||||
'--debug',
|
||||
'extensions',
|
||||
'list',
|
||||
]);
|
||||
expect(listResult).toContain('test-extension-install');
|
||||
writeFileSync(testServerPath, extensionUpdate);
|
||||
const updateResult = await rig.runCommand(
|
||||
['extensions', 'update', `test-extension-install`],
|
||||
['--debug', 'extensions', 'update', `test-extension-install`],
|
||||
{ stdin: 'y\n' },
|
||||
);
|
||||
expect(updateResult).toContain('0.0.2');
|
||||
|
||||
@@ -66,7 +66,7 @@ describe('extension reloading', () => {
|
||||
}
|
||||
|
||||
const result = await rig.runCommand(
|
||||
['extensions', 'install', `${rig.testDir!}`],
|
||||
['--debug', 'extensions', 'install', `${rig.testDir!}`],
|
||||
{ stdin: 'y\n' },
|
||||
);
|
||||
expect(result).toContain('test-extension');
|
||||
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
ValidationRequiredError,
|
||||
type AdminControlsSettings,
|
||||
debugLogger,
|
||||
isHeadlessMode,
|
||||
} from '@google/gemini-cli-core';
|
||||
|
||||
import { loadCliConfig, parseArguments } from './config/config.js';
|
||||
@@ -296,6 +297,7 @@ export async function main() {
|
||||
const isDebugMode = cliConfig.isDebugMode(argv);
|
||||
const consolePatcher = new ConsolePatcher({
|
||||
stderr: true,
|
||||
interactive: isHeadlessMode() ? false : true,
|
||||
debugMode: isDebugMode,
|
||||
onNewMessage: (msg) => {
|
||||
coreEvents.emitConsoleLog(msg.type, msg.content);
|
||||
|
||||
@@ -65,6 +65,7 @@ export async function runNonInteractive({
|
||||
return promptIdContext.run(prompt_id, async () => {
|
||||
const consolePatcher = new ConsolePatcher({
|
||||
stderr: true,
|
||||
interactive: false,
|
||||
debugMode: config.getDebugMode(),
|
||||
onNewMessage: (msg) => {
|
||||
coreEvents.emitConsoleLog(msg.type, msg.content);
|
||||
|
||||
@@ -0,0 +1,236 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2026 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
import { describe, it, expect, vi, afterEach } from 'vitest';
|
||||
import { ConsolePatcher } from './ConsolePatcher.js';
|
||||
|
||||
describe('ConsolePatcher', () => {
|
||||
let patcher: ConsolePatcher;
|
||||
const onNewMessage = vi.fn();
|
||||
|
||||
afterEach(() => {
|
||||
if (patcher) {
|
||||
patcher.cleanup();
|
||||
}
|
||||
vi.restoreAllMocks();
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should patch and restore console methods', () => {
|
||||
const beforeLog = console.log;
|
||||
const beforeWarn = console.warn;
|
||||
const beforeError = console.error;
|
||||
const beforeDebug = console.debug;
|
||||
const beforeInfo = console.info;
|
||||
|
||||
patcher = new ConsolePatcher({ onNewMessage, debugMode: false });
|
||||
patcher.patch();
|
||||
|
||||
expect(console.log).not.toBe(beforeLog);
|
||||
expect(console.warn).not.toBe(beforeWarn);
|
||||
expect(console.error).not.toBe(beforeError);
|
||||
expect(console.debug).not.toBe(beforeDebug);
|
||||
expect(console.info).not.toBe(beforeInfo);
|
||||
|
||||
patcher.cleanup();
|
||||
|
||||
expect(console.log).toBe(beforeLog);
|
||||
expect(console.warn).toBe(beforeWarn);
|
||||
expect(console.error).toBe(beforeError);
|
||||
expect(console.debug).toBe(beforeDebug);
|
||||
expect(console.info).toBe(beforeInfo);
|
||||
});
|
||||
|
||||
describe('Interactive mode', () => {
|
||||
it('should ignore log and info when it is not interactive and debugMode is false', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: false,
|
||||
interactive: false,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.log('test log');
|
||||
console.info('test info');
|
||||
expect(onNewMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should not ignore log and info when it is not interactive and debugMode is true', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: true,
|
||||
interactive: false,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.log('test log');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'log',
|
||||
content: 'test log',
|
||||
count: 1,
|
||||
});
|
||||
|
||||
console.info('test info');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'info',
|
||||
content: 'test info',
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not ignore log and info when it is interactive', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: false,
|
||||
interactive: true,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.log('test log');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'log',
|
||||
content: 'test log',
|
||||
count: 1,
|
||||
});
|
||||
|
||||
console.info('test info');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'info',
|
||||
content: 'test info',
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when stderr is false', () => {
|
||||
it('should call onNewMessage for log, warn, error, and info', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: false,
|
||||
stderr: false,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.log('test log');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'log',
|
||||
content: 'test log',
|
||||
count: 1,
|
||||
});
|
||||
|
||||
console.warn('test warn');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'warn',
|
||||
content: 'test warn',
|
||||
count: 1,
|
||||
});
|
||||
|
||||
console.error('test error');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'error',
|
||||
content: 'test error',
|
||||
count: 1,
|
||||
});
|
||||
|
||||
console.info('test info');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'info',
|
||||
content: 'test info',
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should not call onNewMessage for debug when debugMode is false', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: false,
|
||||
stderr: false,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.debug('test debug');
|
||||
expect(onNewMessage).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call onNewMessage for debug when debugMode is true', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: true,
|
||||
stderr: false,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.debug('test debug');
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'debug',
|
||||
content: 'test debug',
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('should format multiple arguments using util.format', () => {
|
||||
patcher = new ConsolePatcher({
|
||||
onNewMessage,
|
||||
debugMode: false,
|
||||
stderr: false,
|
||||
});
|
||||
patcher.patch();
|
||||
|
||||
console.log('test %s %d', 'string', 123);
|
||||
expect(onNewMessage).toHaveBeenCalledWith({
|
||||
type: 'log',
|
||||
content: 'test string 123',
|
||||
count: 1,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when stderr is true', () => {
|
||||
it('should redirect warn and error to originalConsoleError', () => {
|
||||
const spyError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
patcher = new ConsolePatcher({ debugMode: false, stderr: true });
|
||||
patcher.patch();
|
||||
|
||||
console.warn('test warn');
|
||||
expect(spyError).toHaveBeenCalledWith('test warn');
|
||||
|
||||
console.error('test error');
|
||||
expect(spyError).toHaveBeenCalledWith('test error');
|
||||
});
|
||||
|
||||
it('should redirect log and info to originalConsoleError when debugMode is true', () => {
|
||||
const spyError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
patcher = new ConsolePatcher({ debugMode: true, stderr: true });
|
||||
patcher.patch();
|
||||
|
||||
console.log('test log');
|
||||
expect(spyError).toHaveBeenCalledWith('test log');
|
||||
|
||||
console.info('test info');
|
||||
expect(spyError).toHaveBeenCalledWith('test info');
|
||||
});
|
||||
|
||||
it('should ignore debug when debugMode is false', () => {
|
||||
const spyError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
patcher = new ConsolePatcher({ debugMode: false, stderr: true });
|
||||
patcher.patch();
|
||||
|
||||
console.debug('test debug');
|
||||
expect(spyError).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should redirect debug to originalConsoleError when debugMode is true', () => {
|
||||
const spyError = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
patcher = new ConsolePatcher({ debugMode: true, stderr: true });
|
||||
patcher.patch();
|
||||
|
||||
console.debug('test debug');
|
||||
expect(spyError).toHaveBeenCalledWith('test debug');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,7 @@ interface ConsolePatcherParams {
|
||||
onNewMessage?: (message: Omit<ConsoleMessageItem, 'id'>) => void;
|
||||
debugMode: boolean;
|
||||
stderr?: boolean;
|
||||
interactive?: boolean;
|
||||
}
|
||||
|
||||
export class ConsolePatcher {
|
||||
@@ -49,12 +50,19 @@ export class ConsolePatcher {
|
||||
private patchConsoleMethod =
|
||||
(type: 'log' | 'warn' | 'error' | 'debug' | 'info') =>
|
||||
(...args: unknown[]) => {
|
||||
if (this.params.stderr) {
|
||||
if (type !== 'debug' || this.params.debugMode) {
|
||||
this.originalConsoleError(this.formatArgs(args));
|
||||
// When it is non interactive mode, do not show info logging unless
|
||||
// it is debug mode. default to true if it is undefined.
|
||||
if (this.params.interactive === false) {
|
||||
if ((type === 'info' || type === 'log') && !this.params.debugMode) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (type !== 'debug' || this.params.debugMode) {
|
||||
}
|
||||
// When it is in the debug mode, redirect console output to stderr
|
||||
// depending on if it is stderr only mode.
|
||||
if (type !== 'debug' || this.params.debugMode) {
|
||||
if (this.params.stderr) {
|
||||
this.originalConsoleError(this.formatArgs(args));
|
||||
} else {
|
||||
this.params.onNewMessage?.({
|
||||
type,
|
||||
content: this.formatArgs(args),
|
||||
|
||||
@@ -83,18 +83,55 @@ async function bundle() {
|
||||
}
|
||||
fs.cpSync(srcThirdParty, destThirdParty, {
|
||||
recursive: true,
|
||||
filter: (src) => {
|
||||
// Skip large/unnecessary bundles that are either explicitly excluded
|
||||
// or not required for the browser agent functionality.
|
||||
return (
|
||||
!src.includes('lighthouse-devtools-mcp-bundle.js') &&
|
||||
!src.includes('devtools-formatter-worker.js')
|
||||
);
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.warn(`Warning: third_party assets not found at ${srcThirdParty}`);
|
||||
}
|
||||
|
||||
// Copy watchdog scripts and dependencies
|
||||
const srcTelemetry = path.resolve(
|
||||
__dirname,
|
||||
'../../../node_modules/chrome-devtools-mcp/build/src/telemetry',
|
||||
);
|
||||
const destWatchdog = path.resolve(
|
||||
__dirname,
|
||||
'../dist/bundled/watchdog',
|
||||
);
|
||||
if (fs.existsSync(srcTelemetry)) {
|
||||
fs.mkdirSync(destWatchdog, { recursive: true });
|
||||
// Copy main watchdog directory
|
||||
fs.cpSync(path.join(srcTelemetry, 'watchdog'), destWatchdog, { recursive: true });
|
||||
// Copy shared types needed by watchdog
|
||||
fs.copyFileSync(
|
||||
path.join(srcTelemetry, 'types.js'),
|
||||
path.resolve(__dirname, '../dist/bundled/types.js')
|
||||
);
|
||||
// Copy logger needed by watchdog
|
||||
fs.copyFileSync(
|
||||
path.join(srcTelemetry, '../logger.js'),
|
||||
path.resolve(__dirname, '../dist/bundled/logger.js')
|
||||
);
|
||||
|
||||
// Patch imports in watchdog files to reflect the flattened structure in dist/bundled/
|
||||
const watchdogFiles = fs.readdirSync(destWatchdog);
|
||||
for (const file of watchdogFiles) {
|
||||
if (file.endsWith('.js')) {
|
||||
const filePath = path.join(destWatchdog, file);
|
||||
let content = fs.readFileSync(filePath, 'utf-8');
|
||||
content = content.replace(/\.\.\/\.\.\/logger\.js/g, '../logger.js');
|
||||
content = content.replace(/\.\.\/types\.js/g, '../types.js');
|
||||
fs.writeFileSync(filePath, content);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn(`Warning: telemetry directory not found at ${srcTelemetry}`);
|
||||
}
|
||||
|
||||
// Patch the bundled file to point to the correct watchdog path
|
||||
// The original code uses new URL("./watchdog/main.js", import.meta.url)
|
||||
// which resolves relative to the bundled file.
|
||||
// Our bundling script copies the watchdog to ./watchdog/main.js relative to the bundle.
|
||||
// So the original code should work IF esbuild doesn't mangle import.meta.url.
|
||||
} catch (error) {
|
||||
console.error('Error bundling chrome-devtools-mcp:', error);
|
||||
process.exit(1);
|
||||
|
||||
@@ -386,7 +386,7 @@ export class BrowserManager {
|
||||
})
|
||||
.join(', ');
|
||||
mcpArgs.push(
|
||||
`--chromeArg="--host-rules=MAP * 127.0.0.1, ${exclusionRules}, EXCLUDE 127.0.0.1"`,
|
||||
`--chromeArg=--host-rules=MAP * 127.0.0.1, ${exclusionRules}, EXCLUDE 127.0.0.1`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -427,10 +427,12 @@ export class BrowserManager {
|
||||
}
|
||||
|
||||
this.mcpTransport.onclose = () => {
|
||||
debugLogger.error(
|
||||
'chrome-devtools-mcp transport closed unexpectedly. ' +
|
||||
'The MCP server process may have crashed.',
|
||||
);
|
||||
if (this.rawMcpClient) {
|
||||
debugLogger.error(
|
||||
'chrome-devtools-mcp transport closed unexpectedly. ' +
|
||||
'The MCP server process may have crashed.',
|
||||
);
|
||||
}
|
||||
this.rawMcpClient = undefined;
|
||||
};
|
||||
this.mcpTransport.onerror = (error: Error) => {
|
||||
|
||||
Reference in New Issue
Block a user