Files
memind/conversation-display.mjs
john 74150a30e3
Memind CI / Test, build, and release guards (pull_request) Failing after 18s
feat(search): enable MindSearch defaults and sanitize user-facing search language.
Grant search_external and network egress for default users, enable search-enhanced skill, redact internal architecture terms from assistant UI text, and neutralize search skill prompts so chats use dual-engine web search without exposing vendor names.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-30 10:43:25 +08:00

102 lines
4.2 KiB
JavaScript

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;
}
const ASSISTANT_DELIVERABLE_RE =
/\[[^\]]+\]\(https?:\/\/[^)]+\)|https?:\/\/(?:m\.)?[^/\s]*tkmind\.(?:cn|ai)\/MindSpace\//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 ?? '');
for (const [pattern, replacement] of USER_FACING_ARCHITECTURE_TERM_REPLACEMENTS) {
next = next.replace(pattern, replacement);
}
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();
}