type AnyRecord = Record; export interface ApprovalDetail { label: string; value: string; } export interface ApprovalSummaryItem { title: string; description: string; tone?: 'neutral' | 'success' | 'warning' | 'danger' | 'brand'; details?: ApprovalDetail[]; } export interface WriteApprovalViewModel { goal: string; approvalDescription: string; modalTitle?: string; approveLabel?: string; operationCount: number; items: ApprovalSummaryItem[]; checks: ApprovalSummaryItem[]; rawRequest: unknown; technicalDetails?: unknown; } const isRecord = (value: unknown): value is AnyRecord => ( Boolean(value) && typeof value === 'object' && !Array.isArray(value) ); const asRecord = (value: unknown): AnyRecord => ( isRecord(value) ? value : {} ); const asArray = (value: unknown): unknown[] => ( Array.isArray(value) ? value : [] ); const stringValue = (value: unknown): string | undefined => { if (typeof value === 'string' && value.trim()) return value.trim(); if (typeof value === 'number' || typeof value === 'boolean') return String(value); return undefined; }; const listValue = (value: unknown): string | undefined => { if (!Array.isArray(value) || value.length === 0) return undefined; return value.map(item => stringValue(item)).filter(Boolean).join(', ') || undefined; }; const pluralize = (count: number, word: string) => `${count} ${word}${count === 1 ? '' : 's'}`; const titleCase = (value: string) => ( value .replace(/[._-]+/g, ' ') .replace(/\s+/g, ' ') .trim() .replace(/\b\w/g, char => char.toUpperCase()) ); const compactJson = (value: unknown): string | undefined => { if (value === null || value === undefined) return undefined; if (typeof value === 'string') return value; try { const json = JSON.stringify(value); return json && json.length > 120 ? `${json.slice(0, 117)}...` : json; } catch { return String(value); } }; const opName = (op: unknown) => { const record = asRecord(op); return stringValue(record.op ?? record.action ?? record.type) ?? 'operation'; }; const opArgs = (op: unknown) => asRecord(asRecord(op).args); const opValues = (op: unknown) => asRecord(asRecord(op).values); const fieldsFromValues = (values: AnyRecord) => asRecord(values.fields); const fieldDetails = (fields: AnyRecord): ApprovalDetail[] => ( Object.keys(fields).slice(0, 8).map(key => ({ label: titleCase(key), value: compactJson(fields[key]) ?? 'set' })) ); const scopedDetails = (details: (ApprovalDetail | undefined)[]) => details.filter(Boolean) as ApprovalDetail[]; const patchDetails = (values: AnyRecord): ApprovalDetail[] => { const directSet = asRecord(values.set); const directDelete = asArray(values.delete); const fieldSet = asRecord(asRecord(values.fields).set); const fieldDelete = asArray(asRecord(values.fields).delete); const registeredSet = asRecord(asRecord(values.registered_meta).set); const registeredDelete = asArray(asRecord(values.registered_meta).delete); const acfSet = asRecord(asRecord(values.acf_fields).set); const acfDelete = asArray(asRecord(values.acf_fields).delete); return [ ...Object.keys(directSet).map(key => ({ label: key, value: compactJson(directSet[key]) ?? 'set' })), ...directDelete.map(value => ({ label: String(value), value: 'delete' })), ...Object.keys(fieldSet).map(key => ({ label: key, value: compactJson(fieldSet[key]) ?? 'set' })), ...fieldDelete.map(value => ({ label: String(value), value: 'delete' })), ...Object.keys(registeredSet).map(key => ({ label: key, value: compactJson(registeredSet[key]) ?? 'set' })), ...registeredDelete.map(value => ({ label: String(value), value: 'delete' })), ...Object.keys(acfSet).map(key => ({ label: key, value: compactJson(acfSet[key]) ?? 'set' })), ...acfDelete.map(value => ({ label: String(value), value: 'delete' })), ].slice(0, 10); }; const patchChangeCount = (values: AnyRecord) => Math.max(1, patchDetails(values).length); export const describeSiteWriteOp = (op: unknown): ApprovalSummaryItem => { const name = opName(op); const args = opArgs(op); const values = opValues(op); const fields = fieldsFromValues(values); const postId = stringValue(args.post_id ?? args.source_post_id); if (name === 'rewrite.flush') { return { title: 'Refresh rewrite rules', description: 'Flush WordPress rewrite rules so permalinks and routes are regenerated.', tone: 'warning', }; } if (name === 'cache.clear') { const targets = listValue(asArray(values.targets).length ? values.targets : ['object', 'transients']); return { title: 'Clear caches', description: `Clear ${targets ?? 'selected'} caches.`, tone: 'warning', }; } if (name === 'maintenance.run') { const tasks = listValue(asArray(values.tasks).length ? values.tasks : ['cron']); return { title: 'Run maintenance', description: `Run ${tasks ?? 'selected'} maintenance tasks.`, tone: 'warning', }; } if (name === 'option.patch') { const setCount = Object.keys(asRecord(values.set)).length; const deleteCount = asArray(values.delete).length; return { title: 'Update WordPress settings', description: `Change ${pluralize(setCount + deleteCount, 'setting')}.`, tone: 'warning', details: [ ...Object.keys(asRecord(values.set)).map(key => ({ label: key, value: compactJson(asRecord(values.set)[key]) ?? 'set' })), ...asArray(values.delete).map(value => ({ label: String(value), value: 'delete' })), ].slice(0, 10), }; } if (name === 'post.clone') { return { title: 'Create a draft copy', description: `Clone ${postId ? `post ${postId}` : 'the selected content'} into a new draft.`, tone: 'brand', details: fieldDetails(fields), }; } if (name === 'post.create') { const postType = stringValue(fields.post_type) ?? 'content'; const status = stringValue(fields.post_status ?? fields.status) ?? 'draft'; return { title: `Create ${postType}`, description: `Create a new ${postType} with ${status} status.`, tone: status === 'publish' ? 'warning' : 'brand', details: fieldDetails(fields), }; } if (name === 'post.update') { return { title: 'Update content settings', description: `Update ${postId ? `post ${postId}` : 'the selected content'} settings.`, tone: 'brand', details: fieldDetails(fields), }; } if (name === 'post.publish') { return { title: 'Publish content', description: `Publish ${postId ? `post ${postId}` : 'the selected content'}.`, tone: 'warning', }; } if (name === 'post.meta_patch') { return { title: 'Update metadata or custom fields', description: `Patch ${pluralize(patchChangeCount(values), 'field')} for ${postId ? `post ${postId}` : 'the selected content'}.`, tone: 'brand', details: patchDetails(values), }; } if (name === 'post_type.create') { const postType = stringValue(values.post_type) ?? 'custom post type'; const label = stringValue(values.plural_label) ?? postType; return { title: 'Create custom post type', description: `Create the ACF-managed ${label} post type.`, tone: 'warning', details: fieldDetails(values), }; } if (name === 'post_type.update') { const postType = stringValue(values.post_type) ?? 'custom post type'; return { title: 'Update custom post type', description: `Update the ACF-managed ${postType} post type.`, tone: 'warning', details: fieldDetails(values), }; } return { title: titleCase(name), description: `Run the ${titleCase(name).toLowerCase()} operation.`, tone: 'neutral', }; }; const CONTENT_WRITE_OPS = new Set([ 'replace_body', 'replace_block', 'insert_before', 'insert_after', 'delete_block', 'set_attr', 'set_attrs', 'set_attr_path', 'replace_html_text', 'replace_html_link', 'replace_html_media', 'replace_shortcode', 'replace_custom_css', 'delete_custom_css', ]); const describeContentWriteOp = (op: unknown, args: AnyRecord): ApprovalSummaryItem => { const name = opName(op); const record = asRecord(op); const postId = stringValue(args.post_id); const target = postId ? `post ${postId}` : 'the selected content'; const ref = stringValue(record.ref ?? record.id ?? record.path); const baseDetails = scopedDetails([ postId ? { label: 'Post', value: postId } : undefined, stringValue(args.rev) ? { label: 'Revision', value: stringValue(args.rev) as string } : undefined, stringValue(args.ws) ? { label: 'Workspace', value: stringValue(args.ws) as string } : undefined, ref ? { label: 'Target', value: ref } : undefined, ]); if (name === 'replace_body') { return { title: 'Replace body content', description: `Replace the full block-editor body for ${target}.`, tone: 'warning', details: baseDetails, }; } if (name === 'replace_custom_css') { return { title: 'Update page CSS', description: `Replace page-level custom CSS for ${target}.`, tone: 'warning', details: baseDetails, }; } if (name === 'delete_custom_css') { return { title: 'Remove page CSS', description: `Remove page-level custom CSS from ${target}.`, tone: 'warning', details: baseDetails, }; } if (name === 'replace_block') { return { title: 'Replace a block', description: `Replace one block in ${target}.`, tone: 'brand', details: baseDetails, }; } if (name === 'insert_before' || name === 'insert_after') { return { title: name === 'insert_before' ? 'Insert content before a block' : 'Insert content after a block', description: `Insert block-editor content in ${target}.`, tone: 'brand', details: baseDetails, }; } if (name === 'delete_block') { return { title: 'Delete a block', description: `Delete one block from ${target}.`, tone: 'warning', details: baseDetails, }; } if (name === 'set_attr' || name === 'set_attrs' || name === 'set_attr_path') { return { title: 'Update block settings', description: `Update saved block attributes in ${target}.`, tone: 'brand', details: scopedDetails([ ...baseDetails, stringValue(record.key) ? { label: 'Attribute', value: stringValue(record.key) as string } : undefined, ]), }; } if (name === 'replace_html_text') { return { title: 'Replace visible text', description: `Replace saved HTML text in ${target}.`, tone: 'brand', details: baseDetails, }; } if (name === 'replace_html_link') { return { title: 'Update a link', description: `Update a saved HTML link in ${target}.`, tone: 'brand', details: baseDetails, }; } if (name === 'replace_html_media') { return { title: 'Update media markup', description: `Update saved HTML media markup in ${target}.`, tone: 'brand', details: baseDetails, }; } if (name === 'replace_shortcode') { return { title: 'Replace a shortcode', description: `Replace shortcode content in ${target}.`, tone: 'brand', details: baseDetails, }; } return { title: titleCase(name), description: `Run the ${titleCase(name).toLowerCase()} content operation on ${target}.`, tone: 'neutral', details: baseDetails, }; }; const fallbackWriteGoal = (items: ApprovalSummaryItem[], emptyFallback: string, multipleFallback: string) => { if (items.length === 0) return emptyFallback; if (items.length === 1) return items[0].description; return multipleFallback; }; const normalizeChecks = (checks: unknown[]): ApprovalSummaryItem[] => ( checks.map((check) => { const record = asRecord(check); const type = stringValue(record.type) ?? 'check'; const target = stringValue(record.target); return { title: type === 'http_fetch' ? 'Check page response' : titleCase(type), description: target ? `Verify ${target}.` : 'Run a post-action check.', tone: 'neutral' as const, details: target ? [{ label: 'Target', value: target }] : undefined, }; }) ); const V1_CHECK_DISPLAY_LABEL = 'Verify site change'; const v1CommandExecutionPreview = (command: AnyRecord) => { const preview: AnyRecord = {}; for (const key of ['capability', 'input']) { if (command[key] !== undefined) { preview[key] = command[key]; } } return preview; }; const v1CommandResultPreview = (result: AnyRecord) => { const preview: AnyRecord = {}; for (const key of ['status', 'data', 'policy', 'error']) { if (result[key] !== undefined) { preview[key] = result[key]; } } return preview; }; const v1CommandBatchTechnicalDetails = ( root: AnyRecord, commands: AnyRecord[], results: AnyRecord[], ) => { const approval = asRecord(root.approval); const details: AnyRecord = { commands: commands.map(v1CommandExecutionPreview), }; if (stringValue(approval.mode) === 'after_execute') { details.results_to_share = results.map(v1CommandResultPreview); } return details; }; const contentWriteOpFromGrammarChange = (change: unknown): AnyRecord => { const record = asRecord(change); const path = asArray(record.path).map(stringValue).filter(Boolean); if (stringValue(record.op) === 'set' && path.join('.') === 'content.raw') { return { op: 'replace_body', markup: record.value }; } return { op: stringValue(record.op) ?? 'change', path: path.length ? path.join('.') : record.path, value: record.value, }; }; const contentWriteApprovalArguments = (input: AnyRecord): AnyRecord => { if (Array.isArray(input.ops)) return input; const target = asRecord(input.target); const preconditions = asRecord(target.if); const args: AnyRecord = { ops: asArray(input.changes).map(contentWriteOpFromGrammarChange), }; const postId = stringValue(target.id); const contentHash = stringValue(preconditions.content_hash); if (postId) args.post_id = postId; if (contentHash) args.rev = contentHash; return args; }; /** * Convert a V1 command approval request (approval + grouped Plugin Commands) * into the modal view. Summaries are derived from WordPress command display * metadata and operation-native command inputs; the technical panel keeps the * Plugin command batch shape visible. */ export const normalizeV1ApprovalRequestView = (request: unknown): WriteApprovalViewModel => { const root = asRecord(request); const approval = asRecord(root.approval); const display = asRecord(root.display); const commands = asArray(root.commands).map(asRecord); const results = asArray(root.results).map(asRecord); const technicalDetails = v1CommandBatchTechnicalDetails(root, commands, results); const resultsByCommandId = new Map( results.map(result => [stringValue(result.command_id) ?? '', result]) ); const group = stringValue(approval.group) ?? ''; if (group === 'content_mutation' && commands.length > 0) { return { ...normalizeWriteApprovalView({ name: 'content_write', arguments: contentWriteApprovalArguments(asRecord(commands[0].input)), }), rawRequest: root, technicalDetails, }; } if (group === 'site_mutation') { const opCommands = commands.filter( command => stringValue(asRecord(command.display).label) !== V1_CHECK_DISPLAY_LABEL ); const checkCommands = commands.filter( command => stringValue(asRecord(command.display).label) === V1_CHECK_DISPLAY_LABEL ); // Site command inputs carry id/args/values and the display target carries // the operation name used in the approval summary. const ops = opCommands.map(command => ({ op: stringValue(asRecord(command.display).target), ...asRecord(command.input), })); const checks = checkCommands.map(command => { const input = asRecord(command.input); return { type: input.check_type, target: input.url }; }); return { ...normalizeWriteApprovalView({ name: 'site_write', arguments: { ops, checks, goal: stringValue(approval.summary) ?? stringValue(display.summary), }, }), rawRequest: root, technicalDetails, }; } // Read approvals (require_read_approval) and unknown groups: render each // command's display metadata directly. const items: ApprovalSummaryItem[] = commands.map(command => { const commandDisplay = asRecord(command.display); const target = stringValue(commandDisplay.target); const details: ApprovalDetail[] = []; const summary = stringValue(commandDisplay.summary); if (summary) { details.push({ label: 'Summary', value: summary }); } const resultStatus = stringValue(resultsByCommandId.get(stringValue(command.command_id) ?? '')?.status); if (resultStatus) { details.push({ label: 'Result', value: resultStatus }); } return { title: stringValue(commandDisplay.label) ?? stringValue(command.capability) ?? 'Command', description: target ? `Target: ${target}` : `Run ${stringValue(command.capability) ?? 'command'}.`, tone: 'brand' as const, details: details.length > 0 ? details : undefined, }; }); const isAfterExecuteApproval = stringValue(approval.mode) === 'after_execute'; return { goal: stringValue(approval.action) ?? stringValue(display.label) ?? 'Run operation', approvalDescription: isAfterExecuteApproval ? 'Approving shares these results with the assistant.' : 'Approving runs these operations with your current user permissions.', modalTitle: isAfterExecuteApproval ? 'Approve data sharing' : undefined, approveLabel: isAfterExecuteApproval ? 'Approve & share' : undefined, operationCount: commands.length, items, checks: [], rawRequest: root, technicalDetails, }; }; export const normalizeWriteApprovalView = (request: unknown): WriteApprovalViewModel => { const root = asRecord(request); const requestRecord = asRecord(root.request ?? root); const args = asRecord(requestRecord.arguments ?? requestRecord); const ops = asArray(args.ops ?? args.actions); const toolName = stringValue(requestRecord.name ?? root.name); const isContentWrite = toolName === 'content_write' || (Boolean(args.post_id) && ops.some(op => CONTENT_WRITE_OPS.has(opName(op)))); const items = isContentWrite ? ops.map(op => describeContentWriteOp(op, args)) : ops.map(describeSiteWriteOp); const goal = stringValue(args.goal) ?? fallbackWriteGoal( items, isContentWrite ? 'update content' : 'run a site operation', isContentWrite ? `apply ${pluralize(items.length, 'content change')}` : `run ${pluralize(items.length, 'site operation')}` ); return { goal, approvalDescription: isContentWrite ? 'Approving applies these block-editor content changes to the selected WordPress content.' : 'Approving runs these WordPress site operations with your current user permissions.', operationCount: ops.length, items, checks: normalizeChecks(asArray(args.checks)), rawRequest: args, }; };