mirror of
https://github.com/siteboon/claudecodeui.git
synced 2026-06-11 00:06:00 +08:00
fix(session-synchronizer): improve session name extraction for Claude and Codex
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
|
||||||
import { sessionsDb } from '@/modules/database/index.js';
|
import { sessionsDb } from '@/modules/database/index.js';
|
||||||
import {
|
import {
|
||||||
@@ -91,7 +92,7 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
nameMap: Map<string, string>
|
nameMap: Map<string, string>
|
||||||
): Promise<ParsedSession | null> {
|
): Promise<ParsedSession | null> {
|
||||||
return extractFirstValidJsonlData(filePath, (rawData) => {
|
const parsed = await extractFirstValidJsonlData(filePath, (rawData) => {
|
||||||
const data = rawData as Record<string, unknown>;
|
const data = rawData as Record<string, unknown>;
|
||||||
const sessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined;
|
const sessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined;
|
||||||
const projectPath = typeof data.cwd === 'string' ? data.cwd : undefined;
|
const projectPath = typeof data.cwd === 'string' ? data.cwd : undefined;
|
||||||
@@ -103,8 +104,68 @@ export class ClaudeSessionSynchronizer implements IProviderSessionSynchronizer {
|
|||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
projectPath,
|
projectPath,
|
||||||
sessionName: normalizeSessionName(nameMap.get(sessionId), 'Untitled Claude Session'),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSession = sessionsDb.getSessionById(parsed.sessionId);
|
||||||
|
const existingSessionName = existingSession?.custom_name;
|
||||||
|
if (existingSessionName && existingSessionName !== 'Untitled Claude Session') {
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
sessionName: normalizeSessionName(existingSessionName, 'Untitled Claude Session'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessionName = nameMap.get(parsed.sessionId);
|
||||||
|
if (!sessionName) {
|
||||||
|
sessionName = await this.extractSessionAiTitleFromEnd(filePath, parsed.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
sessionName: normalizeSessionName(sessionName, 'Untitled Claude Session'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async extractSessionAiTitleFromEnd(
|
||||||
|
filePath: string,
|
||||||
|
sessionId: string
|
||||||
|
): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const content = await readFile(filePath, 'utf8');
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
|
||||||
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
||||||
|
const line = lines[index]?.trim();
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(line);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = parsed as Record<string, unknown>;
|
||||||
|
const eventType = typeof data.type === 'string' ? data.type : undefined;
|
||||||
|
const eventSessionId = typeof data.sessionId === 'string' ? data.sessionId : undefined;
|
||||||
|
const aiTitle = typeof data.aiTitle === 'string' ? data.aiTitle : undefined;
|
||||||
|
const lastPrompt = typeof data.lastPrompt === 'string' ? data.lastPrompt : undefined;
|
||||||
|
|
||||||
|
if ((eventType === 'ai-title' && eventSessionId === sessionId && aiTitle?.trim()) || (eventType === 'last-prompt' && eventSessionId === sessionId && lastPrompt?.trim())) {
|
||||||
|
return aiTitle || lastPrompt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore missing/unreadable files so sync can continue.
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { readFile } from 'node:fs/promises';
|
||||||
|
|
||||||
import { sessionsDb } from '@/modules/database/index.js';
|
import { sessionsDb } from '@/modules/database/index.js';
|
||||||
import {
|
import {
|
||||||
@@ -99,7 +100,7 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
nameMap: Map<string, string>
|
nameMap: Map<string, string>
|
||||||
): Promise<ParsedSession | null> {
|
): Promise<ParsedSession | null> {
|
||||||
return extractFirstValidJsonlData(filePath, (rawData) => {
|
const parsed = await extractFirstValidJsonlData(filePath, (rawData) => {
|
||||||
const data = rawData as Record<string, unknown>;
|
const data = rawData as Record<string, unknown>;
|
||||||
const payload = data.payload as Record<string, unknown> | undefined;
|
const payload = data.payload as Record<string, unknown> | undefined;
|
||||||
const sessionId = typeof payload?.id === 'string' ? payload.id : undefined;
|
const sessionId = typeof payload?.id === 'string' ? payload.id : undefined;
|
||||||
@@ -112,8 +113,67 @@ export class CodexSessionSynchronizer implements IProviderSessionSynchronizer {
|
|||||||
return {
|
return {
|
||||||
sessionId,
|
sessionId,
|
||||||
projectPath,
|
projectPath,
|
||||||
sessionName: normalizeSessionName(nameMap.get(sessionId), 'Untitled Codex Session'),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!parsed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const existingSession = sessionsDb.getSessionById(parsed.sessionId);
|
||||||
|
const existingSessionName = existingSession?.custom_name;
|
||||||
|
if (existingSessionName && existingSessionName !== 'Untitled Codex Session') {
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
sessionName: normalizeSessionName(existingSessionName, 'Untitled Codex Session'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let sessionName = nameMap.get(parsed.sessionId);
|
||||||
|
if (!sessionName) {
|
||||||
|
sessionName = await this.extractLastAgentMessageFromEnd(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...parsed,
|
||||||
|
sessionName: normalizeSessionName(sessionName, 'Untitled Codex Session'),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async extractLastAgentMessageFromEnd(filePath: string): Promise<string | undefined> {
|
||||||
|
try {
|
||||||
|
const content = await readFile(filePath, 'utf8');
|
||||||
|
const lines = content.split(/\r?\n/);
|
||||||
|
|
||||||
|
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
||||||
|
const line = lines[index]?.trim();
|
||||||
|
if (!line) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: unknown;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(line);
|
||||||
|
} catch {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = parsed as Record<string, unknown>;
|
||||||
|
const eventType = typeof data.type === 'string' ? data.type : undefined;
|
||||||
|
const payload = data.payload as Record<string, unknown> | undefined;
|
||||||
|
const payloadType = typeof payload?.type === 'string' ? payload.type : undefined;
|
||||||
|
const lastAgentMessage = typeof payload?.last_agent_message === 'string'
|
||||||
|
? payload.last_agent_message
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (eventType === 'event_msg' && payloadType === 'task_complete' && lastAgentMessage?.trim()) {
|
||||||
|
return lastAgentMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore missing/unreadable files so sync can continue.
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user