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
19 changes: 7 additions & 12 deletions apps/cli/ai/runtimes/pi/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,25 +320,20 @@ async function createStudioAgentSession(
imageGenerationEnabled,
visionEnabled
);
const systemPrompt = buildSystemPrompt(
isRemoteSite
const systemPrompt = buildSystemPrompt( {
...( isRemoteSite
? {
remoteSite: {
name: config.activeSite!.name,
url: config.activeSite!.url ?? '',
id: config.activeSite!.wpcomSiteId!,
},
userInstructions,
visionEnabled,
}
: {
chatArtifactsEnabled,
runtime,
userInstructions,
visionEnabled,
tools: tools.map( toolPromptContribution ),
}
);
: { chatArtifactsEnabled, runtime } ),
userInstructions,
visionEnabled,
tools: tools.map( toolPromptContribution ),
} );

const pendingWork = getPendingWork( config.session );
const toolDefinitions = tools.map( ( tool ) =>
Expand Down
29 changes: 14 additions & 15 deletions apps/cli/ai/system-prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ export interface ToolPromptContribution {

export interface BuildSystemPromptOptions {
remoteSite?: RemoteSiteContext;
// The tools registered for this session; the local prompt lists them and
// their guidelines instead of hand-writing the list.
// The tools registered for this session; the prompt lists them and their
// guidelines instead of hand-writing the list.
tools?: ToolPromptContribution[];
// True when a Studio UI is attached and can receive chat artifact events.
chatArtifactsEnabled?: boolean;
Expand All @@ -37,9 +37,10 @@ export function buildSystemPrompt( options?: BuildSystemPromptOptions ): string
const userInstructionsSection = buildUserInstructionsSection( options?.userInstructions );

const visionEnabled = options?.visionEnabled ?? true;
const toolSections = renderToolSections( options?.tools ?? [] );

if ( options?.remoteSite ) {
return `${ buildRemoteIntro( options.remoteSite, visionEnabled ) }
return `${ buildRemoteIntro( options.remoteSite, visionEnabled, toolSections ) }

${ REMOTE_CONTENT_GUIDELINES }

Expand All @@ -51,7 +52,7 @@ ${ REMOTE_DESIGN_GUIDELINES }${ userInstructionsSection }
chatArtifactsEnabled: options?.chatArtifactsEnabled ?? false,
runtime: options?.runtime,
visionEnabled,
tools: options?.tools ?? [],
toolSections,
} ) }

${ LOCAL_SKILL_ROUTING }${ userInstructionsSection }
Expand All @@ -78,7 +79,11 @@ The user saved these standing instructions in Studio's settings. They apply to e
${ instructions }`;
}

function buildRemoteIntro( site: RemoteSiteContext, visionEnabled: boolean ): string {
function buildRemoteIntro(
site: RemoteSiteContext,
visionEnabled: boolean,
toolSections: string
): string {
const verifyStep = visionEnabled
? `Use take_screenshot with \`viewport: "all"\` to capture the site on desktop and mobile viewports in one call. Check spacing, alignment, colors, contrast, and layout. Fix any issues.`
: `You cannot view images, so verify from the rendered DOM with inspect_design instead. Fix any issues.`;
Expand All @@ -89,13 +94,7 @@ IMPORTANT: You MUST use the wpcom_request tool to manage this site. Do NOT use W
IMPORTANT: Before doing ANY work, you MUST first check the site's plan by calling \`GET /\` (apiNamespace: \`""\`). The \`plan.product_slug\` field indicates the plan. If the site is on a free plan (e.g. \`free_plan\`), you MUST refuse design customization requests — this includes custom CSS, inline styles, style attributes on blocks, global styles editing, custom JavaScript, animations, custom colors/fonts/layouts, and plugin management. Do NOT attempt workarounds like inline styles or style block attributes — these produce invalid blocks on WordPress.com. Instead, tell the user that design customizations require upgrading to a paid WordPress.com plan and STOP. Do not proceed with the design task.
IMPORTANT: ${ PLAN_DATA_GUARDRAIL }

## Available Tools

- **wpcom_request**: Manage the active WordPress.com site through WordPress REST API and WordPress.com REST API endpoints, passing its ID as siteId.
- **take_screenshot**: Take a full-page screenshot of a URL (supports desktop, mobile, or \`viewport: "all"\` for both)
- **Read/Write/Edit/Ls**: Local scratch-file tools within Studio app data. They do not modify the remote site directly.
- **site_create**: Create a new local WordPress site (use this to create a local site before pulling remote content into it)
- **site_pull**: Pull the remote WordPress.com site to a local site. Create a local site first with site_create, then pull into it. Specify sync options (all, sqls, uploads, plugins, themes, contents).
${ toolSections }

## Workflow

Expand All @@ -109,6 +108,7 @@ IMPORTANT: ${ PLAN_DATA_GUARDRAIL }
- Always confirm destructive operations (deleting posts, deactivating plugins, etc.) with the user before proceeding.
- When creating content, follow WordPress best practices for block-based content and the remote block content guidelines below.
- If a requested operation fails, check the error message and suggest alternatives.
- To bring the site into Studio, create a local site with site_create, then site_pull this site into it.
- Explore the API — if you're unsure about an endpoint, load the \`wpcom-remote-management\` skill and try a lightweight GET request first to discover available data.`;
}

Expand Down Expand Up @@ -162,9 +162,8 @@ function buildLocalIntro( options: {
chatArtifactsEnabled: boolean;
runtime?: SiteRuntime;
visionEnabled: boolean;
tools: ToolPromptContribution[];
toolSections: string;
} ): string {
const toolSections = renderToolSections( options.tools );
const postContentGuidance = getPostContentGuidance( options.runtime );
const terminalScreenshotSection = `

Expand Down Expand Up @@ -217,7 +216,7 @@ One file per turn: a single \`Write\`, or a single \`Edit\` call (read-only \`si

For long CSS or page-content files (>~200 lines), load the \`block-content\` skill and use its skeleton-first recipes instead of writing the full payload at once.

${ toolSections }${ screenshotSection }
${ options.toolSections }${ screenshotSection }

## General rules

Expand Down
8 changes: 6 additions & 2 deletions apps/cli/ai/tests/system-prompt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,12 @@ describe( 'buildSystemPrompt', () => {
] );
} );

it( 'lists the registered tools and their guidelines', () => {
it.each( [
[ 'local', {} ],
[ 'remote', { remoteSite } ],
] )( 'lists the registered tools and their guidelines (%s)', ( _, options ) => {
const prompt = buildSystemPrompt( {
...options,
tools: [
{ name: 'wp_cli', promptSnippet: 'Run WP-CLI commands on a running site' },
{ name: 'Edit', promptGuidelines: [ 'Use one Edit call with multiple entries' ] },
Expand All @@ -164,7 +168,7 @@ describe( 'buildSystemPrompt', () => {
);
expect( prompt ).toContain( '## Tool guidelines\n\n- Use one Edit call with multiple entries' );
expect( prompt ).not.toContain( '- hidden' );
expect( buildSystemPrompt( {} ) ).not.toContain( '## Tool guidelines' );
expect( buildSystemPrompt( options ) ).not.toContain( '## Tool guidelines' );
} );

it( 'mentions refresh_browser only when chat artifacts are enabled', () => {
Expand Down
4 changes: 4 additions & 0 deletions apps/cli/ai/tools/wpcom-request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,5 +290,9 @@ export const wpcomRequestTool = defineTool(
`WP.com API request failed (${ args.method } ${ args.path }): ${ getErrorMessage( error ) }`
);
}
},
{
promptSnippet:
'Manage a WordPress.com site through WordPress REST API and WordPress.com REST API endpoints, passing its ID as siteId.',
}
);
Loading