import { stripKnownChatSkillPrompt } from './chat-skills.mjs'; /** * Strip agent-only prefixes from persisted user messages for UI display. * Regression guard — see docs/regression-guards/mindspace-publish-and-chat-finish.md */ export const TASK_ROUTING_HINT_RE = /^【TKMind 路由提示】[\s\S]*?\n\n/; export const MEMIND_TASK_ORCHESTRATION_RE = /^【Memind 任务编排】[\s\S]*?(?:用户任务:|用户任务:)\s*/u; export const MINDSPACE_CONTEXT_RE = /^\[MindSpace 上下文\][\s\S]*?\n\n/; export const IMAGE_TURN_SCOPE_SUFFIX_RE = /\n*【本轮图片主题(硬性)】[\s\S]*$/u; const USER_IDENTITY_BLOCK_RE = /^\[用户身份\][\s\S]*?\n\n/; export function stripUserIdentityPrefix(text) { return String(text ?? '').replace(USER_IDENTITY_BLOCK_RE, '').trimStart(); } export function stripTaskRoutingHint(text) { return String(text ?? '').replace(TASK_ROUTING_HINT_RE, '').trimStart(); } export function stripMemindTaskOrchestrationPrefix(text) { return String(text ?? '').replace(MEMIND_TASK_ORCHESTRATION_RE, '').trimStart(); } export function stripMindSpaceContextPrefix(text) { let next = String(text ?? ''); while (MINDSPACE_CONTEXT_RE.test(next)) { next = next.replace(MINDSPACE_CONTEXT_RE, '').trimStart(); } return next; } /** MindSpace 交付链接:生产域名与本地 dev(127.0.0.1 / localhost)都必须可点击,不可被架构脱敏替换。 */ export const MINDSPACE_DELIVERABLE_ORIGIN_RE = '(?:127\\.0\\.0\\.1|localhost):\\d+|(?:m\\.)?[^/\\s]*tkmind\\.(?:cn|ai)'; const ASSISTANT_DELIVERABLE_RE = new RegExp( [ '\\[[^\\]]+\\]\\(https?:\\/\\/[^)]+\\)', `https?:\\/\\/${MINDSPACE_DELIVERABLE_ORIGIN_RE}\\/MindSpace\\/`, ].join('|'), 'i', ); const INTERNAL_ASSISTANT_MARKERS = [ /data-mindspace-page-tag/i, /mindspace-cover/i, /\bload_skill\b/i, /static-page-publish/i, /\.agents\/skills/i, /SKILL\.md/i, /注意到技能/u, /技能更新/u, /页脚要用.*platform-brand/u, ]; /** Replace internal architecture / vendor names before showing assistant text to users. */ export const USER_FACING_ARCHITECTURE_TERM_REPLACEMENTS = Object.freeze([ [/duck\s*duck\s*go|\bduckduckgo\b|\bddg\b/gi, '联网搜索'], [/\bsearxng\b|\bsearx\b/gi, '联网搜索'], [/\bgoosed\b|\bgoose\b/gi, '助手'], [/\bcolima\b|\bdocker\b|\bkubernetes\b|\bk8s\b/gi, '运行环境'], [/\b(?:stdio\s*)?mcp\b|\btkmind-search\b|\bsandbox-fs\b/gi, '工具扩展'], [/\bopenhands\b|\baider\b|\blitellm\b/gi, '代码助手'], [/\borchestrator\b|\bmemindadm\b|\bportal\b(?=\s*(?:api|server|runtime|8081|8085))/gi, '后台服务'], [/host\.docker\.internal|127\.0\.0\.1:\d{4,5}/gi, '本地服务'], [/platform\/web|web_search|fetch_url|tkmind_search|tkmind_read/gi, '联网搜索'], [/\bpostgres(?:ql)?\b|\bredis\b|\bmysql\b|\bweaviate\b/gi, '数据服务'], ]); export const USER_FACING_ARCHITECTURE_LANGUAGE_RULE = '- 向用户回复时禁止出现内部实现、架构、供应商或运行时名称(搜索引擎品牌、中间件、容器平台、MCP、编排/代理/代码执行器名称等);统一说「联网搜索」「搜索服务」「助手」「数据服务」'; /** Strip or neutralize architecture terms from assistant replies shown in chat UI. */ export function redactUserFacingArchitectureTerms(text) { let next = String(text ?? ''); const protectedSegments = []; const protect = (match) => { const token = `__MINDSPACE_DELIVERABLE_${protectedSegments.length}__`; protectedSegments.push(match); return token; }; next = next.replace( new RegExp( `\\[[^\\]]+\\]\\(https?:\\/\\/${MINDSPACE_DELIVERABLE_ORIGIN_RE}\\/MindSpace\\/[^)]+\\)`, 'gi', ), protect, ); next = next.replace( new RegExp( `https?:\\/\\/${MINDSPACE_DELIVERABLE_ORIGIN_RE}\\/MindSpace\\/[^\\s)]+`, 'gi', ), protect, ); for (const [pattern, replacement] of USER_FACING_ARCHITECTURE_TERM_REPLACEMENTS) { next = next.replace(pattern, replacement); } for (const [index, segment] of protectedSegments.entries()) { next = next.replace(`__MINDSPACE_DELIVERABLE_${index}__`, segment); } return next.replace(/[ \t]{2,}/g, ' ').replace(/\n{3,}/g, '\n\n').trim(); } /** Agent-only process narration that should not appear in the chat UI. */ export function isInternalAssistantProcessNarration(text) { const trimmed = String(text ?? '').trim(); if (!trimmed) return false; if (ASSISTANT_DELIVERABLE_RE.test(trimmed)) return false; return INTERNAL_ASSISTANT_MARKERS.some((pattern) => pattern.test(trimmed)); } export function deriveAssistantFacingText(text) { const trimmed = String(text ?? '').trim(); if (!trimmed) return ''; if (isInternalAssistantProcessNarration(trimmed)) return ''; return redactUserFacingArchitectureTerms(trimmed); } /** Strip agent-only prefixes from persisted user message content for UI display. */ export function deriveUserFacingText(text) { let next = String(text ?? ''); next = next.replace(IMAGE_TURN_SCOPE_SUFFIX_RE, ''); next = stripUserIdentityPrefix(next); next = stripTaskRoutingHint(next); next = stripMemindTaskOrchestrationPrefix(next); next = stripMindSpaceContextPrefix(next); next = stripKnownChatSkillPrompt(next); return next.trim(); }