feat(agents): improve session ID extraction and JSONL file handling

- Fix session ID extraction to use correct field name "session_id" instead of "sessionId"
- Add comprehensive database update logging with error handling
- Implement cross-project session file search in get_session_output
- Add new load_agent_session_history command for robust JSONL loading
- Update UI components to prioritize JSONL file loading over fallback methods
- Improve error handling and logging throughout the session management flow
- Fix BufReader imports and alias conflicts in Tauri backend

This enhances the reliability of agent session tracking and output retrieval
by properly handling Claude Code's actual JSON structure and implementing
better fallback mechanisms for session data access.
This commit is contained in:
Mufeed VH
2025-07-04 19:12:47 +05:30
parent 9eeb336a8b
commit 7a2372dcde
6 changed files with 320 additions and 29 deletions

View File

@@ -116,23 +116,81 @@ export function AgentRunOutputViewer({
const loadOutput = async (skipCache = false) => {
if (!run.id) return;
console.log('[AgentRunOutputViewer] Loading output for run:', {
runId: run.id,
status: run.status,
sessionId: run.session_id,
skipCache
});
try {
// Check cache first if not skipping cache
if (!skipCache) {
const cached = getCachedOutput(run.id);
if (cached) {
console.log('[AgentRunOutputViewer] Found cached output');
const cachedJsonlLines = cached.output.split('\n').filter(line => line.trim());
setRawJsonlOutput(cachedJsonlLines);
setMessages(cached.messages);
// If cache is recent (less than 5 seconds old) and session isn't running, use cache only
if (Date.now() - cached.lastUpdated < 5000 && run.status !== 'running') {
console.log('[AgentRunOutputViewer] Using recent cache, skipping refresh');
return;
}
}
}
setLoading(true);
// If we have a session_id, try to load from JSONL file first
if (run.session_id && run.session_id !== '') {
console.log('[AgentRunOutputViewer] Attempting to load from JSONL with session_id:', run.session_id);
try {
const history = await api.loadAgentSessionHistory(run.session_id);
console.log('[AgentRunOutputViewer] Successfully loaded JSONL history:', history.length, 'messages');
// Convert history to messages format
const loadedMessages: ClaudeStreamMessage[] = history.map(entry => ({
...entry,
type: entry.type || "assistant"
}));
setMessages(loadedMessages);
setRawJsonlOutput(history.map(h => JSON.stringify(h)));
// Update cache
setCachedOutput(run.id, {
output: history.map(h => JSON.stringify(h)).join('\n'),
messages: loadedMessages,
lastUpdated: Date.now(),
status: run.status
});
// Set up live event listeners for running sessions
if (run.status === 'running') {
console.log('[AgentRunOutputViewer] Setting up live listeners for running session');
setupLiveEventListeners();
try {
await api.streamSessionOutput(run.id);
} catch (streamError) {
console.warn('[AgentRunOutputViewer] Failed to start streaming, will poll instead:', streamError);
}
}
return;
} catch (err) {
console.warn('[AgentRunOutputViewer] Failed to load from JSONL:', err);
console.warn('[AgentRunOutputViewer] Falling back to regular output method');
}
} else {
console.log('[AgentRunOutputViewer] No session_id available, using fallback method');
}
// Fallback to the original method if JSONL loading fails or no session_id
console.log('[AgentRunOutputViewer] Using getSessionOutput fallback');
const rawOutput = await api.getSessionOutput(run.id);
console.log('[AgentRunOutputViewer] Received raw output:', rawOutput.length, 'characters');
// Parse JSONL output into messages
const jsonlLines = rawOutput.split('\n').filter(line => line.trim());
@@ -144,9 +202,10 @@ export function AgentRunOutputViewer({
const message = JSON.parse(line) as ClaudeStreamMessage;
parsedMessages.push(message);
} catch (err) {
console.error("Failed to parse message:", err, line);
console.error("[AgentRunOutputViewer] Failed to parse message:", err, line);
}
}
console.log('[AgentRunOutputViewer] Parsed', parsedMessages.length, 'messages from output');
setMessages(parsedMessages);
// Update cache
@@ -159,12 +218,13 @@ export function AgentRunOutputViewer({
// Set up live event listeners for running sessions
if (run.status === 'running') {
console.log('[AgentRunOutputViewer] Setting up live listeners for running session (fallback)');
setupLiveEventListeners();
try {
await api.streamSessionOutput(run.id);
} catch (streamError) {
console.warn('Failed to start streaming, will poll instead:', streamError);
console.warn('[AgentRunOutputViewer] Failed to start streaming (fallback), will poll instead:', streamError);
}
}
} catch (error) {

View File

@@ -64,7 +64,25 @@ export const AgentRunView: React.FC<AgentRunViewProps> = ({
const runData = await api.getAgentRunWithRealTimeMetrics(runId);
setRun(runData);
// Parse JSONL output into messages
// If we have a session_id, try to load from JSONL file first
if (runData.session_id && runData.session_id !== '') {
try {
const history = await api.loadAgentSessionHistory(runData.session_id);
// Convert history to messages format
const loadedMessages: ClaudeStreamMessage[] = history.map(entry => ({
...entry,
type: entry.type || "assistant"
}));
setMessages(loadedMessages);
return;
} catch (err) {
console.warn('Failed to load from JSONL, falling back to output field:', err);
}
}
// Fallback: Parse JSONL output from the output field
if (runData.output) {
const parsedMessages: ClaudeStreamMessage[] = [];
const lines = runData.output.split('\n').filter(line => line.trim());

View File

@@ -109,6 +109,47 @@ export function SessionOutputViewer({ session, onClose, className }: SessionOutp
}
setLoading(true);
// If we have a session_id, try to load from JSONL file first
if (session.session_id && session.session_id !== '') {
try {
const history = await api.loadAgentSessionHistory(session.session_id);
// Convert history to messages format using AgentExecution style
const loadedMessages: ClaudeStreamMessage[] = history.map(entry => ({
...entry,
type: entry.type || "assistant"
}));
setMessages(loadedMessages);
setRawJsonlOutput(history.map(h => JSON.stringify(h)));
// Update cache
setCachedOutput(session.id, {
output: history.map(h => JSON.stringify(h)).join('\n'),
messages: loadedMessages,
lastUpdated: Date.now(),
status: session.status
});
// Set up live event listeners for running sessions
if (session.status === 'running') {
setupLiveEventListeners();
try {
await api.streamSessionOutput(session.id);
} catch (streamError) {
console.warn('Failed to start streaming, will poll instead:', streamError);
}
}
return;
} catch (err) {
console.warn('Failed to load from JSONL, falling back to regular output:', err);
}
}
// Fallback to the original method if JSONL loading fails or no session_id
const rawOutput = await api.getSessionOutput(session.id);
// Parse JSONL output into messages using AgentExecution style

View File

@@ -925,6 +925,21 @@ export const api = {
return invoke("load_session_history", { sessionId, projectId });
},
/**
* Loads the JSONL history for a specific agent session
* Similar to loadSessionHistory but searches across all project directories
* @param sessionId - The session ID (UUID)
* @returns Promise resolving to array of session messages
*/
async loadAgentSessionHistory(sessionId: string): Promise<any[]> {
try {
return await invoke<any[]>('load_agent_session_history', { sessionId });
} catch (error) {
console.error("Failed to load agent session history:", error);
throw error;
}
},
/**
* Executes a new interactive Claude Code session with streaming output
*/