import { deriveUserFacingText } from './conversation-display.mjs'; const CONFIG_TABLE = 'h5_system_disclosure_policy_config'; const CONFIG_SCOPE = 'global'; const POLICY_ID = 'system-disclosure'; const DEFAULT_REFRESH_INTERVAL_MS = 5000; export const SYSTEM_DISCLOSURE_MODE = Object.freeze({ OFF: 'off', SHADOW: 'shadow', ENFORCE: 'enforce', }); export const DEFAULT_SYSTEM_DISCLOSURE_REFUSAL = '出于安全与隐私考虑,我不能提供 TKMind、Memind 或 MindSpace 的内部技术实现信息。我可以继续帮助你了解公开功能、使用方法、服务边界,或者讨论不针对本系统的一般性技术原理。'; const DEFAULT_PRODUCT_NAMES = Object.freeze([ 'tkmind', 'memind', 'mindspace', '智趣', ]); const DEFAULT_SELF_REFERENCES = Object.freeze([ '本系统', '这个系统', '该系统', '你们系统', '你们的系统', '你们平台', '你们的平台', '这个平台', '该平台', '你的系统', '你的平台', '你们产品', '你们的产品', ]); const TECHNICAL_CATEGORY_PATTERNS = Object.freeze({ architecture: [ /(?:底层|内部|系统|技术|整体|服务|运行时|agent)\s*(?:架构|设计|实现|原理|机制)/iu, /(?:架构|设计|实现|原理|机制)\s*(?:图|说明|细节|文档|方案|是什|怎么|如何)/iu, /(?:技术栈|开发语言|编程语言|前端框架|后端框架|开源组件|内部组件|依赖版本|运行框架)/iu, /(?:tkmind|memind|mindspace).{0,24}(?:关系|区别|协作|调用链|怎么工作|如何工作)/iu, /\b(?:architecture|internals?|implementation|system design|runtime design)\b/iu, ], model_and_routing: [ /(?:模型|大模型|llm|provider|供应商).{0,18}(?:选择|路由|调用|切换|配置|名称|版本|怎么|如何|哪些|什么)/iu, /(?:使用|采用|接入|调用).{0,12}(?:什么|哪个|哪些)?(?:模型|大模型|llm|agent\s*框架)/iu, /(?:意图识别|意图路由|任务路由|模型路由|agent\s*编排|智能体编排)/iu, /\b(?:model routing|intent routing|agent orchestration|provider routing)\b/iu, ], prompts_and_memory: [ /(?:系统提示词|system\s*prompt|隐藏提示词|内部提示词|开发者提示词|developer\s*message)/iu, /(?:记忆|memory|上下文).{0,18}(?:注入|召回|存储|实现|机制|路由|拼接|读取)/iu, ], tools_and_extensions: [ /(?:工具|扩展|插件|skill|mcp).{0,18}(?:清单|列表|数量|名称|调用|编排|配置|内部|有哪些|多少)/iu, /(?:agent|智能体).{0,18}(?:数量|几个|名称|分工|清单|列表|怎么协作|如何协作)/iu, /(?:几个|多少|哪些).{0,8}(?:agent|智能体|工具|扩展|插件)/iu, /(?:sandbox-fs|developer\s*tool|tool\s*gateway|extension\s*override)/iu, ], data_and_deployment: [ /(?:数据库|存储|数据层|表结构|schema).{0,18}(?:类型|选型|结构|实现|位置|连接|配置|怎么|如何|什么)/iu, /(?:数据|文件|记忆).{0,12}(?:存在哪里|保存在哪|落在哪里|怎么保存|如何保存)/iu, /(?:部署|拓扑|服务器|主机|端口|容器|运行目录|源码目录|仓库路径|内部\s*api|内部接口)/iu, /(?:源码|代码|仓库).{0,12}(?:开源|位置|目录|结构|地址|在哪)/iu, /\b(?:deployment|topology|database schema|source tree|internal api|host|port)\b/iu, ], security_boundaries: [ /(?:沙箱|权限|安全边界|防护|鉴权|认证|密钥|token).{0,18}(?:实现|机制|策略|配置|绕过|细节|怎么|如何)/iu, /(?:越权|绕过|突破|规避).{0,18}(?:限制|防护|沙箱|鉴权|策略)/iu, /\b(?:security boundary|sandbox escape|bypass|auth internals)\b/iu, ], }); const IMPLICIT_SELF_TECHNICAL_PATTERNS = Object.freeze([ /你(?:们)?(?:到底)?(?:使用|采用|接入|调用|运行).{0,12}(?:什么|哪个|哪些)?(?:模型|大模型|llm|数据库|框架|技术栈)/iu, /你(?:们)?(?:使用|用了|有|部署).{0,8}(?:几个|多少|哪些).{0,8}(?:agent|智能体|工具|扩展|插件)/iu, /你(?:们)?(?:的)?(?:底层|内部|后台).{0,12}(?:架构|实现|原理|机制|模型|技术栈)/iu, /你(?:是|们是)?(?:怎么|如何).{0,18}(?:路由|编排|调用工具|保存记忆|部署|存储数据)/iu, ]); function clone(value) { if (typeof structuredClone === 'function') return structuredClone(value); return JSON.parse(JSON.stringify(value)); } function normalizeBoolean(value, fallback = false) { if (value == null || value === '') return fallback; if (typeof value === 'boolean') return value; const normalized = String(value).trim().toLowerCase(); if (['1', 'true', 'yes', 'on'].includes(normalized)) return true; if (['0', 'false', 'no', 'off'].includes(normalized)) return false; return fallback; } function normalizeMode(value, fallback = SYSTEM_DISCLOSURE_MODE.SHADOW) { const normalized = String(value ?? '').trim().toLowerCase(); return Object.values(SYSTEM_DISCLOSURE_MODE).includes(normalized) ? normalized : fallback; } function normalizeStringList(value, fallback) { if (!Array.isArray(value)) return [...fallback]; const items = [...new Set(value.map((item) => String(item ?? '').trim()).filter(Boolean))]; return items.length ? items.slice(0, 100) : [...fallback]; } function normalizeRefusalText(value) { const normalized = String(value ?? '').trim(); return normalized ? normalized.slice(0, 1000) : DEFAULT_SYSTEM_DISCLOSURE_REFUSAL; } export function defaultSystemDisclosureConfig() { return { enabled: true, mode: SYSTEM_DISCLOSURE_MODE.SHADOW, refusalText: DEFAULT_SYSTEM_DISCLOSURE_REFUSAL, productNames: [...DEFAULT_PRODUCT_NAMES], selfReferences: [...DEFAULT_SELF_REFERENCES], categories: Object.fromEntries( Object.keys(TECHNICAL_CATEGORY_PATTERNS).map((key) => [key, true]), ), }; } export function normalizeSystemDisclosureConfig(input = {}, fallback = defaultSystemDisclosureConfig()) { const source = input && typeof input === 'object' && !Array.isArray(input) ? input : {}; const base = fallback && typeof fallback === 'object' ? fallback : defaultSystemDisclosureConfig(); const next = { enabled: normalizeBoolean(source.enabled, base.enabled), mode: normalizeMode(source.mode, base.mode), refusalText: normalizeRefusalText(source.refusalText ?? base.refusalText), productNames: normalizeStringList(source.productNames, base.productNames), selfReferences: normalizeStringList(source.selfReferences, base.selfReferences), categories: {}, }; for (const key of Object.keys(TECHNICAL_CATEGORY_PATTERNS)) { next.categories[key] = normalizeBoolean(source.categories?.[key], base.categories?.[key] !== false); } if (!next.enabled) next.mode = SYSTEM_DISCLOSURE_MODE.OFF; return next; } function parseJsonLike(value, fallback) { if (value == null || value === '') return fallback; if (typeof value === 'object') return value; try { return JSON.parse(String(value)); } catch { return fallback; } } function normalizeText(value) { return String(value ?? '') .normalize('NFKC') .replace(/\s+/g, ' ') .trim() .toLowerCase(); } function includesAny(text, values) { return values.some((value) => text.includes(normalizeText(value))); } function matchingCategories(text, config) { const matched = []; for (const [category, patterns] of Object.entries(TECHNICAL_CATEGORY_PATTERNS)) { if (config.categories?.[category] === false) continue; if (patterns.some((pattern) => pattern.test(text))) matched.push(category); } return matched; } export function extractSystemDisclosureMessageText(message) { const displayText = String(message?.metadata?.displayText ?? '').trim(); if (displayText) return deriveUserFacingText(displayText); if (typeof message?.content === 'string') return deriveUserFacingText(message.content); if (!Array.isArray(message?.content)) { return deriveUserFacingText(message?.text ?? message?.value ?? ''); } return deriveUserFacingText(message.content .filter((item) => item?.type === 'text') .map((item) => String(item.text ?? '').trim()) .filter(Boolean) .join('\n')); } export function evaluateSystemDisclosureText(text, configInput = defaultSystemDisclosureConfig()) { const config = normalizeSystemDisclosureConfig(configInput); const normalizedText = normalizeText(text); const base = { policyId: POLICY_ID, policyVersion: null, mode: config.mode, action: 'allow', matched: false, enforced: false, reasonCode: null, categories: [], responseText: null, }; if (!config.enabled || config.mode === SYSTEM_DISCLOSURE_MODE.OFF || !normalizedText) { return base; } const categories = matchingCategories(normalizedText, config); if (!categories.length) return base; const explicitProduct = includesAny(normalizedText, config.productNames); const explicitSelfReference = includesAny(normalizedText, config.selfReferences); const implicitSelfTechnical = IMPLICIT_SELF_TECHNICAL_PATTERNS.some((pattern) => pattern.test(normalizedText)); if (!explicitProduct && !explicitSelfReference && !implicitSelfTechnical) return base; const enforced = config.mode === SYSTEM_DISCLOSURE_MODE.ENFORCE; return { ...base, action: enforced ? 'refuse' : 'allow', wouldAction: 'refuse', matched: true, enforced, reasonCode: 'SYSTEM_TECHNICAL_DISCLOSURE', categories, responseText: enforced ? config.refusalText : null, }; } async function ensureConfigTable(pool) { await pool.query(` CREATE TABLE IF NOT EXISTS ${CONFIG_TABLE} ( config_scope VARCHAR(32) PRIMARY KEY, config_json JSON NOT NULL, policy_version BIGINT NOT NULL DEFAULT 1, updated_by CHAR(36) NULL, updated_at BIGINT NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci `); } async function loadStoredState(pool) { await ensureConfigTable(pool); const [rows] = await pool.query( `SELECT config_json, policy_version, updated_by, updated_at FROM ${CONFIG_TABLE} WHERE config_scope = ? LIMIT 1`, [CONFIG_SCOPE], ); const row = rows[0]; if (!row) return null; return { config: normalizeSystemDisclosureConfig( parseJsonLike(row.config_json, defaultSystemDisclosureConfig()), ), policyVersion: Math.max(1, Number(row.policy_version ?? 1) || 1), updatedBy: row.updated_by ?? null, updatedAt: Number(row.updated_at ?? 0) || null, }; } export function createSystemDisclosurePolicyService( pool, { refreshIntervalMs = DEFAULT_REFRESH_INTERVAL_MS, logger = console, autoRefresh = true, } = {}, ) { let state = { config: defaultSystemDisclosureConfig(), policyVersion: 0, updatedBy: null, updatedAt: null, source: 'default', refreshedAt: null, lastRefreshError: null, }; let refreshTimer = null; let refreshPromise = null; async function refresh() { if (refreshPromise) return refreshPromise; refreshPromise = (async () => { try { const stored = await loadStoredState(pool); state = { config: stored?.config ?? defaultSystemDisclosureConfig(), policyVersion: stored?.policyVersion ?? 0, updatedBy: stored?.updatedBy ?? null, updatedAt: stored?.updatedAt ?? null, source: stored ? 'admin-db' : 'default', refreshedAt: Date.now(), lastRefreshError: null, }; } catch (error) { state = { ...state, refreshedAt: Date.now(), lastRefreshError: error instanceof Error ? error.message : String(error), }; logger?.warn?.( '[system-disclosure-policy] refresh failed; keeping last-known-good snapshot:', state.lastRefreshError, ); } finally { refreshPromise = null; } return clone(state); })(); return refreshPromise; } function startAutoRefresh() { if (!autoRefresh || refreshTimer || refreshIntervalMs <= 0) return; refreshTimer = setInterval(() => { void refresh(); }, refreshIntervalMs); refreshTimer.unref?.(); } function stopAutoRefresh() { if (refreshTimer) clearInterval(refreshTimer); refreshTimer = null; } return { async initialize() { await refresh(); startAutoRefresh(); return this.getRuntimeState(); }, close() { stopAutoRefresh(); }, evaluate({ text, userMessage } = {}) { const decision = evaluateSystemDisclosureText( text ?? extractSystemDisclosureMessageText(userMessage), state.config, ); return { ...decision, policyVersion: state.policyVersion, }; }, async refresh() { return refresh(); }, getRuntimeState() { return clone(state); }, async getAdminConfig() { const stored = await loadStoredState(pool); return { config: stored?.config ?? defaultSystemDisclosureConfig(), policyVersion: stored?.policyVersion ?? 0, updatedBy: stored?.updatedBy ?? null, updatedAt: stored?.updatedAt ?? null, source: stored ? 'admin-db' : 'default', }; }, async updateAdminConfig(patch = {}, { updatedBy = null } = {}) { const stored = await loadStoredState(pool); const current = stored?.config ?? defaultSystemDisclosureConfig(); const patchConfig = patch?.config ?? patch; const merged = normalizeSystemDisclosureConfig({ ...current, ...patchConfig, categories: { ...current.categories, ...(patchConfig?.categories ?? {}), }, }, current); const nextVersion = (stored?.policyVersion ?? 0) + 1; const now = Date.now(); await ensureConfigTable(pool); await pool.query( `INSERT INTO ${CONFIG_TABLE} (config_scope, config_json, policy_version, updated_by, updated_at) VALUES (?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE config_json = VALUES(config_json), policy_version = VALUES(policy_version), updated_by = VALUES(updated_by), updated_at = VALUES(updated_at)`, [CONFIG_SCOPE, JSON.stringify(merged), nextVersion, updatedBy, now], ); await refresh(); return this.getAdminConfig(); }, }; } export const systemDisclosurePolicyInternals = { CONFIG_SCOPE, CONFIG_TABLE, POLICY_ID, TECHNICAL_CATEGORY_PATTERNS, IMPLICIT_SELF_TECHNICAL_PATTERNS, };