Skip to content

Commit 52961d2

Browse files
committed
fix(history): 修复纯路径输入会话被误判为仅助手输出
Claude 历史索引依赖 preview 判断会话是否存在有效用户输入。 当用户首条消息只有图片或文件路径时,cleanClaudeUserPrompt 会把路径 作为噪声过滤掉,导致 preview 为空,索引阶段把该会话误判为仅助手输出并忽略。 本次调整: - 在 Claude 用户消息清洗阶段增加路径型输入回退,提取文件名作为预览 - 对常见图片扩展名生成“图片:<文件名>”,其他路径生成“文件:<文件名>” - 补充纯路径输入场景测试,覆盖 summaryOnly 历史索引链路 产品效果: - 上传图片或仅引用文件路径的 Claude 会话会正常出现在历史列表 - 避免这类会话在项目扫描和历史候选中被误判丢失 Signed-off-by: Lulu <58587930+lulu-sk@users.noreply.github.com>
1 parent 0fd8f60 commit 52961d2

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

electron/agentSessions/claude/parser.test.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,5 +99,37 @@ describe("parseClaudeSessionFile(本地命令 transcript 分类)", () => {
9999
expect(details.preview).toBe("真实首条:OK");
100100
expect(details.messages.length).toBe(0);
101101
});
102+
103+
it("纯路径输入也应回退为文件名预览,避免会话被误判为仅助手输出", async () => {
104+
const cwd = "G:\\\\Projects\\\\CodexFlow";
105+
const sessionId = "61d0bdd2-f31f-4c77-a148-bf0470f4fffa";
106+
const lines = [
107+
{
108+
cwd,
109+
sessionId,
110+
type: "user",
111+
message: {
112+
role: "user",
113+
content: "`C:\\\\Users\\\\52628\\\\AppData\\\\Roaming\\\\codexflow\\\\assets\\\\CodexFlow\\\\image-20260317-115705-67p2.png`",
114+
},
115+
},
116+
{
117+
cwd,
118+
sessionId,
119+
type: "assistant",
120+
message: {
121+
role: "assistant",
122+
content: [{ type: "text", text: "你好!我已经看到了这张图片。" }],
123+
},
124+
},
125+
];
126+
127+
const fp = await writeTempJsonl(lines, `${sessionId}.jsonl`);
128+
const stat = await fs.promises.stat(fp);
129+
const details = await parseClaudeSessionFile(fp, stat, { summaryOnly: true, maxLines: 2000 });
130+
131+
expect(details.preview).toBe("图片:image-20260317-115705-67p2.png");
132+
expect(details.title).toBe("图片:image-20260317-115705-67p2.png");
133+
});
102134
});
103135

electron/agentSessions/claude/parser.ts

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,7 @@ function cleanClaudeUserPrompt(text?: string): string {
379379
if (!raw) return "";
380380
const lines = raw.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
381381
if (lines.length === 0) return "";
382+
const pathFallback = extractClaudePathPreviewFallback(lines);
382383
const hasCaveat = lines.some((l) => l.toLowerCase().startsWith("caveat:"));
383384
if (!hasCaveat) {
384385
for (const l of lines) {
@@ -388,7 +389,7 @@ function cleanClaudeUserPrompt(text?: string): string {
388389
if (!stripped) continue;
389390
return collapseSpaces(stripped);
390391
}
391-
return "";
392+
return pathFallback;
392393
}
393394

394395
// 取最后一个“看起来像自然语言”的行,跳过 <local-command-*> 片段
@@ -410,12 +411,52 @@ function cleanClaudeUserPrompt(text?: string): string {
410411
if (stripped.toLowerCase().includes("local-command-stdout")) continue;
411412
return collapseSpaces(stripped);
412413
}
413-
return "";
414+
return pathFallback;
414415
} catch {
415416
return String(text || "").trim();
416417
}
417418
}
418419

420+
/**
421+
* 当用户输入只有路径/附件引用时,提取一个简短可读的历史预览,避免该会话被误判为“仅助手输出”。
422+
*/
423+
function extractClaudePathPreviewFallback(lines: string[]): string {
424+
try {
425+
for (const rawLine of lines) {
426+
const line = unwrapClaudePreviewLine(rawLine);
427+
if (!line) continue;
428+
if (!isWinOrWslPathLineForPreview(line)) continue;
429+
const normalized = line.replace(/\\/g, "/");
430+
const base = normalized.split("/").filter(Boolean).pop() || line;
431+
const name = collapseSpaces(base);
432+
if (!name) continue;
433+
if (/\.(png|jpe?g|gif|webp|bmp|svg|ico)$/i.test(name)) return `图片:${name}`;
434+
return `文件:${name}`;
435+
}
436+
return "";
437+
} catch {
438+
return "";
439+
}
440+
}
441+
442+
/**
443+
* 去掉预览候选行首尾的成对包裹符,兼容 ``path`` / "path" / 'path' 这类纯路径输入。
444+
*/
445+
function unwrapClaudePreviewLine(value: string): string {
446+
try {
447+
let text = String(value || "").trim();
448+
const stripPairs = (ch: string) => {
449+
if (text.startsWith(ch) && text.endsWith(ch) && text.length >= 2) text = text.slice(1, -1).trim();
450+
};
451+
stripPairs("`");
452+
stripPairs("\"");
453+
stripPairs("'");
454+
return text;
455+
} catch {
456+
return String(value || "").trim();
457+
}
458+
}
459+
419460
/**
420461
* 将 JSON stringify 降级为可展示文本(失败时回退为 String)。
421462
*/

0 commit comments

Comments
 (0)