fix: 修复 Cursor 身份泄漏 — 模型声称只有 read_file/read_dir 工具 (#68)

问题:模型回复"在当前环境中我只有读取 Cursor 文档的工具
(read_file / read_dir),无法访问你的本地文件系统",
暴露了 Cursor 文档助手身份。

修复:
1. handleDirectTextStream 的 warmupChars 从 96 → 300,
   与工具模式对齐,确保拒绝检测窗口覆盖完整的中文拒绝句式
2. constants.ts 新增 9 条中文拒绝检测规则,覆盖
   "只有读取 Cursor/文档的工具"、"无法访问本地文件"、
   "无法执行命令"、"需要在 Claude Code CLI 环境" 等新措辞
3. sanitizeResponse 新增 5 条清洗规则作为最后防线
This commit is contained in:
chinadoiphin
2026-03-19 22:12:14 +08:00
parent f6ad4292f8
commit f17353e05b
2 changed files with 18 additions and 0 deletions

View File

@@ -119,6 +119,16 @@ export const REFUSAL_PATTERNS: RegExp[] = [
/只能用.*?read_file/i,
/无法调用.*?工具/,
/(?:仅限于|仅用于).*?(?:查阅|浏览).*?(?:文档|docs)/,
// ── 中文: 工具可用性声明 (2026-03 新增) ──
/只有.*?读取.*?Cursor.*?工具/,
/只有.*?读取.*?文档的工具/,
/无法访问.*?本地文件/,
/无法.*?执行命令/,
/需要在.*?Claude\s*Code/i,
/需要.*?CLI.*?环境/i,
/当前环境.*?只有.*?工具/,
/只有.*?read_file.*?read_dir/i,
/只有.*?read_dir.*?read_file/i,
// ── 中文: Cursor 中文界面拒绝措辞 (2026-03 批次) ──
/只能回答.*(?:Cursor|编辑器).*(?:相关|有关)/,

View File

@@ -250,6 +250,12 @@ export function sanitizeResponse(text: string): string {
result = result.replace(/\*\*`?read_dir`?\*\*[^\n]*\n(?:[^\n]*\n){0,3}/gi, '');
result = result.replace(/\d+\.\s*\*\*`?read_(?:file|dir)`?\*\*[^\n]*/gi, '');
result = result.replace(/[⚠注意].*?(?:不是|并非|无法).*?(?:本地文件|代码库|执行代码)[^。\n]*[。]?\s*/g, '');
// 中文: "只有读取 Cursor 文档的工具" / "无法访问本地文件系统" 等新措辞清洗
result = result.replace(/[^。\n]*只有.*?读取.*?(?:Cursor|文档).*?工具[^。\n]*[。]?\s*/g, '');
result = result.replace(/[^。\n]*无法访问.*?本地文件[^。\n]*[。]?\s*/g, '');
result = result.replace(/[^。\n]*无法.*?执行命令[^。\n]*[。]?\s*/g, '');
result = result.replace(/[^。\n]*需要在.*?Claude\s*Code[^。\n]*[。]?\s*/gi, '');
result = result.replace(/[^。\n]*当前环境.*?只有.*?工具[^。\n]*[。]?\s*/g, '');
// === Hallucination about accidentally calling Cursor internal tools ===
// "I accidentally called the Cursor documentation read_dir tool." -> remove entire sentence
result = result.replace(/[^\n.!?]*(?:accidentally|mistakenly|keep|sorry|apologies|apologize)[^\n.!?]*(?:called|calling|used|using)[^\n.!?]*Cursor[^\n.!?]*tool[^\n.!?]*[.!?]\s*/gi, '');
@@ -786,6 +792,7 @@ async function handleDirectTextStream(
let finalVisibleText = '';
let finalThinkingContent = '';
let streamer = createIncrementalTextStreamer({
warmupChars: 300, // ★ 与工具模式对齐:前 300 chars 不释放,确保拒绝检测完成后再流
transform: sanitizeResponse,
isBlockedPrefix: (text) => isRefusal(text.substring(0, 300)),
});
@@ -802,6 +809,7 @@ async function handleDirectTextStream(
let leadingResolved = false;
let thinkingContent = '';
const attemptStreamer = createIncrementalTextStreamer({
warmupChars: 300, // ★ 与工具模式对齐
transform: sanitizeResponse,
isBlockedPrefix: (text) => isRefusal(text.substring(0, 300)),
});