import crypto from 'node:crypto'; import fs from 'node:fs'; import path from 'node:path'; import { parseMindspaceCoverMeta, upsertMindspaceCoverMeta, } from '../../mindspace-cover-meta.mjs'; const RASTER_MIME_PATTERN = /^image\/(?:png|jpeg|webp)$/i; function parseToolResultJson(toolResult) { const content = toolResult?.value?.content; if (!Array.isArray(content)) return null; for (const item of content) { if (item?.type !== 'text' || !String(item.text ?? '').trim()) continue; try { return JSON.parse(String(item.text)); } catch { // A non-JSON tool message cannot prove a fresh generated image. } } return null; } function normalizeGeneratedImage(result) { const mimeType = String(result?.source?.mimeType ?? result?.asset?.mimeType ?? '').toLowerCase(); if (!result?.ok || !String(result?.jobId ?? '').trim() || !RASTER_MIME_PATTERN.test(mimeType)) { return null; } const asset = result.asset ?? {}; const htmlSrc = String(asset.htmlSrc ?? '').trim(); const publicUrl = String(asset.publicUrl ?? '').trim(); const workspaceRelativePath = String(asset.workspaceRelativePath ?? '').trim(); if (!htmlSrc && !publicUrl && !workspaceRelativePath) return null; return { jobId: String(result.jobId), purpose: String(result.purpose ?? '').trim() || null, mimeType, assetId: String(asset.id ?? '').trim() || null, htmlSrc: htmlSrc || null, publicUrl: publicUrl || null, workspaceRelativePath: workspaceRelativePath || null, }; } export function collectWechatGeneratedImages(messages = []) { const requestNames = new Map(); const images = []; for (const message of messages) { for (const item of message?.content ?? []) { if (item?.type === 'toolRequest') { const name = String(item?.toolCall?.value?.name ?? '').trim(); if (item.id && name) requestNames.set(String(item.id), name); continue; } if (item?.type !== 'toolResponse') continue; const name = requestNames.get(String(item.id ?? '')) ?? String(item?.toolResult?.name ?? ''); if (!name.endsWith('generate_image')) continue; const toolResult = item.toolResult ?? {}; if (toolResult.status !== 'success' || toolResult?.value?.isError === true) continue; const generated = normalizeGeneratedImage(parseToolResultJson(toolResult)); if (generated && !images.some((image) => image.jobId === generated.jobId)) images.push(generated); } } return images; } function decodeHtmlAttribute(value) { return String(value ?? '') .replaceAll('"', '"') .replaceAll('"', '"') .replaceAll(''', "'") .replaceAll('&', '&'); } export function extractMindspaceCoverPath(html) { const tag = String(html ?? '').match(/]*name=["']mindspace-cover["'][^>]*>/i)?.[0] ?? ''; if (!tag) return ''; const match = tag.match(/content=(['"])([\s\S]*?)\1/i); if (!match?.[2]) return ''; try { const meta = JSON.parse(decodeHtmlAttribute(match[2])); return String(meta.cover ?? meta.image ?? '').trim(); } catch { return ''; } } function normalizeRelativeAssetPath(value) { const clean = String(value ?? '') .trim() .split(/[?#]/, 1)[0] .replace(/\\/g, '/') .replace(/^\.\//, '') .replace(/^\/+/, ''); return clean.startsWith('public/') ? clean.slice('public/'.length) : clean; } function imageMatchesCover(image, cover, artifact) { if (!cover) return false; if (/^https?:\/\//i.test(cover)) return image.publicUrl === cover; const coverPath = normalizeRelativeAssetPath(cover); const artifactRelativePath = normalizeRelativeAssetPath(artifact?.relativePath ?? ''); const artifactDir = path.posix.dirname(artifactRelativePath); const resolvedCover = normalizeRelativeAssetPath(path.posix.join(artifactDir === '.' ? '' : artifactDir, coverPath)); const candidates = [image.htmlSrc, image.workspaceRelativePath] .map(normalizeRelativeAssetPath) .filter(Boolean); return candidates.includes(coverPath) || candidates.includes(resolvedCover); } const AUXILIARY_PAGE_PATH_PATTERN = /(?:^|[-_.\/])(?:admin|manage|management|backend|password|redirect|download|error)(?:[-_.\/]|$)/i; export function isWechatAuxiliaryPageArtifact(artifact) { const relativePath = String(artifact?.relativePath ?? '').trim().replace(/\\/g, '/'); if (AUXILIARY_PAGE_PATH_PATTERN.test(relativePath)) return true; const localPath = String(artifact?.localPath ?? '').trim(); if (!localPath || !fs.existsSync(localPath)) return false; const html = fs.readFileSync(localPath, 'utf8'); return /]*name=["']mindspace-page-role["'][^>]*content=["'](?:admin|auxiliary)["']/i.test(html) || /<[^>]+data-mindspace-page-role=["'](?:admin|auxiliary)["']/i.test(html); } export function verifyFreshWechatPageThumbnails(artifacts = [], images = []) { if (!Array.isArray(artifacts) || artifacts.length === 0) { return { ok: false, reason: 'missing_page_artifact', matches: [] }; } const eligibleArtifacts = artifacts.filter((artifact) => !isWechatAuxiliaryPageArtifact(artifact)); const skippedArtifacts = artifacts.filter((artifact) => isWechatAuxiliaryPageArtifact(artifact)); if (eligibleArtifacts.length === 0) { return { ok: true, reason: null, matches: [], skippedArtifacts }; } if (!Array.isArray(images) || images.length === 0) { return { ok: false, reason: 'fresh_image_not_generated', matches: [], skippedArtifacts }; } const usedJobs = new Set(); const matches = []; for (const artifact of eligibleArtifacts) { const localPath = String(artifact?.localPath ?? '').trim(); if (!localPath || !fs.existsSync(localPath)) { return { ok: false, reason: 'missing_page_artifact', artifact, matches, skippedArtifacts }; } const cover = extractMindspaceCoverPath(fs.readFileSync(localPath, 'utf8')); const image = images.find((candidate) => !usedJobs.has(candidate.jobId) && imageMatchesCover(candidate, cover, artifact)); if (!image) { return { ok: false, reason: cover ? 'cover_not_from_current_run' : 'missing_generated_cover', artifact, cover: cover || null, matches, skippedArtifacts, }; } usedJobs.add(image.jobId); matches.push({ artifact, image, cover }); } return { ok: true, reason: null, matches, skippedArtifacts }; } function uniqueArtifactsByLocalPath(artifacts = []) { const unique = new Map(); for (const artifact of artifacts) { const localPath = String(artifact?.localPath ?? '').trim(); if (localPath) unique.set(localPath, artifact); } return [...unique.values()]; } function atomicWriteHtml(localPath, content) { const tempPath = path.join( path.dirname(localPath), `.${path.basename(localPath)}.wechat-thumbnail-${process.pid}-${crypto.randomUUID()}.tmp`, ); try { fs.writeFileSync(tempPath, content, 'utf8'); fs.renameSync(tempPath, localPath); } finally { try { if (fs.existsSync(tempPath)) fs.unlinkSync(tempPath); } catch { // Best-effort cleanup only; the destination write already decided the result. } } } export function repairUnambiguousFreshWechatPageThumbnail({ artifacts = [], images = [], currentRunHtmlArtifacts = [], verificationReason = '', } = {}) { if (!['missing_generated_cover', 'cover_not_from_current_run'].includes(verificationReason)) { return { ok: false, reason: 'verification_not_repairable' }; } const eligibleArtifacts = uniqueArtifactsByLocalPath( artifacts.filter((artifact) => !isWechatAuxiliaryPageArtifact(artifact)), ); const pageImages = images.filter((image) => image?.purpose === 'hero' || !image?.purpose); if (eligibleArtifacts.length !== 1 || pageImages.length !== 1) { return { ok: false, reason: 'ambiguous_page_image_mapping', artifactCount: eligibleArtifacts.length, imageCount: pageImages.length, }; } const artifact = eligibleArtifacts[0]; const image = pageImages[0]; const cover = String(image?.htmlSrc ?? '').trim(); const localPath = String(artifact?.localPath ?? '').trim(); if (!cover || !localPath || !fs.existsSync(localPath)) { return { ok: false, reason: 'repair_source_unavailable', artifact, image }; } const normalizedRelativePath = String(artifact.relativePath ?? '').trim().replace(/\\/g, '/'); const currentRunArtifact = currentRunHtmlArtifacts.find( (candidate) => String(candidate?.relativePath ?? '').trim().replace(/\\/g, '/') === normalizedRelativePath, ); const sourceHtml = typeof currentRunArtifact?.content === 'string' ? currentRunArtifact.content : fs.readFileSync(localPath, 'utf8'); const coverMeta = parseMindspaceCoverMeta(sourceHtml); if (!coverMeta) { return { ok: false, reason: 'valid_cover_meta_required', artifact, image }; } const repairedHtml = upsertMindspaceCoverMeta(sourceHtml, { cover }); atomicWriteHtml(localPath, repairedHtml); return { ok: true, reason: null, artifact, image, cover, restoredCurrentRunHtml: Boolean(currentRunArtifact), }; } export function summarizeFreshWechatThumbnailVerification(verification, images = []) { return { reason: String(verification?.reason ?? 'unknown'), artifact: verification?.artifact ? { relativePath: String(verification.artifact.relativePath ?? ''), localPath: path.basename(String(verification.artifact.localPath ?? '')), } : null, cover: String(verification?.cover ?? ''), matches: Array.isArray(verification?.matches) ? verification.matches.length : 0, images: images.map((image) => ({ jobId: String(image?.jobId ?? ''), purpose: String(image?.purpose ?? ''), htmlSrc: String(image?.htmlSrc ?? ''), workspaceRelativePath: String(image?.workspaceRelativePath ?? ''), })), }; }