Skip to content
16 changes: 16 additions & 0 deletions apps/cli/ai/tests/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,22 @@ describe( 'Studio AI MCP tools', () => {
expect( getProgressCallback() ).toBe( previousCallback );
} );

it( 'forwards progress messages to the previous callback during command execution', async () => {
const previousCallback = vi.fn();
setProgressCallback( previousCallback );

vi.mocked( runCreatePreviewCommand ).mockImplementation( async () => {
const currentCallback = getProgressCallback();
currentCallback?.( 'Creating preview…' );
currentCallback?.( 'Almost done…' );
} );

await getTool( 'preview_create' ).handler( { nameOrPath: 'My Site' } as never, null );

expect( previousCallback ).toHaveBeenCalledWith( 'Creating preview…', undefined );
expect( previousCallback ).toHaveBeenCalledWith( 'Almost done…', undefined );
} );

it( 'rejects shell syntax in wp_cli post content before dispatching to WP-CLI', async () => {
vi.mocked( isServerRunning ).mockResolvedValue( {
name: 'site-123',
Expand Down
3 changes: 2 additions & 1 deletion apps/cli/ai/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,9 @@ export async function captureCommandOutput( fn: () => Promise< void > ): Promise
consoleOutput += args.map( String ).join( ' ' ) + '\n';
};
process.exitCode = undefined;
setProgressCallback( ( message ) => {
setProgressCallback( ( message, update ) => {
progressMessages.push( message );
previousCallback?.( message, update );
} );

try {
Expand Down
14 changes: 12 additions & 2 deletions apps/cli/ai/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1321,11 +1321,19 @@ export class AiChatUI {
this.tui.requestRender();
}

setLoaderMessage( message: string ): void {
private lastProgressText: Text | null = null;

setLoaderMessage( message: string, update?: boolean ): void {
if ( ! message ) {
return;
}
this.messages.addChild( new Text( ' ' + chalk.dim( '⎿ ' ) + chalk.dim( message ), 0, 0 ) );
const formatted = ' ' + chalk.dim( '⎿ ' ) + chalk.dim( message );
if ( update && this.lastProgressText ) {
this.lastProgressText.setText( formatted );
} else {
this.lastProgressText = new Text( formatted, 0, 0 );
this.messages.addChild( this.lastProgressText );
}
this.tui.requestRender();
}

Expand Down Expand Up @@ -1354,6 +1362,7 @@ export class AiChatUI {
this.loader.stop();
this.tui.removeChild( this.loader );
this.loaderVisible = false;
this.lastProgressText = null;
this.tui.requestRender();
}
}
Expand Down Expand Up @@ -1740,6 +1749,7 @@ export class AiChatUI {
private showToolUse( toolLabel: string ): void {
this.showLoader( this.randomThinkingMessage() );
this.stopToolDotBlink();
this.lastProgressText = null;
this.toolDotLabel = toolLabel;
this.toolDotText = new Text( '\n ' + '⏺' + ' ' + toolLabel, 0, 0 );
this.messages.addChild( this.toolDotText );
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/commands/ai/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,9 +157,9 @@ export async function runCommand(
);
}

setProgressCallback( ( message ) => {
setProgressCallback( ( message, update ) => {
const timestamp = new Date().toISOString();
ui.setLoaderMessage( message );
ui.setLoaderMessage( message, update );
void persist( ( recorder ) => recorder.recordToolProgress( message, timestamp ) );
} );

Expand Down
2 changes: 1 addition & 1 deletion apps/cli/commands/pull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export async function runCommand(

// Backup phase: 0-50%
const backupProgress = Math.round( status.percent * 0.5 );
logger.spinner.text = sprintf( __( 'Creating remote backup… (%d%%)' ), backupProgress );
logger.reportProgress( sprintf( __( 'Creating remote backup… (%d%%)' ), backupProgress ) );

await new Promise( ( resolve ) => setTimeout( resolve, SYNC_POLL_INTERVAL_MS ) );
}
Expand Down
6 changes: 3 additions & 3 deletions apps/cli/commands/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ export async function runCommand(
onProgress: ( percent ) => {
// Upload phase: 20-40%
const progress = Math.round( 20 + percent * 0.2 );
logger.spinner.text = sprintf( __( 'Uploading archive… (%d%%)' ), progress );
logger.reportProgress( sprintf( __( 'Uploading archive… (%d%%)' ), progress ) );
},
} );

Expand All @@ -183,7 +183,7 @@ export async function runCommand(
}

// Initiate import: 40%
logger.spinner.text = sprintf( __( 'Initiating import… (%d%%)' ), 40 );
logger.reportProgress( sprintf( __( 'Initiating import… (%d%%)' ), 40 ) );
await initiateImport( token.accessToken, remoteSite.id, attachmentId, {
optionsToSync,
specificSelectionPaths,
Expand Down Expand Up @@ -237,7 +237,7 @@ export async function runCommand(
stalledAttempts++;
}

logger.spinner.text = sprintf( '%s (%d%%)', statusMessage, roundedProgress );
logger.reportProgress( sprintf( '%s (%d%%)', statusMessage, roundedProgress ) );

await new Promise( ( resolve ) => setTimeout( resolve, SYNC_POLL_INTERVAL_MS ) );
}
Expand Down
4 changes: 2 additions & 2 deletions apps/cli/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import ora, { Ora } from 'ora';

const isIpcMode = Boolean( process.send );

type ProgressCallback = ( message: string ) => void;
type ProgressCallback = ( message: string, update?: boolean ) => void;
let progressCallback: ProgressCallback | null = null;

export function setProgressCallback( callback: ProgressCallback | null ): void {
Expand Down Expand Up @@ -73,7 +73,7 @@ export class Logger< T extends string > {
}

if ( progressCallback ) {
progressCallback!( message );
progressCallback!( message, true );
return;
}

Expand Down
Loading