import fs from 'node:fs'; import path from 'node:path'; import { PAGE_TEMPLATE_SKILL_PREFIX, isPageTemplateSkill } from './skills-registry.mjs'; const PLACEHOLDER_PATTERN = /\{\{[A-Z0-9_]+\}\}/g; export function resolvePageTemplateHtmlPath(skillName, h5Root = process.cwd()) { if (!isPageTemplateSkill(skillName)) return null; const templatesDir = path.join(h5Root, 'skills', skillName, 'templates'); if (!fs.existsSync(templatesDir)) return null; const files = fs .readdirSync(templatesDir) .filter((name) => name.endsWith('.html')) .sort(); if (!files.length) return null; return path.join(templatesDir, files[0]); } export function extractStyleBlock(html) { const match = String(html ?? '').match(/]*>([\s\S]*?)<\/style>/i); return match?.[1] ?? ''; } export function normalizeTemplateStyleFingerprint(styleText) { return String(styleText ?? '') .replace(PLACEHOLDER_PATTERN, 'VALUE') .replace(/#[0-9a-fA-F]{3,8}/g, 'VALUE') .replace(/url\(['"]?[^'")]+['"]?\)/g, 'url(VALUE)') .replace(/\s+/g, ' ') .trim(); } export function extractStyleSelectors(styleText) { const selectors = new Set(); const withoutComments = String(styleText ?? '').replace(/\/\*[\s\S]*?\*\//g, ''); for (const chunk of withoutComments.split('}')) { const idx = chunk.lastIndexOf('{'); if (idx <= 0) continue; const selectorPart = chunk.slice(0, idx).trim(); if (!selectorPart || selectorPart.startsWith('@')) continue; for (const sel of selectorPart.split(',')) { const normalized = sel.trim().replace(/\s+/g, ' '); if (normalized) selectors.add(normalized); } } return selectors; } export function resolveSelectedPageTemplateSkill(messages) { for (let index = (Array.isArray(messages) ? messages.length : 0) - 1; index >= 0; index -= 1) { const message = messages[index]; if (message?.role !== 'user') continue; const metadata = message?.metadata ?? {}; const runMetadata = metadata.memindRun ?? metadata.agentRun ?? {}; const skill = String(runMetadata.selectedChatSkill ?? metadata.selectedChatSkill ?? '').trim(); if (isPageTemplateSkill(skill)) return skill; } return null; } export function buildPageTemplateMandatoryInstruction(skillName, h5Root = process.cwd()) { const templatePath = resolvePageTemplateHtmlPath(skillName, h5Root); const workspaceTemplatePath = templatePath ? `.agents/skills/${skillName}/templates/${path.basename(templatePath)}` : `.agents/skills/${skillName}/templates/*.html`; return [ `【强制页面模板:${skillName}】`, `1. 必须先 load_skill → ${skillName},再 read_file → ${workspaceTemplatePath}`, '2. 只能替换模板中的 {{PLACEHOLDER}};禁止重写 、