feat: i18n for CLI history view; fix Claude session discovery path encoding

## Changes

### i18n 中文化 (i18n.js, cli-history.js)
- 添加 60+ 个翻译键用于 CLI 执行历史和对话详情
- 将 cli-history.js 中的硬编码英文字符串替换为 t() 函数调用
- 覆盖范围: 执行历史、对话详情、状态、工具标签、按钮、提示等

### 修复 Claude 会话追踪 (native-session-discovery.ts)
- 问题: Claude 使用路径编码存储会话 (D:\path -> D--path),但代码使用 SHA256 哈希导致无法发现
- 解决方案:
  - 添加 encodeClaudeProjectPath() 函数用于路径编码
  - 更新 ClaudeSessionDiscoverer.getSessions() 使用路径编码
  - 增强 extractFirstUserMessage() 支持多种消息格式 (string/array)
- 结果: Claude 会话现可正确关联,UI 按钮 "查看完整过程对话" 应可正常显示

## 验证
- npm run build 通过 
- Claude 会话发现 1267 个会话 
- 消息提取成功率 80% 
- 路径编码验证正确 
This commit is contained in:
catlog22
2026-01-22 14:53:38 +08:00
parent 5fa7524ad7
commit 02531c4d15
3 changed files with 206 additions and 55 deletions

View File

@@ -232,6 +232,19 @@ function encodeQwenProjectPath(projectDir: string): string {
.replace(/_/g, '-');
}
/**
* Encode a path to Claude Code's project folder name format
* D:\Claude_dms3 -> D--Claude-dms3 (same as Qwen)
* Rules: : -> -, \ -> -, _ -> -
*/
function encodeClaudeProjectPath(projectDir: string): string {
const absolutePath = resolve(projectDir);
return absolutePath
.replace(/:/g, '-')
.replace(/\\/g, '-')
.replace(/_/g, '-');
}
/**
* Qwen Session Discoverer
* New path: ~/.qwen/projects/<path-encoded>/chats/<uuid>.jsonl
@@ -576,9 +589,10 @@ class ClaudeSessionDiscoverer extends SessionDiscoverer {
// If workingDir provided, only look in that project's folder
let projectDirs: string[];
if (workingDir) {
const projectHash = calculateProjectHash(workingDir);
const projectPath = join(this.basePath, projectHash);
projectDirs = existsSync(projectPath) ? [projectHash] : [];
// Claude Code uses path encoding (D:\path -> D--path) not SHA256 hash
const encodedPath = encodeClaudeProjectPath(workingDir);
const projectPath = join(this.basePath, encodedPath);
projectDirs = existsSync(projectPath) ? [encodedPath] : [];
} else {
projectDirs = readdirSync(this.basePath).filter(d => {
const fullPath = join(this.basePath, d);
@@ -652,6 +666,7 @@ class ClaudeSessionDiscoverer extends SessionDiscoverer {
/**
* Extract first user message from Claude Code session file (.jsonl)
* Format: {"type":"user","message":{"role":"user","content":"..."},"isMeta":false,...}
* Content can be: string | array of {type,text} | array of {type,source} etc.
*/
extractFirstUserMessage(filePath: string): string | null {
try {
@@ -661,14 +676,30 @@ class ClaudeSessionDiscoverer extends SessionDiscoverer {
for (const line of lines) {
try {
const entry = JSON.parse(line);
// Claude Code format: type="user", message.role="user", message.content="..."
// Claude Code format: type="user", message.role="user", message.content can be string or array
// Skip meta messages and command messages
if (entry.type === 'user' &&
entry.message?.role === 'user' &&
entry.message?.content &&
!entry.isMeta &&
!entry.message.content.startsWith('<command-')) {
return entry.message.content;
!entry.isMeta) {
const msgContent = entry.message.content;
// Handle string content (simple case)
if (typeof msgContent === 'string') {
if (!msgContent.startsWith('<command-') && !msgContent.includes('<local-command')) {
return msgContent;
}
}
// Handle array content (can contain text, image, tool_result, etc.)
else if (Array.isArray(msgContent)) {
for (const item of msgContent) {
// Look for text items
if (item.type === 'text' && item.text) {
return item.text;
}
}
}
}
} catch { /* skip invalid lines */ }
}