Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions config/dev.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{
"linkis": {
"token": "[*key_linkis_client_auth_token]",
"requestUrl": "[@GLOBAL_LINKIS_GZ_BDAP_DEV_GATEWAY_URL]"
},
"dms": {
"appid": "[*key_dms_appid]",
"token": "[*key_dms_token]",
"baseUrl": "[*key_dms_base_url]",
"systemUsername": "[*key_dms_system_username]"
}
"linkis": {
"token": "[*key_linkis_client_auth_token]",
"requestUrl": "[@GLOBAL_LINKIS_GZ_BDAP_DEV_GATEWAY_URL]"
},
"dms": {
"appid": "[*key_dms_appid]",
"token": "[*key_dms_token]",
"baseUrl": "[*key_dms_base_url]",
"systemUsername": "[*key_dms_system_username]"
},
"machine_id": "vm-f6298bff015cb6c5",
"authToken": "085a169792c65190c43fa2deaf6cf8ab"
}
1 change: 0 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 44 additions & 3 deletions src/cui-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,11 @@ import { WebPushService } from './services/web-push-service.js';
import { geminiService } from './services/gemini-service.js';
import { ClaudeRouterService } from './services/claude-router-service.js';
import { DmsUserTokenService } from './services/dms-user-token-service.js';
import {
import {
StreamEvent,
CUIError,
PermissionRequest
PermissionRequest,
ResultStreamMessage
} from './types/index.js';
import { createLogger, type Logger } from './services/logger.js';
import { createConversationRoutes } from './routes/conversation.routes.js';
Expand Down Expand Up @@ -282,6 +283,9 @@ export class CUIServer {
this.logger.error('Failed to reload router after config change', error);
}
});

