import fs from 'node:fs'; import path from 'node:path'; import { extractPublicHtmlWriteArtifacts, isStubPublicHtmlContent, materializeMissingPublicHtmlWrites, normalizePublicHtmlRelativePath, } from './mindspace-public-finish-sync.mjs'; import { repairUnambiguousFreshWechatPageThumbnail, verifyFreshWechatPageThumbnails, } from './wechat/verify/generated-thumbnail.mjs'; import { ensureWorkspaceHtmlThumbnail, workspaceThumbnailRelativePath, } from './mindspace-workspace-thumbnails.mjs'; import { verifySharePreviewMeta, } from './wechat/verify/share-preview.mjs'; import { ensureArtifactsSharePreviewReady, } from './wechat/verify/share-preview-repair.mjs'; const PUBLIC_HTML_LINK_PATTERN = /https?:\/\/[^\s<>"')\]]+\/MindSpace\/([0-9a-f-]{36}|[a-z0-9._-]+)\/public\/([^\s<>"')\]]+\.html)/gi; const AUXILIARY_PAGE_PATH_PATTERN = /(?:^|[-_.\/])(?:admin|manage|management|backend|password|redirect|download|error)(?:[-_.\/]|$)/i; function replyMessages(reply) { return Array.isArray(reply?.messages) ? reply.messages : []; } function messageVisibleText(message) { if (!message?.content) return ''; return message.content .filter((item) => item.type === 'text' && typeof item.text === 'string') .map((item) => item.text) .join(''); } function collectReplyLinkScanText(reply) { const parts = []; const seen = new Set(); const append = (text) => { const normalized = String(text ?? '').trim(); if (!normalized || seen.has(normalized)) return; seen.add(normalized); parts.push(normalized); }; append(reply?.text); for (const message of replyMessages(reply)) { if (message?.role !== 'assistant') continue; append(messageVisibleText(message)); } return parts.join('\n'); } function extractHtmlWriteTargets(messages = []) { const targets = new Set(); for (const message of messages) { for (const item of message?.content ?? []) { if (item?.type !== 'toolRequest') continue; const toolCall = item.toolCall?.value; const args = toolCall?.arguments ?? {}; const name = String(toolCall?.name ?? '').trim(); const action = String(args.action ?? '').trim().toLowerCase(); const writeLike = (name === 'developer' && (action === 'write' || action === 'edit')) || name === 'write' || name.endsWith('__write') || name === 'write_file' || name === 'edit_file' || name.endsWith('__write_file') || name.endsWith('__edit_file'); if (!writeLike || typeof args.path !== 'string') { continue; } const candidate = String(args.path).trim(); if (candidate.toLowerCase().endsWith('.html')) { targets.add(candidate); } } } return [...targets]; } function extractRequestedHtmlTarget(text) { const value = String(text ?? ''); if (!value) return ''; const explicitPublicMatch = value.match( /(?:^|[\s`'"])(public\/[a-z0-9._-]+\.html)\b/i, ); if (explicitPublicMatch?.[1]) { return explicitPublicMatch[1]; } const bareMatch = value.match( /(?:^|[\s`'"])([a-z0-9._-]+\.html)\b/i, ); return bareMatch?.[1] ? `public/${bareMatch[1]}` : ''; } function collectLinkedHtmlFilenames(text) { const filenames = new Set(); for ( const match of String(text ?? '') .matchAll(PUBLIC_HTML_LINK_PATTERN) ) { const filename = path.posix.basename( String(match[2] ?? '').trim(), ); if (filename) filenames.add(filename); } return filenames; } function readArtifactDescriptor( target, { publishDir, buildCanonicalUrl, copyIntoPublic = true, }, ) { const workspaceRoot = path.resolve(String(publishDir ?? '')); if (!workspaceRoot) return null; const candidate = String(target ?? '').trim(); if (!candidate) return null; const source = path.isAbsolute(candidate) ? path.resolve(candidate) : path.resolve(workspaceRoot, candidate); if ( source !== workspaceRoot && !source.startsWith( `${workspaceRoot}${path.sep}`, ) ) { return null; } try { if ( !fs.existsSync(source) || !fs.statSync(source).isFile() ) { return null; } } catch { return null; } const publicRoot = path.join(workspaceRoot, 'public'); let publishedPath = source; if ( copyIntoPublic && source !== publicRoot && !source.startsWith(`${publicRoot}${path.sep}`) ) { fs.mkdirSync(publicRoot, { recursive: true }); publishedPath = path.join( publicRoot, path.basename(source), ); if (publishedPath !== source) { fs.copyFileSync(source, publishedPath); } } const relativePath = path .relative(workspaceRoot, publishedPath) .replace(/\\/g, '/'); if ( !relativePath || relativePath.startsWith('..') || path.isAbsolute(relativePath) ) { return null; } const normalizedRelativePath = normalizePublicHtmlRelativePath(relativePath); if ( !normalizedRelativePath .toLowerCase() .endsWith('.html') ) { return null; } let content = ''; let stat; try { content = fs.readFileSync( publishedPath, 'utf8', ); stat = fs.statSync(publishedPath); } catch { return null; } const canonicalUrl = String( buildCanonicalUrl?.( normalizedRelativePath, ) ?? '', ).trim(); const auxiliary = AUXILIARY_PAGE_PATH_PATTERN.test( normalizedRelativePath, ) || /]*name=["']mindspace-page-role["'][^>]*content=["'](?:admin|auxiliary)["']/i.test( content, ) || /<[^>]+data-mindspace-page-role=["'](?:admin|auxiliary)["']/i.test( content, ); const sharePreview = verifySharePreviewMeta(content); return { localPath: publishedPath, relativePath: normalizedRelativePath, url: canonicalUrl, canonicalUrl, exists: true, isStub: isStubPublicHtmlContent(content), isHtmlDocument: /<(?:html|body|main|article)\b/i.test( content, ), sharePreview, auxiliary, sizeBytes: Number(stat.size ?? 0), mtimeMs: Number(stat.mtimeMs ?? 0), }; } function collectRecentArtifacts({ publishDir, buildCanonicalUrl, replyText = '', requestStartedAt = 0, limit = 20, }) { const publicRoot = path.join( path.resolve(publishDir), 'public', ); try { if ( !fs.existsSync(publicRoot) || !fs.statSync(publicRoot).isDirectory() ) { return []; } } catch { return []; } const linkedFilenames = collectLinkedHtmlFilenames(replyText); const artifacts = []; const stack = [publicRoot]; while ( stack.length > 0 && artifacts.length < limit ) { const current = stack.pop(); let entries; try { entries = fs.readdirSync(current, { withFileTypes: true, }); } catch { continue; } for (const entry of entries) { const absolutePath = path.join(current, entry.name); if (entry.isDirectory()) { stack.push(absolutePath); continue; } if ( !entry.isFile() || !entry.name.toLowerCase().endsWith('.html') ) { continue; } const artifact = readArtifactDescriptor( absolutePath, { publishDir, buildCanonicalUrl, copyIntoPublic: false, }, ); if (!artifact) continue; if ( requestStartedAt > 0 && artifact.mtimeMs + 1 < requestStartedAt ) { continue; } const filename = path.posix.basename( artifact.relativePath, ); if ( linkedFilenames.size > 0 && !linkedFilenames.has(filename) ) { continue; } artifacts.push(artifact); if (artifacts.length >= limit) break; } } return artifacts.sort( (left, right) => right.mtimeMs - left.mtimeMs, ); } function uniqueArtifacts(artifacts = []) { const byRelativePath = new Map(); for (const artifact of artifacts) { const relativePath = String( artifact?.relativePath ?? '', ).trim(); if (relativePath) { byRelativePath.set(relativePath, artifact); } } return [...byRelativePath.values()]; } function sanitizeArtifact(artifact) { if (!artifact) return null; const { localPath: _localPath, mtimeMs: _mtimeMs, ...safeArtifact } = artifact; return safeArtifact; } function sanitizeArtifacts(artifacts = []) { return artifacts .map(sanitizeArtifact) .filter(Boolean); } function buildValidReplyUrls( replyText, confirmedArtifacts, ) { const artifactsByFilename = new Map( confirmedArtifacts.map((artifact) => [ path.posix.basename(artifact.relativePath), artifact, ]), ); const urls = []; for ( const match of String(replyText ?? '') .matchAll(PUBLIC_HTML_LINK_PATTERN) ) { const filename = path.posix.basename( String(match[2] ?? '').trim(), ); if (artifactsByFilename.has(filename)) { urls.push(match[0]); } } return [...new Set(urls)]; } export function prepareWechatHtmlDeliveryAtWorkspace({ reply = null, intent = null, publishDir, buildCanonicalUrl, requestStartedAt = 0, allowRecentArtifacts = true, } = {}) { const messages = replyMessages(reply); const materialization = materializeMissingPublicHtmlWrites({ messages, publishDir, }); const publishedArtifacts = uniqueArtifacts( extractHtmlWriteTargets(messages) .map((target) => readArtifactDescriptor(target, { publishDir, buildCanonicalUrl, }), ) .filter(Boolean), ); const expectedTarget = extractRequestedHtmlTarget( intent?.agentText ?? intent?.displayText ?? '', ); const expectedArtifacts = expectedTarget ? [ readArtifactDescriptor(expectedTarget, { publishDir, buildCanonicalUrl, }), ].filter(Boolean) : []; const recentArtifacts = allowRecentArtifacts ? collectRecentArtifacts({ publishDir, buildCanonicalUrl, replyText: collectReplyLinkScanText(reply), requestStartedAt, }) : []; const confirmedArtifacts = ensureArtifactsSharePreviewReady( uniqueArtifacts([ ...publishedArtifacts, ...expectedArtifacts, ...recentArtifacts, ]), { publishDir, topic: String( intent?.agentText ?? intent?.displayText ?? '', ).trim(), }, ).map( (artifact) => readArtifactDescriptor(artifact.relativePath, { publishDir, buildCanonicalUrl, copyIntoPublic: false, }) ?? artifact, ); const linkedFilenames = collectLinkedHtmlFilenames(collectReplyLinkScanText(reply)); const matchedArtifacts = linkedFilenames.size > 0 ? confirmedArtifacts.filter((artifact) => linkedFilenames.has( path.posix.basename( artifact.relativePath, ), ), ) : confirmedArtifacts; const validReplyUrls = buildValidReplyUrls( collectReplyLinkScanText(reply), confirmedArtifacts, ); return { materialization, publishedArtifacts: sanitizeArtifacts(publishedArtifacts), expectedArtifacts: sanitizeArtifacts(expectedArtifacts), recentArtifacts: sanitizeArtifacts(recentArtifacts), confirmedArtifacts: sanitizeArtifacts(confirmedArtifacts), verifiedArtifacts: sanitizeArtifacts(matchedArtifacts), validReplyUrls, hasValidReplyLink: validReplyUrls.length > 0, }; } function restoreWorkspaceArtifacts({ artifacts, publishDir, buildCanonicalUrl, }) { return uniqueArtifacts( artifacts .map((artifact) => readArtifactDescriptor( artifact?.relativePath, { publishDir, buildCanonicalUrl, copyIntoPublic: false, }, ), ) .filter(Boolean), ); } function sanitizeFreshVerification( verification, repair = null, ) { return { ok: verification?.ok === true, reason: verification?.reason == null ? null : String(verification.reason), artifact: sanitizeArtifact( verification?.artifact, ), cover: String(verification?.cover ?? '').trim() || null, matchRelativePaths: Array.isArray( verification?.matches, ) ? verification.matches .map((match) => String( match?.artifact?.relativePath ?? '', ).trim(), ) .filter(Boolean) : [], skippedRelativePaths: Array.isArray( verification?.skippedArtifacts, ) ? verification.skippedArtifacts .map((artifact) => String( artifact?.relativePath ?? '', ).trim(), ) .filter(Boolean) : [], repair: repair ? { ok: repair.ok === true, reason: repair.reason == null ? null : String(repair.reason), relativePath: String( repair?.artifact?.relativePath ?? '', ).trim() || null, cover: String(repair?.cover ?? '').trim() || null, } : null, }; } function collectDeliverablePageArtifacts({ artifacts = [], messages = [], publishDir, buildCanonicalUrl, } = {}) { const restored = restoreWorkspaceArtifacts({ artifacts, publishDir, buildCanonicalUrl, }); if (restored.length > 0) return restored; const writeArtifacts = extractPublicHtmlWriteArtifacts( messages, { publishDir }, ); return restoreWorkspaceArtifacts({ artifacts: writeArtifacts.map((artifact) => ({ relativePath: artifact.relativePath, })), publishDir, buildCanonicalUrl, }); } async function acceptWorkspaceThumbnailFallback({ artifacts = [], publishDir, } = {}) { const eligible = artifacts.filter((artifact) => { const localPath = String(artifact?.localPath ?? '').trim(); return localPath && fs.existsSync(localPath) && artifact?.auxiliary !== true; }); if (eligible.length === 0) { return { ok: false, reason: 'missing_page_artifact', matches: [] }; } await Promise.all( eligible.map((artifact) => ensureWorkspaceHtmlThumbnail( publishDir, artifact.relativePath, ), ), ); const withThumbnails = eligible.filter((artifact) => fs.existsSync( path.join( publishDir, workspaceThumbnailRelativePath(artifact.relativePath), ), ), ); if (withThumbnails.length !== eligible.length) { return { ok: false, reason: 'workspace_thumbnail_fallback_failed', matches: [], artifact: eligible.find((artifact) => !fs.existsSync( path.join( publishDir, workspaceThumbnailRelativePath(artifact.relativePath), ), ), ), }; } return { ok: true, reason: 'workspace_thumbnail_fallback', matches: withThumbnails.map((artifact) => ({ artifact, image: null, cover: null, })), }; } export async function ensureWechatFreshPageThumbnailsAtWorkspace({ artifacts = [], images = [], messages = [], publishDir, buildCanonicalUrl, repairEnabled = false, } = {}) { let workspaceArtifacts = collectDeliverablePageArtifacts({ artifacts, messages, publishDir, buildCanonicalUrl, }); let verification = verifyFreshWechatPageThumbnails( workspaceArtifacts, images, { publishDir }, ); let repair = null; if ( !verification.ok && repairEnabled ) { repair = repairUnambiguousFreshWechatPageThumbnail({ artifacts: workspaceArtifacts, images, currentRunHtmlArtifacts: extractPublicHtmlWriteArtifacts( messages, { publishDir }, ), verificationReason: verification.reason, }); if (repair.ok) { workspaceArtifacts = restoreWorkspaceArtifacts({ artifacts: workspaceArtifacts, publishDir, buildCanonicalUrl, }); verification = verifyFreshWechatPageThumbnails( workspaceArtifacts, images, { publishDir }, ); } } // When image_make failed or returned nothing, still deliver pages that already // landed on disk by generating the workspace share thumbnail sidecars. if ( !verification.ok && ['fresh_image_not_generated', 'missing_generated_cover', 'cover_not_from_current_run'].includes( String(verification.reason ?? ''), ) && workspaceArtifacts.length > 0 ) { verification = await acceptWorkspaceThumbnailFallback({ artifacts: workspaceArtifacts, publishDir, }); } if (verification.ok) { await Promise.all( (verification.matches ?? []).map((match) => ensureWorkspaceHtmlThumbnail( publishDir, match.artifact.relativePath, ), ), ); } return { ...sanitizeFreshVerification( verification, repair, ), thumbnailRelativePaths: verification.ok ? (verification.matches ?? []).map((match) => workspaceThumbnailRelativePath( match.artifact.relativePath, ), ) : [], }; } export const mindSpaceWechatHtmlDeliveryInternals = { buildValidReplyUrls, collectLinkedHtmlFilenames, extractHtmlWriteTargets, extractRequestedHtmlTarget, readArtifactDescriptor, sanitizeArtifact, };