/** * Content sanitization utilities for display. * * SHARED MODULE: Used by both main and renderer processes. * - Main process: Used in jsonl.ts for initial parsing * - Renderer process: Used in groupTransformer.ts for display formatting * * This module handles conversion of raw JSONL content (with XML tags) into * human-readable format for the UI. * * NOTE: This file was previously duplicated in both main/utils and renderer/utils. * Consolidated to src/shared/utils to maintain DRY principle while serving both processes. */ /** * Patterns for noise tags that should be completely removed. * These are system-generated metadata that provide no value in display. */ const NOISE_TAG_PATTERNS = [ /[\s\S]*?<\/local-command-caveat>/gi, /[\s\S]*?<\/system-reminder>/gi, ]; /** * Extract content from tags. * Returns the command output without the wrapper tags. */ function extractCommandOutput(content: string): string | null { const match = /([\s\S]*?)<\/local-command-stdout>/i.exec(content); const matchStderr = /([\s\S]*?)<\/local-command-stderr>/i.exec(content); if (match) { return match[1].trim(); } if (matchStderr) { return matchStderr[1].trim(); } return null; } /** * Extract command info from command XML tags. * Returns the slash command in readable format (e.g., "/model sonnet") */ function extractCommandDisplay(content: string): string | null { const commandNameMatch = /\/([^<]+)<\/command-name>/.exec(content); const commandArgsMatch = /([^<]*)<\/command-args>/.exec(content); if (commandNameMatch) { const commandName = `/${commandNameMatch[1].trim()}`; const args = commandArgsMatch?.[1]?.trim(); return args ? `${commandName} ${args}` : commandName; } return null; } /** * Check if content is primarily a command message. * Handles both orderings: * - Built-in commands: comes first * - Skill commands: comes first, followed by */ export function isCommandContent(content: string): boolean { return content.startsWith('') || content.startsWith(''); } /** * Check if content is a command output message. */ export function isCommandOutputContent(content: string): boolean { return ( content.startsWith('') || content.startsWith('') ); } /** * Sanitize content for display. * * - Command messages: Converted to readable format (e.g., "/model sonnet") * - Command output: Extracted from tags * - Noise tags: Completely removed * - Regular content: Returned as-is */ export function sanitizeDisplayContent(content: string): string { // If it's a command output message, extract the output content if (isCommandOutputContent(content)) { const commandOutput = extractCommandOutput(content); if (commandOutput) { return commandOutput; } } // If it's a command message, extract the command for display if (isCommandContent(content)) { const commandDisplay = extractCommandDisplay(content); if (commandDisplay) { return commandDisplay; } } // Remove noise tags let sanitized = content; for (const pattern of NOISE_TAG_PATTERNS) { sanitized = sanitized.replace(pattern, ''); } // Also remove any remaining command tags (in case of mixed content) sanitized = sanitized .replace(/[\s\S]*?<\/command-name>/gi, '') .replace(/[\s\S]*?<\/command-message>/gi, '') .replace(/[\s\S]*?<\/command-args>/gi, ''); return sanitized.trim(); } /** * Slash info extracted from command XML tags. * All slash commands have the same format: * /xxx * xxx * optional */ export interface SlashInfo { /** Slash name without the leading slash (e.g., "model", "isolate-context") */ name: string; /** Message content from */ message?: string; /** Optional arguments from */ args?: string; } /** * Extract slash information from command XML tags. * Works for all slash types: skills, built-in commands, plugins, MCP, user commands. * Returns null if not a slash command format. */ export function extractSlashInfo(content: string): SlashInfo | null { const nameMatch = /\/([^<]+)<\/command-name>/.exec(content); if (!nameMatch) return null; const name = nameMatch[1].trim(); const messageMatch = /([^<]*)<\/command-message>/.exec(content); const argsMatch = /([^<]*)<\/command-args>/.exec(content); return { name, message: messageMatch?.[1]?.trim() ?? undefined, args: argsMatch?.[1]?.trim() ?? undefined, }; }