import { v4 as uuidv4 } from 'uuid'; import { runV1CommandBatch } from './v1CommandBatchRuntime'; export const V1_BROWSER_CAPABILITIES = ['browser.page.get']; export type V1TerminalStatus = | 'succeeded' | 'failed' | 'denied' | 'rejected' | 'skipped' | 'unsupported' | 'unavailable' | 'expired'; export type V1CommandStatus = V1TerminalStatus; export interface V1GrammarDiscovery { interface_version: number; capabilities: string[]; site_shape?: string; settings_rev?: string; } export type V1CapabilitySnapshotGrammar = V1GrammarDiscovery; export interface V1CapabilitySnapshot { snapshot_id: string; captured_at: string; schema_version: 'v1'; plugin_version: string; site_identity: Record; calling_surface: string; assistant_settings: Record; // V1 grammar capabilities (names only) plus interface version and fingerprints. grammar: V1CapabilitySnapshotGrammar; } export interface V1PluginCommand { command_id: string; capability: string; phase: 'execute' | 'validate'; input: Record; depends_on?: string[]; continue_after_failure?: boolean; display?: { label?: string; target?: string; summary?: string; }; } export interface V1CommandExecutionCommand { command_id: string; capability: string; phase: 'execute' | 'validate'; input: Record; depends_on?: string[]; continue_after_failure?: boolean; approval?: Record; } export interface V1CommandExecutionRequest { batch_id: string; commands: V1CommandExecutionCommand[]; } export interface V1CommandPauseEvent { type: 'v1.command_pause'; pause_id: string; command_batch: { id: string; commands: V1PluginCommand[]; }; } export interface V1CommandResult { command_id: string; capability?: string; status: V1TerminalStatus; data?: unknown; policy?: { decision: string; reason_codes: string[]; }; // V1 grammar results carry meta (site_shape fingerprint) and optional // artifacts instead of policy; forwarded verbatim to the resume payload. meta?: Record; artifacts?: unknown[]; error?: { code: string; message: string; }; } export interface V1ResumePayload { pause_id: string; command_results: V1CommandResult[]; } export interface V1RuntimeArtifact { id: string; kind: 'rendered_screenshot'; mime: string; size_bytes: number; origin: Record; visibility: 'llm_visible' | 'trace_only' | 'user_visible'; retention: Record; llm_projection: Record; } export type V1PauseEvent = V1CommandPauseEvent; export type V1PauseResumePayload = V1ResumePayload; export type V1CommandRuntimeEvent = | { type: 'command_started'; id: string; batch_id: string; label: string; target?: string } | { type: 'approval_needed'; id: string; batch_id: string; label: string; target?: string } | { type: 'approval_granted'; id: string; batch_id: string; label: string; target?: string } | { type: 'approval_rejected'; id: string; batch_id: string; label: string; target?: string } | { type: 'command_completed'; id: string; batch_id: string; ok: boolean; summary?: string }; export interface V1CommandApprovalRequest { batch_id?: string; approval: V1CommandApprovalRequirement; commands: V1PluginCommand[]; results?: V1CommandResult[]; display: { label: string; target?: string; summary?: string; }; } interface V1RuntimeDiscovery { plugin_version?: string; site_identity?: Record; assistant_settings?: Record; grammar: V1GrammarDiscovery; } interface V1CommandExecutionResponse { results?: V1CommandResult[]; } interface V1CommandExecutionClientOptions { signal?: AbortSignal; } export interface V1CommandClient { executeCommands: ( request: V1CommandExecutionRequest, options?: V1CommandExecutionClientOptions, ) => Promise; supportsAbortSignal?: boolean; } export interface V1CommandApprovalRequirement { required?: boolean; group?: string; action?: string; mode?: 'before_execute' | 'after_execute'; [key: string]: unknown; } export interface V1CommandApprovalDecision { approved: boolean; reason?: string; approval?: Record; } export interface V1CommandApprovalPolicy { requireReadApproval: boolean; } export interface V1RuntimeContext { capabilitySnapshot: V1CapabilitySnapshot; approvalPolicy: V1CommandApprovalPolicy; } interface V1CommandExecutionOptions { requestApproval?: (request: V1CommandApprovalRequest) => Promise; approvalPolicy?: V1CommandApprovalPolicy; commandDispatchTimeoutMs?: number; } interface PreparedCommandExecution { commands: V1CommandExecutionCommand[]; terminalResults: Map; } interface V1PreflightDecision { result?: V1CommandResult; requiresApproval: boolean; } interface V1LifecycleLabels { started: string; completed: string; failed: string; skipped: string; target?: string; } export interface V1BrowserCommandRunner { runBrowserCommand: (command: V1CommandExecutionCommand) => Promise>; } export interface V1CommandRuntimeOptions { commandClient?: V1CommandClient; browserCommandRunner?: V1BrowserCommandRunner; approvalPolicy?: V1CommandApprovalPolicy; requestApproval?: (request: V1CommandApprovalRequest) => Promise; emitEvent?: (event: V1CommandRuntimeEvent) => void; commandDispatchTimeoutMs?: number; } const DEFAULT_COMMAND_DISPATCH_TIMEOUT_MS = 45000; export async function fetchPluginV1RuntimeDiscovery(options: { endpoint: string; restNonce: string; fetchImpl?: typeof fetch; }): Promise { const fetchImpl = options.fetchImpl || fetch; const response = await fetchImpl(options.endpoint, { method: 'GET', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': options.restNonce }, }); if (!response.ok) { throw new Error(`Capability discovery failed: ${response.status}`); } return await response.json(); } export function createFetchV1CommandClient(options: { endpoint: string; restNonce: string; fetchImpl?: typeof fetch; }): V1CommandClient { return { supportsAbortSignal: true, executeCommands: async ( request: V1CommandExecutionRequest, clientOptions?: V1CommandExecutionClientOptions, ): Promise => { const fetchImpl = options.fetchImpl || fetch; const requestInit: RequestInit = { method: 'POST', credentials: 'include', headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': options.restNonce }, body: JSON.stringify(request), }; if (clientOptions?.signal) { requestInit.signal = clientOptions.signal; } const response = await fetchImpl(options.endpoint, requestInit); if (!response.ok) { throw new Error(`Plugin command execution failed: ${response.status}`); } return await response.json(); }, }; } export async function buildV1CapabilitySnapshot(options: { fetchJson: () => Promise; callingSurface: string; assistantSettings: Record; requireReadApproval?: boolean; browserCapabilities?: string[]; now?: () => string; }): Promise { return (await buildV1RuntimeContext(options)).capabilitySnapshot; } export async function buildV1RuntimeContext(options: { fetchJson: () => Promise; callingSurface: string; assistantSettings: Record; requireReadApproval?: boolean; browserCapabilities?: string[]; now?: () => string; }): Promise { const runtime = await options.fetchJson(); const browserCapabilities = options.browserCapabilities || V1_BROWSER_CAPABILITIES; const grammar = backendGrammarDiscovery(runtime.grammar, browserCapabilities); const snapshot: V1CapabilitySnapshot = { snapshot_id: `snap_${uuidv4()}`, captured_at: options.now ? options.now() : new Date().toISOString(), schema_version: 'v1', plugin_version: runtime.plugin_version || 'unknown', site_identity: runtime.site_identity || {}, calling_surface: options.callingSurface, assistant_settings: backendAssistantSettings(options.assistantSettings), grammar, }; return { capabilitySnapshot: snapshot, approvalPolicy: { requireReadApproval: Boolean(options.requireReadApproval), }, }; } function backendGrammarDiscovery( source: V1GrammarDiscovery, browserCapabilities: string[], ): V1CapabilitySnapshotGrammar { return { interface_version: source.interface_version, capabilities: mergeCapabilities(source.capabilities, browserCapabilities), ...(source.site_shape ? { site_shape: source.site_shape } : {}), ...(source.settings_rev ? { settings_rev: source.settings_rev } : {}), }; } function backendAssistantSettings(source: Record): Record { const settings: Record = {}; if ('allow_log_reads' in source) settings.allow_log_reads = Boolean(source.allow_log_reads); if ('allow_write_operations' in source) settings.allow_write_operations = Boolean(source.allow_write_operations); return settings; } function mergeCapabilities(primary: string[] = [], additions: string[] = []): string[] { const merged: string[] = []; const seen = new Set(); for (const capability of [...primary, ...additions]) { if (typeof capability !== 'string' || !capability.trim() || seen.has(capability)) continue; seen.add(capability); merged.push(capability); } return merged; } export async function executeV1CommandPause( pause: V1CommandPauseEvent, client: V1CommandClient, options: V1CommandExecutionOptions = {}, ): Promise { const runtimeOptions: V1CommandRuntimeOptions = { commandClient: client, approvalPolicy: options.approvalPolicy, requestApproval: options.requestApproval, commandDispatchTimeoutMs: options.commandDispatchTimeoutMs, }; const prepared = await prepareGroupedCommandsForExecution(pause.command_batch, runtimeOptions); const command_results = needsLocalBatchState(pause.command_batch.commands, prepared) ? await executeCommandsWithLocalBatchState(pause.command_batch, prepared, runtimeOptions) : await executePluginOnlyCommands(pause.command_batch, prepared, runtimeOptions); return { pause_id: pause.pause_id, command_results, }; } export async function executeV1PauseEvent( pause: V1PauseEvent, options: V1CommandRuntimeOptions, ): Promise { return executeV1CommandPauseWithEvents(pause, options); } async function executeV1CommandPauseWithEvents( pause: V1CommandPauseEvent, options: V1CommandRuntimeOptions, ): Promise { for (const command of pause.command_batch.commands) { const labels = commandLifecycleLabels(command); emitRuntimeEvent(options, { type: 'command_started', id: command.command_id, batch_id: pause.command_batch.id, label: labels.started, target: labels.target, }); } const prepared = await prepareGroupedCommandsForExecution(pause.command_batch, options); const command_results = needsLocalBatchState(pause.command_batch.commands, prepared) ? await executeCommandsWithLocalBatchState(pause.command_batch, prepared, options) : await executePluginOnlyCommands(pause.command_batch, prepared, options); for (const command of pause.command_batch.commands) { const result = command_results.find(item => item.command_id === command.command_id); emitRuntimeEvent(options, { type: 'command_completed', id: command.command_id, batch_id: pause.command_batch.id, ok: result?.status === 'succeeded', summary: commandResultSummary(command, result), }); } return { pause_id: pause.pause_id, command_results, }; } async function executePluginOnlyCommands( batch: V1CommandPauseEvent['command_batch'], prepared: PreparedCommandExecution, options: V1CommandRuntimeOptions, ): Promise { const response = await executePreparedCommands( batch.id, prepared.commands, options.commandClient || missingCommandClient(), options.commandDispatchTimeoutMs, ); return applyGroupedAfterExecuteApprovals( batch, commandResultsForPause(batch.commands, prepared, response), options, ); } async function executeCommandsWithLocalBatchState( batch: V1CommandPauseEvent['command_batch'], prepared: PreparedCommandExecution, options: V1CommandRuntimeOptions, ): Promise { const executableByCommandId = new Map(prepared.commands.map(command => [command.command_id, command])); const commandClient = options.commandClient || missingCommandClient(); const browserRunner = options.browserCommandRunner || missingBrowserCommandRunner(); const commandResults = await runV1CommandBatch(batch.commands, { initialResults: prepared.terminalResults.values(), executeOne: async command => { const executableCommand = executableByCommandId.get(command.command_id); if (!executableCommand) { return missingCommandResult(command.command_id); } const resolvedCommand: V1CommandExecutionCommand = { ...executableCommand, input: command.input, }; return isBrowserCapability(resolvedCommand.capability) ? executeOneBrowserCommand(resolvedCommand, browserRunner) : executeOnePluginCommand(batch.id, resolvedCommand, commandClient, options.commandDispatchTimeoutMs); }, }); return applyGroupedAfterExecuteApprovals( batch, commandResults, options, ); } async function prepareGroupedCommandsForExecution( batch: V1CommandPauseEvent['command_batch'], options: V1CommandRuntimeOptions, ): Promise { const executable: V1CommandExecutionCommand[] = []; const terminalResults = new Map(); const decisions = new Map(); const preflight = await preflightBatchCommands(batch, options); const approvalGroups = groupedPreflightApprovalRequests(batch, preflight); for (const command of batch.commands) { const preflightDecision = preflight.get(command.command_id) || { requiresApproval: false, }; if (preflightDecision.result && preflightDecision.result.status !== 'succeeded') { terminalResults.set(command.command_id, preflightDecision.result); continue; } if (!preflightDecision.requiresApproval) { executable.push(commandForExecution(command)); continue; } const approvalKey = approvalGroupKey(command, 'before_execute'); let decision = decisions.get(approvalKey); if (!decision) { const approvalRequest = approvalGroups.get(approvalKey) || singleCommandApprovalRequest(command, 'before_execute', batch.id); const approvalDisplay = approvalRequest.display; emitRuntimeEvent(options, { type: 'approval_needed', id: approvalKey, batch_id: batch.id, label: approvalDisplay.label, target: approvalDisplay.target, }); decision = await approvalDecisionForGroup(approvalRequest, options, 'before_execute'); decisions.set(approvalKey, decision); emitRuntimeEvent(options, { type: decision.approved ? 'approval_granted' : 'approval_rejected', id: approvalKey, batch_id: batch.id, label: approvalDisplay.label, target: approvalDisplay.target, }); } if (!decision.approved) { terminalResults.set(command.command_id, rejectedCommandResult(command.command_id, decision.reason)); continue; } executable.push(commandForExecution(command, decision.approval || { approved: true })); } return { commands: executable, terminalResults }; } async function preflightBatchCommands( batch: V1CommandPauseEvent['command_batch'], options: V1CommandRuntimeOptions, ): Promise> { const client = options.commandClient; const pluginCommands = batch.commands.filter(command => ( !isBrowserCapability(command.capability) && isBeforeExecutePreflightCandidate(command) )); if (!client || pluginCommands.length < 1) { return new Map(); } const response = await executePreparedCommands( `${batch.id}:validate`, pluginCommands.map(commandForPreflight), client, options.commandDispatchTimeoutMs, ); const results = new Map((response.results || []).map(result => [result.command_id, result])); const decisions = new Map(); for (const command of pluginCommands) { const result = results.get(command.command_id) || missingCommandResult(command.command_id); decisions.set(command.command_id, { result, requiresApproval: result.status === 'succeeded' ? preflightRequiresApproval(result) : false, }); } return decisions; } function groupedPreflightApprovalRequests( batch: V1CommandPauseEvent['command_batch'], preflight: Map, ): Map { const approvalGroups = new Map(); for (const command of batch.commands) { const decision = preflight.get(command.command_id); const requiresApproval = decision?.result?.status === 'succeeded' && decision.requiresApproval; if (!requiresApproval) continue; const key = approvalGroupKey(command, 'before_execute'); const existing = approvalGroups.get(key); if (existing) { existing.commands.push(command); approvalGroups.set(key, approvalRequestForCommands(existing.commands, [], 'before_execute', batch.id)); continue; } approvalGroups.set(key, singleCommandApprovalRequest(command, 'before_execute', batch.id)); } return approvalGroups; } function isBeforeExecutePreflightCandidate(command: V1PluginCommand): boolean { return command.capability === 'wp.http.request' || !isReadApprovalCapability(command.capability); } async function applyGroupedAfterExecuteApprovals( batch: V1CommandPauseEvent['command_batch'], commandResults: V1CommandResult[], options: V1CommandRuntimeOptions, ): Promise { const nextResults = [...commandResults]; const decisions = new Map(); const approvalGroups = groupedAfterExecuteApprovalRequests(batch, options.approvalPolicy, commandResults); const resultsByCommandId = new Map(commandResults.map(result => [result.command_id, result])); for (const command of batch.commands) { if (!requiresReadSharingApproval(command, resultsByCommandId.get(command.command_id), options.approvalPolicy)) continue; const approvalKey = approvalGroupKey(command, 'after_execute'); let decision = decisions.get(approvalKey); if (!decision) { const approvalRequest = approvalGroups.get(approvalKey) || singleCommandApprovalRequest(command, 'after_execute', batch.id); const approvalDisplay = approvalRequest.display; emitRuntimeEvent(options, { type: 'approval_needed', id: approvalKey, batch_id: batch.id, label: approvalDisplay.label, target: approvalDisplay.target, }); decision = await approvalDecisionForGroup(approvalRequest, options, 'after_execute'); decisions.set(approvalKey, decision); emitRuntimeEvent(options, { type: decision.approved ? 'approval_granted' : 'approval_rejected', id: approvalKey, batch_id: batch.id, label: approvalDisplay.label, target: approvalDisplay.target, }); } if (!decision.approved) { const resultIndex = nextResults.findIndex(result => result.command_id === command.command_id); if (resultIndex >= 0) { nextResults[resultIndex] = rejectedCommandResult(command.command_id, decision.reason); } } } return nextResults; } function groupedAfterExecuteApprovalRequests( batch: V1CommandPauseEvent['command_batch'], approvalPolicy?: V1CommandApprovalPolicy, commandResults: V1CommandResult[] = [], ): Map { const groups = new Map(); const resultsByCommandId = new Map(commandResults.map(result => [result.command_id, result])); for (const command of batch.commands) { const requiresApproval = requiresReadSharingApproval( command, resultsByCommandId.get(command.command_id), approvalPolicy, ); if (!requiresApproval) continue; const key = approvalGroupKey(command, 'after_execute'); const groupedCommands = groups.get(key); if (groupedCommands) { groupedCommands.push(command); } else { groups.set(key, [command]); } } return new Map( Array.from(groups.entries()).map(([key, groupedCommands]) => [ key, approvalRequestForCommands( groupedCommands, commandResultsForCommands(groupedCommands, commandResults), 'after_execute', batch.id, ), ]) ); } function needsLocalBatchState( commands: V1PluginCommand[], prepared: PreparedCommandExecution, ): boolean { return prepared.terminalResults.size > 0 || commands.some(command => isBrowserCapability(command.capability)); } function approvalGroupKey(command: V1PluginCommand, timing: 'before_execute' | 'after_execute'): string { return approvalPromptForCommand(command, timing).group || command.command_id; } function singleCommandApprovalRequest( command: V1PluginCommand, timing: 'before_execute' | 'after_execute', batchId?: string, ): V1CommandApprovalRequest { return approvalRequestForCommands([command], [], timing, batchId); } function approvalRequestForCommands( commands: V1PluginCommand[], results: V1CommandResult[] = [], timing: 'before_execute' | 'after_execute', batchId?: string, ): V1CommandApprovalRequest { const first = commands[0]; const approval = approvalPromptForCommand(first, timing); const request: V1CommandApprovalRequest = { ...(batchId ? { batch_id: batchId } : {}), approval, commands, display: { label: approval.action || first.display?.label || 'Approve command', target: displayTargets(commands), summary: stringValue(approval.summary) || first.display?.summary, }, }; if (results.length > 0) { request.results = results; } return request; } function displayTargets(commands: V1PluginCommand[]): string | undefined { const targets = Array.from(new Set( commands .map(command => command.display?.target) .filter((target): target is string => Boolean(target)) )); return targets.length ? targets.join(', ') : undefined; } function commandResultsForPause( commands: V1PluginCommand[], prepared: PreparedCommandExecution, response: V1CommandExecutionResponse, ): V1CommandResult[] { const resultsByCommandId = commandResultsByCommandId(response.results || []); return commands.map(command => ( prepared.terminalResults.get(command.command_id) || takeCommandResult(resultsByCommandId, command.command_id) || missingCommandResult(command.command_id) )); } function commandResultsForCommands( commands: V1PluginCommand[], commandResults: V1CommandResult[], ): V1CommandResult[] { const resultsByCommandId = commandResultsByCommandId(commandResults); return commands .map(command => takeCommandResult(resultsByCommandId, command.command_id)) .filter((result): result is V1CommandResult => Boolean(result)); } function commandResultsByCommandId(commandResults: V1CommandResult[]): Map { const resultsByCommandId = new Map(); for (const result of commandResults) { const existing = resultsByCommandId.get(result.command_id); if (existing) { existing.push(result); } else { resultsByCommandId.set(result.command_id, [result]); } } return resultsByCommandId; } function takeCommandResult( resultsByCommandId: Map, commandId: string, ): V1CommandResult | undefined { return resultsByCommandId.get(commandId)?.shift(); } function isBrowserCapability(capability: string): boolean { return capability.startsWith('browser.'); } async function executeOnePluginCommand( batchId: string, command: V1CommandExecutionCommand, client: V1CommandClient, timeoutMs?: number, ): Promise { const dispatchCommand = commandForSinglePluginDispatch(command); const response = await executePreparedCommands(batchId, [dispatchCommand], client, timeoutMs); return response.results?.find(result => result.command_id === command.command_id) || missingCommandResult(command.command_id); } function commandForSinglePluginDispatch(command: V1CommandExecutionCommand): V1CommandExecutionCommand { const dispatchCommand = { ...command }; delete dispatchCommand.depends_on; return dispatchCommand; } async function executeOneBrowserCommand( command: V1CommandExecutionCommand, runner: V1BrowserCommandRunner, ): Promise { try { const result = await runner.runBrowserCommand(command); return { ...result, command_id: command.command_id, capability: command.capability, artifacts: result.artifacts || [], }; } catch (error) { return { command_id: command.command_id, capability: command.capability, status: 'failed', artifacts: [], error: { code: 'browser_command_failed', message: error instanceof Error ? error.message : String(error), }, }; } } function requiresReadSharingApproval( command: V1PluginCommand, result: V1CommandResult | undefined, approvalPolicy?: V1CommandApprovalPolicy, ): boolean { return approvalPolicy?.requireReadApproval === true && isReadApprovalCapability(command.capability) && result?.status === 'succeeded'; } // Builds Frontend prompt grouping and copy after Plugin preflight or local read policy // has already determined that approval is required. function approvalPromptForCommand( command: V1PluginCommand, timing: 'before_execute' | 'after_execute', ): V1CommandApprovalRequirement { const group = approvalGroupForCommand(command, timing); const summary = command.display?.summary; if (timing === 'after_execute') { return { required: true, group, action: readApprovalAction(command), mode: 'after_execute', ...(summary ? { summary } : {}), }; } return { required: true, group, action: group === 'content_mutation' ? 'Apply content changes' : group === 'site_mutation' ? 'Apply site changes' : command.display?.label || 'Approve command', ...(summary ? { summary } : {}), }; } function approvalGroupForCommand( command: V1PluginCommand, timing: 'before_execute' | 'after_execute', ): string { if (timing === 'after_execute') { return isContentReadCapability(command.capability) ? 'content_read' : 'site_read'; } if (command.display?.label === 'Review content changes') { return 'content_mutation'; } if (isReadApprovalCapability(command.capability)) { return 'site_read'; } return 'site_mutation'; } function readApprovalAction(command: V1PluginCommand): string { if (isContentReadCapability(command.capability)) return 'Read page content'; if (command.capability === 'browser.page.get') return 'Analyze rendered page'; if (command.capability === 'wp.http.request') return 'Run site request'; if (command.capability === 'wp.performance.inspect') return 'Inspect page performance'; return 'Read site data'; } function isReadApprovalCapability(capability: string): boolean { return capability.endsWith('.get') || capability.endsWith('.query') || capability === 'wp.http.request' || capability === 'wp.performance.inspect' || capability === 'browser.page.get'; } async function executePreparedCommands( batchId: string, commands: V1CommandExecutionCommand[], client: V1CommandClient, timeoutMs = DEFAULT_COMMAND_DISPATCH_TIMEOUT_MS, ): Promise { if (commands.length < 1) { return { results: [] }; } const request = commandExecutionRequestFromCommands(batchId, commands); const abortController = canAbortCommandDispatch(timeoutMs) ? new AbortController() : null; const dispatchPromise = abortController && client.supportsAbortSignal === true ? client.executeCommands(request, { signal: abortController.signal }) : client.executeCommands(request); try { return await withCommandDispatchTimeout( dispatchPromise, timeoutMs, () => ({ results: commands.map(command => commandDispatchErrorResult( command, 'plugin_command_dispatch_timeout', `Plugin command dispatch timed out after ${timeoutMs}ms. Command outcome is unknown; inspect the site before retrying mutating work.`, )), }), () => abortController?.abort(), ); } catch (error) { return { results: commands.map(command => commandDispatchErrorResult( command, 'plugin_command_dispatch_failed', `Plugin command dispatch failed before command outcomes were confirmed; inspect the site before retrying mutating work: ${error instanceof Error ? error.message : String(error)}`, )), }; } } function canAbortCommandDispatch(timeoutMs: number): boolean { return Number.isFinite(timeoutMs) && timeoutMs > 0 && typeof AbortController !== 'undefined'; } function withCommandDispatchTimeout( promise: Promise, timeoutMs: number, fallback: () => T, onTimeout?: () => void, ): Promise { if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) { return promise; } return new Promise((resolve, reject) => { let settled = false; const timer = window.setTimeout(() => { if (settled) return; settled = true; onTimeout?.(); resolve(fallback()); }, timeoutMs); promise.then( value => { if (settled) return; settled = true; window.clearTimeout(timer); resolve(value); }, error => { if (settled) return; settled = true; window.clearTimeout(timer); reject(error); }, ); }); } function commandDispatchErrorResult( command: V1CommandExecutionCommand, code: string, message: string, ): V1CommandResult { return { command_id: command.command_id, capability: command.capability, status: 'failed', error: { code, message }, }; } async function approvalDecisionForGroup( request: V1CommandApprovalRequest, options: V1CommandRuntimeOptions, timing: 'before_execute' | 'after_execute' = 'before_execute', ): Promise { if (options.requestApproval) { return options.requestApproval(request); } return { approved: false, reason: timing === 'after_execute' ? 'Approval is required before sharing this command result.' : 'Approval is required before this command can run.', }; } function commandExecutionRequestFromCommands( batchId: string, commands: V1CommandExecutionCommand[], ): V1CommandExecutionRequest { return { batch_id: batchId, commands, }; } function commandForExecution(command: V1PluginCommand, approval?: Record): V1CommandExecutionCommand { const executionCommand: V1CommandExecutionCommand = { command_id: command.command_id, capability: command.capability, phase: command.phase, input: command.input, }; if (command.depends_on) { executionCommand.depends_on = command.depends_on; } if (command.continue_after_failure === true) { executionCommand.continue_after_failure = true; } if (approval) { executionCommand.approval = approval; } return executionCommand; } function commandForPreflight(command: V1PluginCommand): V1CommandExecutionCommand { const preflightCommand = commandForExecution(command); delete preflightCommand.depends_on; preflightCommand.phase = 'validate'; return preflightCommand; } function preflightRequiresApproval(result: V1CommandResult): boolean { if (!result.data || typeof result.data !== 'object') return true; const approval = (result.data as { approval?: unknown }).approval; if (!approval || typeof approval !== 'object') return true; return (approval as { required?: unknown }).required === true; } function missingCommandResult(commandId: string): V1CommandResult { return { command_id: commandId, status: 'failed', error: { code: 'missing_command_result', message: 'Plugin command endpoint did not return a terminal result for this command.', }, }; } function rejectedCommandResult(commandId: string, reason?: string): V1CommandResult { return { command_id: commandId, status: 'rejected', data: { status: 'rejected', user_reason: reason || 'User rejected this command.', }, }; } function emitRuntimeEvent(options: V1CommandRuntimeOptions, event: V1CommandRuntimeEvent): void { options.emitEvent?.(event); } function commandResultSummary(command: V1PluginCommand, result?: V1CommandResult): string | undefined { if (!result) return undefined; const labels = commandLifecycleLabels(command); if (result.status === 'succeeded') return labels.completed; if (result.status === 'denied' || result.status === 'rejected' || result.status === 'skipped') { return labels.skipped; } if (result.error?.message) return `${labels.failed}: ${result.error.message}`; if (result.error?.code) return `${labels.failed}: ${result.error.code}`; if (result.policy?.decision && result.policy.decision !== 'allowed') { return `${labels.failed}: ${result.policy.reason_codes?.[0] || result.policy.decision}`; } return labels.failed; } function commandLifecycleLabels(command: V1PluginCommand): V1LifecycleLabels { if (command.capability === 'wp.http.request') { const input = objectValue(command.input) || {}; if (stringValue(input.check_type) || command.display?.label === 'Verify site change') { return actionLabels('Checking page response', 'Checked page response', 'check page response', command.display?.target); } return actionLabels('Fetching page response', 'Fetched page response', 'fetch page response', command.display?.target); } if (isContentReadCapability(command.capability)) { const target = contentReadTarget(command.input, command.display?.target); return actionLabels( `Reading ${target} content`, `Read ${target} content`, `read ${target} content`, command.display?.target, ); } const siteReadResource = siteReadResourceFromCommand(command); if (siteReadResource) { const label = siteReadResourceLabel(siteReadResource); return actionLabels(`Reading ${label}`, `Read ${label}`, `read ${label}`, command.display?.target); } const siteWriteLabels = siteWriteLifecycleLabels(command); if (siteWriteLabels) return siteWriteLabels; const fallback = humanizeCapability(command.capability); return actionLabels(`Running ${fallback}`, `Ran ${fallback}`, `run ${fallback}`, command.display?.target); } function actionLabels(started: string, completed: string, action: string, target?: string): V1LifecycleLabels { return { started, completed, failed: `Failed to ${action}`, skipped: `Skipped ${action}`, target, }; } function siteReadResourceFromCommand(command: V1PluginCommand): string | undefined { const input = objectValue(command.input) || {}; const resource = stringValue(input.resource); if (resource) return resource; const resourcesByCapability: Record = { 'wp.posts.query': 'posts', 'wp.media.query': 'media', 'wp.terms.query': 'terms', 'wp.post_types.query': 'post_types', 'wp.taxonomies.query': 'taxonomies', 'wp.block_patterns.query': 'block_patterns', 'wp.template_parts.query': 'template_parts', 'wp.block_types.query': 'block_types', 'wp.shortcodes.query': 'shortcodes', 'wp.options.query': 'options', 'wp.plugins.query': 'plugins', 'wp.themes.get': 'theme', 'wp.global_styles.get': 'global_styles', 'wp.templates.query': 'templates', 'wp.runtime.get': 'runtime', 'wp.server.get': 'server', 'wp.site_health.get': 'site_health', 'wp.performance.inspect': 'performance', 'wp.rest_routes.query': 'rest_routes', 'wp.hooks.query': 'hooks', 'wp.files.query': 'files', 'wp.transients.query': 'transients', 'wp.users.query': 'users', 'wp.logs.query': 'logs', }; return resourcesByCapability[command.capability]; } function siteReadResourceLabel(resource: string): string { const labels: Record = { posts: 'posts and pages', media: 'media library', terms: 'taxonomy terms', post_types: 'post types', taxonomies: 'taxonomies', block_patterns: 'block patterns', template_parts: 'template parts', block_types: 'block types', shortcodes: 'shortcodes', dynamic_blocks: 'dynamic blocks', options: 'WordPress settings', plugins: 'plugin information', theme: 'theme information', global_styles: 'global styles', templates: 'site templates', runtime: 'WordPress runtime', server: 'server details', site_health: 'Site Health results', performance: 'performance diagnostics', rest_routes: 'REST routes', hooks: 'WordPress hooks', files: 'file snippet', transients: 'transients', commerce: 'commerce data', users: 'user summaries', logs: 'debug logs', }; return labels[resource] || titleCase(resource); } function isContentReadCapability(capability: string): boolean { return capability === 'wp.posts.get' || capability === 'wp.template_parts.get'; } function contentReadTarget(input: Record, fallback?: string): string { const source = objectValue(input.source); const kind = stringValue(source?.kind) || 'post'; const id = stringValue(source?.id); if (id) return `${titleCase(kind).toLowerCase()} ${id}`; return fallback || 'selected'; } function siteWriteLifecycleLabels(command: V1PluginCommand): V1LifecycleLabels | undefined { const opName = siteWriteOperationFromCapability(command.capability) || command.display?.target; const input = objectValue(command.input) || {}; const values = objectValue(input.values) || {}; const fields = objectValue(values.fields) || values; const target = command.display?.target; if (opName === 'post.create') { const postType = stringValue(fields.post_type) || 'content'; const status = stringValue(fields.post_status ?? fields.status); const createdThing = status === 'draft' ? `draft ${postType}` : postType; return actionLabels(`Creating ${createdThing}`, `Created ${createdThing}`, `create ${createdThing}`, target); } if (opName === 'post.update') { return actionLabels('Updating post settings', 'Updated post settings', 'update post settings', target); } if (opName === 'option.patch') { return actionLabels('Updating WordPress settings', 'Updated WordPress settings', 'update WordPress settings', target); } if (opName === 'cache.clear') { return actionLabels('Clearing caches', 'Cleared caches', 'clear caches', target); } if (opName === 'rewrite.flush') { return actionLabels('Refreshing rewrite rules', 'Refreshed rewrite rules', 'refresh rewrite rules', target); } if (opName === 'maintenance.run') { return actionLabels('Running maintenance', 'Ran maintenance', 'run maintenance', target); } if (opName === 'post_type.create') { return actionLabels('Creating custom post type', 'Created custom post type', 'create custom post type', target); } if (opName === 'post_type.update') { return actionLabels('Updating custom post type', 'Updated custom post type', 'update custom post type', target); } return undefined; } function siteWriteOperationFromCapability(capability: string): string | undefined { const operationsByCapability: Record = { 'wp.rewrite_rules.flush': 'rewrite.flush', 'wp.options.update': 'option.patch', 'wp.cache.clear': 'cache.clear', 'wp.cron.run': 'maintenance.run', 'wp.posts.create': 'post.create', 'wp.posts.update': 'post.update', 'wp.post_types.create': 'post_type.create', 'wp.post_types.update': 'post_type.update', }; return operationsByCapability[capability]; } function humanizeCapability(capability: string): string { return capability.replace(/^wp\./, '').replace(/[._-]+/g, ' ').trim() || 'command'; } function missingCommandClient(): V1CommandClient { return { executeCommands: async (request: V1CommandExecutionRequest) => ({ results: request.commands.map(command => ({ command_id: command.command_id, status: 'unavailable' as const, error: { code: 'plugin_command_client_unavailable', message: 'Plugin command execution is unavailable in this Frontend runtime.', }, })), }), }; } function missingBrowserCommandRunner(): V1BrowserCommandRunner { return { runBrowserCommand: async command => ({ status: 'unsupported' as const, artifacts: [], error: { code: 'browser_command_runner_unavailable', message: `Browser command execution is unavailable for ${command.capability}.`, }, }), }; } function stringValue(value: unknown): string | undefined { return typeof value === 'string' && value.trim() ? value : undefined; } function objectValue(value: unknown): Record | undefined { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value as Record : undefined; } function titleCase(value: string): string { return value .replace(/[._-]+/g, ' ') .replace(/\s+/g, ' ') .trim() .replace(/\b\w/g, char => char.toUpperCase()); }