Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/cli/src/gemini.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
),
),
patchStdio: vi.fn(() => () => {}),
createInkStdio: vi.fn(() => ({
createWorkingStdio: vi.fn(() => ({
stdout: {
write: vi.fn((...args) =>
process.stdout.write(
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/gemini.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ import {
recordSlowRender,
coreEvents,
CoreEvent,
createInkStdio,
createWorkingStdio,
patchStdio,
writeToStdout,
writeToStderr,
Expand Down Expand Up @@ -203,7 +203,7 @@ export async function startInteractiveUI(
consolePatcher.patch();
registerCleanup(consolePatcher.cleanup);

const { stdout: inkStdout, stderr: inkStderr } = createInkStdio();
const { stdout: inkStdout, stderr: inkStderr } = createWorkingStdio();

// Create wrapper component to use hooks inside render
const AppWrapper = () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/gemini_cleanup.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
...actual,
writeToStdout: vi.fn(),
patchStdio: vi.fn(() => () => {}),
createInkStdio: vi.fn(() => ({
createWorkingStdio: vi.fn(() => ({
stdout: {
write: vi.fn(),
columns: 80,
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/src/nonInteractiveCli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
getMetrics: vi.fn(),
},
coreEvents: mockCoreEvents,
createWorkingStdio: vi.fn(() => ({
stdout: process.stdout,
stderr: process.stderr,
})),
};
});

Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/nonInteractiveCli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
debugLogger,
coreEvents,
CoreEvent,
createWorkingStdio,
} from '@google/gemini-cli-core';

import type { Content, Part } from '@google/genai';
Expand Down Expand Up @@ -70,7 +71,8 @@ export async function runNonInteractive({
coreEvents.emitConsoleLog(msg.type, msg.content);
},
});
const textOutput = new TextOutput();
const { stdout: workingStdout } = createWorkingStdio();
const textOutput = new TextOutput(workingStdout);

const handleUserFeedback = (payload: UserFeedbackPayload) => {
const prefix = payload.severity.toUpperCase();
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/ui/AppContainer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ vi.mock('@google/gemini-cli-core', async (importOriginal) => {
),
),
patchStdio: vi.fn(() => () => {}),
createInkStdio: vi.fn(() => ({
createWorkingStdio: vi.fn(() => ({
stdout: process.stdout,
stderr: process.stderr,
})),
Expand Down
7 changes: 6 additions & 1 deletion packages/cli/src/ui/utils/textOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ import stripAnsi from 'strip-ansi';

export class TextOutput {
private atStartOfLine = true;
private outputStream: NodeJS.WriteStream;

constructor(outputStream: NodeJS.WriteStream = process.stdout) {
this.outputStream = outputStream;
}

/**
* Writes a string to stdout.
Expand All @@ -22,7 +27,7 @@ export class TextOutput {
if (str.length === 0) {
return;
}
process.stdout.write(str);
this.outputStream.write(str);
const strippedStr = stripAnsi(str);
if (strippedStr.length > 0) {
this.atStartOfLine = strippedStr.endsWith('\n');
Expand Down
10 changes: 3 additions & 7 deletions packages/cli/src/zed-integration/zedIntegration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
debugLogger,
ReadManyFilesTool,
getEffectiveModel,
createWorkingStdio,
startupProfiler,
} from '@google/gemini-cli-core';
import * as acp from './acp.js';
Expand All @@ -51,15 +52,10 @@ export async function runZedIntegration(
settings: LoadedSettings,
argv: CliArgs,
) {
const stdout = Writable.toWeb(process.stdout) as WritableStream;
const { stdout: workingStdout } = createWorkingStdio();
const stdout = Writable.toWeb(workingStdout) as WritableStream;
const stdin = Readable.toWeb(process.stdin) as ReadableStream<Uint8Array>;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The removal of the console redirection (console.log = console.error, etc.) might reintroduce a bug where log messages interfere with the Zed ACP (Agent-Client Protocol) communication over stdout. The original comment, which was also removed, explained that this redirection was necessary to ensure logs go to stderr. While consolePatcher is active, there are no listeners for coreEvents in the Zed integration path. This means logs might be lost, or if a default listener is ever added, they could be routed to stdout, breaking the protocol. Please consider restoring the redirection to ensure logs are correctly routed to stderr.

  // Stdout is used to send messages to the client, so console.log/console.info
  // messages to stderr so that they don't interfere with ACP.
  console.log = console.error;
  console.info = console.error;
  console.debug = console.error;

// Stdout is used to send messages to the client, so console.log/console.info
// messages to stderr so that they don't interfere with ACP.
console.log = console.error;
console.info = console.error;
console.debug = console.error;

new acp.AgentSideConnection(
(client: acp.Client) => new GeminiAgent(config, settings, argv, client),
stdout,
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/code_assist/oauth2.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ vi.mock('../utils/browser.js', () => ({
vi.mock('../utils/stdio.js', () => ({
writeToStdout: vi.fn(),
writeToStderr: vi.fn(),
createInkStdio: vi.fn(() => ({
createWorkingStdio: vi.fn(() => ({
stdout: process.stdout,
stderr: process.stderr,
})),
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/code_assist/oauth2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import { FORCE_ENCRYPTED_FILE_ENV_VAR } from '../mcp/token-storage/index.js';
import { debugLogger } from '../utils/debugLogger.js';
import {
writeToStdout,
createInkStdio,
createWorkingStdio,
writeToStderr,
} from '../utils/stdio.js';
import {
Expand Down Expand Up @@ -334,7 +334,7 @@ async function authWithUserCode(client: OAuth2Client): Promise<boolean> {
const code = await new Promise<string>((resolve, _) => {
const rl = readline.createInterface({
input: process.stdin,
output: createInkStdio().stdout,
output: createWorkingStdio().stdout,
terminal: true,
});

Expand Down
10 changes: 5 additions & 5 deletions packages/core/src/utils/stdio.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { patchStdio, createInkStdio } from './stdio.js';
import { patchStdio, createWorkingStdio } from './stdio.js';
import { coreEvents } from './events.js';

vi.mock('./events.js', () => ({
Expand Down Expand Up @@ -53,14 +53,14 @@ describe('stdio utils', () => {
expect(process.stderr.write).toBe(originalStderrWrite);
});

it('createInkStdio writes to real stdout/stderr bypassing patch', () => {
it('createWorkingStdio writes to real stdout/stderr bypassing patch', () => {
const cleanup = patchStdio();
const { stdout: inkStdout, stderr: inkStderr } = createInkStdio();
const { stdout, stderr } = createWorkingStdio();

inkStdout.write('ink stdout');
stdout.write('working stdout');
expect(coreEvents.emitOutput).not.toHaveBeenCalled();

inkStderr.write('ink stderr');
stderr.write('working stderr');
expect(coreEvents.emitOutput).not.toHaveBeenCalled();

cleanup();
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/utils/stdio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@ export function patchStdio(): () => void {
/**
* Creates proxies for process.stdout and process.stderr that use the real write methods
* (writeToStdout and writeToStderr) bypassing any monkey patching.
* This is used by Ink to render to the real output.
* This is used to write to the real output even when stdio is patched.
*/
export function createInkStdio() {
export function createWorkingStdio() {
const inkStdout = new Proxy(process.stdout, {
get(target, prop, receiver) {
if (prop === 'write') {
Expand Down
Loading