// Backfill historical conversation metadata into the DB (fire-and-forget)
this.runHistoricalMigration();
} catch (error) {
this.logger.error('Failed to initialize server:', error, {
errorType: error instanceof Error ? error.constructor.name : typeof error,
Expand Down Expand Up @@ -603,7 +607,19 @@ export class CUIServer {
});
return;
}


// Persist completion metadata to DB when the result arrives
if (message && message.type === 'result') {
const resultMsg = message as ResultStreamMessage;
const sessionId = resultMsg.session_id;
if (sessionId) {
this.sessionInfoService.saveConversationEnd(
sessionId,
resultMsg.result || ''
);
}
}

// Stream other Claude messages as normal
this.logger.debug('Broadcasting message to StreamManager', {
streamingId,
Expand Down Expand Up @@ -793,4 +809,29 @@ export class CUIServer {
this.logger.error('Router initialization failed, continuing without router', error);
}
}

/**
* Backfill project_path/model/summary into the DB for historical conversations
* that pre-date the DB-only approach. Runs once at startup, non-blocking.
*/
private runHistoricalMigration(): void {
this.historyReader.parseConversationChains()
.then(chains => {
let count = 0;
for (const chain of chains) {
if (!chain.projectPath) continue;
this.sessionInfoService.saveConversationStart(chain.sessionId, chain.projectPath);
if (chain.summary) {
this.sessionInfoService.saveConversationEnd(chain.sessionId, chain.summary);
}
count++;
}
this.logger.info('Historical conversation migration complete', { migrated: count, total: chains.length });
})
.catch(error => {
this.logger.warn('Historical conversation migration failed (non-fatal)', {
error: error instanceof Error ? error.message : String(error)
});
});
}
}
56 changes: 26 additions & 30 deletions src/routes/conversation.routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,18 @@ export function createConversationRoutes(
};

const { streamingId, systemInit } = await processManager.startConversation(conversationConfig);


// Persist initial conversation metadata to DB immediately (enables DB-only list)
try {
sessionInfoService.saveConversationStart(systemInit.session_id, systemInit.cwd);
} catch (error) {
logger.warn('Failed to save conversation start metadata', {
requestId,
sessionId: systemInit.session_id,
error: error instanceof Error ? error.message : String(error)
});
}

// Update original session with continuation session ID if resuming
if (req.body.resumedSessionId) {
try {
Expand Down Expand Up @@ -250,65 +261,51 @@ export function createConversationRoutes(
username,
projectPath
};
const result = await historyReader.listConversations(queryWithProjectPath);

// Update status for each conversation based on active streams

// DB-only query — no file parsing
const result = sessionInfoService.listConversationsFromDB(queryWithProjectPath);

// Overlay live status and toolMetrics for active streams
const conversationsWithStatus = result.conversations.map(conversation => {
const status = statusTracker.getConversationStatus(conversation.sessionId);
const baseConversation = {
...conversation,
status
};

// Add toolMetrics if available
const baseConversation = { ...conversation, status };

const metrics = toolMetricsService.getMetrics(conversation.sessionId);
if (metrics) {
baseConversation.toolMetrics = metrics;
}

// Add streamingId if conversation is ongoing

if (status === 'ongoing') {
const streamingId = statusTracker.getStreamingId(conversation.sessionId);
if (streamingId) {
return { ...baseConversation, streamingId };
}
}

return baseConversation;
});

// Get all active sessions and add optimistic conversations for those not in history
// Include active conversations that haven't been flushed to the DB yet
const existingSessionIds = new Set(conversationsWithStatus.map(c => c.sessionId));
const conversationsNotInHistory = conversationStatusManager.getConversationsNotInHistory(
existingSessionIds,
projectPath
);

// Combine history conversations with active ones not in history
var allConversations = [...conversationsWithStatus, ...conversationsNotInHistory];

// Ensure session info entries exist for all conversations
try {
await sessionInfoService.syncMissingSessions(allConversations.map(c => c.sessionId));
} catch (syncError) {
logger.info('Failed to sync session info', {
requestId,
error: syncError instanceof Error ? syncError.message : String(syncError)
});
}
const allConversations = [...conversationsWithStatus, ...conversationsNotInHistory];

logger.debug('Conversations listed successfully', {
requestId,
conversationCount: allConversations.length,
historyConversations: conversationsWithStatus.length,
dbConversations: conversationsWithStatus.length,
conversationsNotInHistory: conversationsNotInHistory.length,
totalFound: result.total,
activeConversations: allConversations.filter(c => c.status === 'ongoing').length
});

res.json({
conversations: allConversations,
total: allConversations.length // Update total to include conversations not in history
total: allConversations.length
});
} catch (error) {
logger.debug('List conversations failed', {
Expand Down Expand Up @@ -347,7 +344,6 @@ export function createConversationRoutes(
summary: metadata.summary,
projectPath: metadata.projectPath,
metadata: {
totalDuration: metadata.totalDuration,
model: metadata.model
}
};
Expand Down
16 changes: 8 additions & 8 deletions src/services/claude-history-reader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,6 @@ export class ClaudeHistoryReader {
sessionInfo: sessionInfo,
createdAt: chain.createdAt,
updatedAt: chain.updatedAt,
messageCount: chain.messages.length,
totalDuration: chain.totalDuration,
model: chain.model,
status: 'completed' as const, // Default status, will be updated by server
toolMetrics: toolMetrics
Expand Down Expand Up @@ -160,7 +158,6 @@ export class ClaudeHistoryReader {
summary: string;
projectPath: string;
model: string;
totalDuration: number;
} | null> {
try {
const conversationChains = await this.parseAllConversations(username);
Expand All @@ -173,8 +170,7 @@ export class ClaudeHistoryReader {
return {
summary: conversation.summary,
projectPath: conversation.projectPath,
model: conversation.model,
totalDuration: conversation.totalDuration
model: conversation.model
};
} catch (error) {
this.logger.error('Error getting metadata for conversation', error, { sessionId });
Expand Down Expand Up @@ -309,6 +305,13 @@ export class ClaudeHistoryReader {
return conversationChains;
}

/**
* Parse all conversations from JSONL files without DB enrichment (used for startup migration)
*/
async parseConversationChains(username?: string): Promise<ConversationChain[]> {
return this.parseAllConversations(username);
}

/**
* Parse all conversations from all JSONL files with file-level caching and concurrency protection
*/
Expand Down Expand Up @@ -469,8 +472,6 @@ export class ClaudeHistoryReader {
// Determine conversation summary
const summary = this.determineConversationSummary(filteredMessages, summaries);

// Calculate metadata from filtered messages
const totalDuration = filteredMessages.reduce((sum, msg) => sum + (msg.durationMs || 0), 0);
const model = this.extractModel(filteredMessages);

// Get timestamps from filtered messages
Expand All @@ -489,7 +490,6 @@ export class ClaudeHistoryReader {
summary,
createdAt,
updatedAt,
totalDuration,
model
};
} catch (error) {
Expand Down
1 change: 0 additions & 1 deletion src/services/conversation-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ export interface ConversationChain {
summary: string;
createdAt: string;
updatedAt: string;
totalDuration: number;
model: string;
}

Expand Down
3 changes: 0 additions & 3 deletions src/services/conversation-status-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,8 +248,6 @@ export class ConversationStatusManager extends EventEmitter {
},
createdAt: context.timestamp,
updatedAt: context.timestamp,
messageCount: 1, // At least the initial user message
totalDuration: 0, // No duration yet
model: context.model || 'unknown',
status: 'ongoing' as const,
streamingId
Expand Down Expand Up @@ -323,7 +321,6 @@ export class ConversationStatusManager extends EventEmitter {
summary: '', // No summary for active conversation
projectPath: context.workingDirectory,
metadata: {
totalDuration: 0,
model: context.model || 'unknown'
}
};
Expand Down
Loading
Loading