Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
c6172aa
Add AI screenshot artifacts and color scheme capture
shaunandrews Jun 28, 2026
e160e95
Improve local screenshot data URL conversion
shaunandrews Jun 29, 2026
63ce0e8
Hide screenshot payloads in Studio Code transcript
shaunandrews Jun 29, 2026
df716ff
Forward tool execute args and emit chat artifacts for all remote stud…
shaunandrews Jul 3, 2026
cf66b73
Add shared media artifact helpers and payload-line stripper to @studi…
shaunandrews Jul 3, 2026
4bbec3c
Stop embedding screenshot payload text now that artifacts emit struct…
shaunandrews Jul 3, 2026
686cb25
Store screenshots in a per-session sidecar directory instead of os tmp
shaunandrews Jul 3, 2026
7cd5632
Harden inline artifact rendering in the classic conversation view
shaunandrews Jul 3, 2026
85b2b81
Render screenshot artifacts inline in the legacy Studio Code conversa…
shaunandrews Jul 3, 2026
e007735
Share the colorScheme schema and media emulation across screenshot tools
shaunandrews Jul 3, 2026
a6c3f8f
Load screenshot artifacts as data URLs instead of object URLs
shaunandrews Jul 3, 2026
ae49653
Handle take_screenshot in terminal sessions: file link, inline images…
bcotrim Jul 6, 2026
c4c70d6
Address review feedback: shared media helpers, sturdier schema consta…
bcotrim Jul 6, 2026
d2563cc
Merge branch 'trunk' into extract-screenshot-artifacts
bcotrim Jul 6, 2026
2094470
Skip payload-line stripping when no marker is present
bcotrim Jul 6, 2026
a2598eb
Never let a failed artifact emit error a successful tool result
bcotrim Jul 6, 2026
c812fcb
Log when screenshot storage falls back to the temp directory
bcotrim Jul 6, 2026
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Forward tool execute args and emit chat artifacts for all remote stud…
…io tools

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
  • Loading branch information
shaunandrews and claude committed Jul 3, 2026
commit df716ffaebd9af61bcf8046eaa4f72e280a355c5
11 changes: 4 additions & 7 deletions apps/cli/ai/runtimes/pi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -527,15 +527,12 @@ function buildAgentTools(
];

if ( isRemoteSite ) {
const screenshotTool = withChatArtifactEmission(
takeScreenshotTool,
chatArtifactsEnabled
) as unknown as AgentToolAny;
const remoteStudioTools = [ takeScreenshotTool, createSiteTool, pullSiteTool ].map( ( tool ) =>
withChatArtifactEmission( tool, chatArtifactsEnabled )
);
return [
createWpcomRequestTool( config.wpcomAccessToken!, config.activeSite!.wpcomSiteId! ),
screenshotTool,
createSiteTool,
pullSiteTool,
...remoteStudioTools,
...remoteScratchTools,
...askUserTool,
...skillTool,
Expand Down
60 changes: 60 additions & 0 deletions apps/cli/ai/tests/tools.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@ import {
captureCommandOutput,
resolveStudioToolDefinitions,
studioToolDefinitions,
withChatArtifactEmission,
} from '../tools';
import { createSiteTool } from '../tools/create-site';
import { enrichPreviewListOutput } from '../tools/list-previews';
import type { AnyStudioAgentTool } from '../tools/define-tool';

vi.mock( 'cli/ai/block-validator', () => ( {
validateBlocks: vi.fn(),
Expand Down Expand Up @@ -1022,6 +1025,63 @@ describe( 'Studio AI MCP tools', () => {
expect( getTextContent( result ) ).toContain( '"name": "My Site"' );
} );

describe( 'withChatArtifactEmission', () => {
const widget = { type: 'media', widgetProps: { mediaKind: 'image' } };
const makeFakeTool = () => {
const execute = vi.fn().mockResolvedValue( {
content: [ { type: 'text', text: 'ok' } ],
details: { studioArtifacts: [ widget ] },
} );
return {
tool: { name: 'fake_tool', execute } as unknown as AnyStudioAgentTool,
execute,
};
};

it( 'forwards execute arguments and preserves details when emitting', async () => {
const { tool, execute } = makeFakeTool();
const signal = new AbortController().signal;
const onUpdate = () => {};

const wrapped = withChatArtifactEmission( tool, true );
const result = await wrapped.execute( 'tool-call-9', { a: 1 } as never, signal, onUpdate );

expect( execute ).toHaveBeenCalledWith( 'tool-call-9', { a: 1 }, signal, onUpdate );
expect( result.details ).toEqual( { studioArtifacts: [ widget ] } );
expect( emitEvent ).toHaveBeenCalledWith(
expect.objectContaining( {
type: 'chat.artifact',
artifact: expect.objectContaining( { widgets: [ widget ] } ),
} )
);
} );

it( 'returns the tool unchanged when chat artifacts are disabled', () => {
const { tool } = makeFakeTool();
expect( withChatArtifactEmission( tool, false ) ).toBe( tool );
} );

it( 'emits site_create artifacts when wrapped directly (remote tool list)', async () => {
const wrapped = withChatArtifactEmission( createSiteTool, true );

await executeTool( wrapped, { name: 'My Site' } );

expect( emitEvent ).toHaveBeenCalledWith(
expect.objectContaining( {
type: 'chat.artifact',
artifact: expect.objectContaining( {
widgets: [
expect.objectContaining( {
type: 'site-preview',
widgetProps: expect.objectContaining( { siteId: 'site-123' } ),
} ),
],
} ),
} )
);
} );
} );

it( 'notifies JSON-mode callers when site_create selects the created site', async () => {
const onSiteSelected = vi.fn();
setLocalSiteSelectedCallback( onSiteSelected );
Expand Down
16 changes: 9 additions & 7 deletions apps/cli/ai/tools/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import { updatePreviewTool } from './update-preview';
import { validateBlocksTool } from './validate-blocks';
import { waitForAnnotationsTool } from './wait-for-annotations';
import { runWpCliTool } from './wp-cli';
import type { AnyStudioAgentTool } from './define-tool';
import type { AnyStudioAgentTool, StudioToolResultDetails } from './define-tool';

export { captureCommandOutput } from './utils';

Expand Down Expand Up @@ -93,14 +93,16 @@ export function withChatArtifactEmission< TTool extends AnyStudioAgentTool >(
tool: TTool,
emitChatArtifacts: boolean
): TTool {
if ( ! emitChatArtifacts ) {
return tool;
}
return {
...tool,
execute: async ( _toolCallId, params ) => {
const result = await tool.rawHandler( params );
if ( emitChatArtifacts ) {
await emitChatArtifactWidgets( result.studioArtifacts );
}
return { content: result.content, details: undefined };
execute: async ( toolCallId, params, signal, onUpdate ) => {
const result = await tool.execute( toolCallId, params, signal, onUpdate );
const details = result.details as StudioToolResultDetails | undefined;
await emitChatArtifactWidgets( details?.studioArtifacts );
return result;
},
};
}
Loading