feat: Refactor Settings and Viewer routes to extend BaseRouteHandler for improved error handling
- Introduced BaseRouteHandler class to centralize error handling and response management. - Updated SettingsRoutes to use wrapHandler for automatic error logging and response. - Refactored ViewerRoutes to extend BaseRouteHandler and utilize wrapHandler for health check and UI serving. - Enhanced error handling in SettingsRoutes and ViewerRoutes for better maintainability and readability.
This commit is contained in:
@@ -14,15 +14,18 @@ import { DatabaseManager } from '../../DatabaseManager.js';
|
||||
import { SDKAgent } from '../../SDKAgent.js';
|
||||
import { SSEBroadcaster } from '../../SSEBroadcaster.js';
|
||||
import type { WorkerService } from '../../../worker-service.js';
|
||||
import { BaseRouteHandler } from '../BaseRouteHandler.js';
|
||||
|
||||
export class SessionRoutes {
|
||||
export class SessionRoutes extends BaseRouteHandler {
|
||||
constructor(
|
||||
private sessionManager: SessionManager,
|
||||
private dbManager: DatabaseManager,
|
||||
private sdkAgent: SDKAgent,
|
||||
private sseBroadcaster: SSEBroadcaster,
|
||||
private workerService: WorkerService
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures SDK agent generator is running for a session
|
||||
@@ -66,322 +69,293 @@ export class SessionRoutes {
|
||||
/**
|
||||
* Initialize a new session
|
||||
*/
|
||||
private handleSessionInit(req: Request, res: Response): void {
|
||||
try {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
const { userPrompt, promptNumber } = req.body;
|
||||
const session = this.sessionManager.initializeSession(sessionDbId, userPrompt, promptNumber);
|
||||
private handleSessionInit = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||
if (sessionDbId === null) return;
|
||||
|
||||
// Get the latest user_prompt for this session to sync to Chroma
|
||||
const latestPrompt = this.dbManager.getSessionStore().getLatestUserPrompt(session.claudeSessionId);
|
||||
const { userPrompt, promptNumber } = req.body;
|
||||
const session = this.sessionManager.initializeSession(sessionDbId, userPrompt, promptNumber);
|
||||
|
||||
// Broadcast new prompt to SSE clients (for web UI)
|
||||
if (latestPrompt) {
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'new_prompt',
|
||||
prompt: {
|
||||
id: latestPrompt.id,
|
||||
claude_session_id: latestPrompt.claude_session_id,
|
||||
project: latestPrompt.project,
|
||||
prompt_number: latestPrompt.prompt_number,
|
||||
prompt_text: latestPrompt.prompt_text,
|
||||
created_at_epoch: latestPrompt.created_at_epoch
|
||||
}
|
||||
});
|
||||
// Get the latest user_prompt for this session to sync to Chroma
|
||||
const latestPrompt = this.dbManager.getSessionStore().getLatestUserPrompt(session.claudeSessionId);
|
||||
|
||||
// Start activity indicator immediately when prompt arrives (work is about to begin)
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'processing_status',
|
||||
isProcessing: true
|
||||
});
|
||||
|
||||
// Sync user prompt to Chroma with error logging
|
||||
const chromaStart = Date.now();
|
||||
const promptText = latestPrompt.prompt_text;
|
||||
this.dbManager.getChromaSync().syncUserPrompt(
|
||||
latestPrompt.id,
|
||||
latestPrompt.sdk_session_id,
|
||||
latestPrompt.project,
|
||||
promptText,
|
||||
latestPrompt.prompt_number,
|
||||
latestPrompt.created_at_epoch
|
||||
).then(() => {
|
||||
const chromaDuration = Date.now() - chromaStart;
|
||||
const truncatedPrompt = promptText.length > 60
|
||||
? promptText.substring(0, 60) + '...'
|
||||
: promptText;
|
||||
logger.debug('CHROMA', 'User prompt synced', {
|
||||
promptId: latestPrompt.id,
|
||||
duration: `${chromaDuration}ms`,
|
||||
prompt: truncatedPrompt
|
||||
});
|
||||
}).catch(err => {
|
||||
logger.error('CHROMA', 'Failed to sync user_prompt', {
|
||||
promptId: latestPrompt.id,
|
||||
sessionId: sessionDbId
|
||||
}, err);
|
||||
});
|
||||
}
|
||||
|
||||
// Broadcast processing status (based on queue depth)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Start SDK agent in background (pass worker ref for spinner control)
|
||||
logger.info('SESSION', 'Generator starting', {
|
||||
sessionId: sessionDbId,
|
||||
project: session.project,
|
||||
promptNum: session.lastPromptNumber
|
||||
});
|
||||
|
||||
session.generatorPromise = this.sdkAgent.startSession(session, this.workerService)
|
||||
.catch(err => {
|
||||
logger.failure('SDK', 'SDK agent error', { sessionId: sessionDbId }, err);
|
||||
})
|
||||
.finally(() => {
|
||||
// Clear generator reference when completed
|
||||
logger.info('SESSION', `Generator finished`, { sessionId: sessionDbId });
|
||||
session.generatorPromise = null;
|
||||
// Broadcast status change (generator finished, may stop spinner)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
});
|
||||
|
||||
// Broadcast SSE event
|
||||
// Broadcast new prompt to SSE clients (for web UI)
|
||||
if (latestPrompt) {
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_started',
|
||||
sessionDbId,
|
||||
project: session.project
|
||||
type: 'new_prompt',
|
||||
prompt: {
|
||||
id: latestPrompt.id,
|
||||
claude_session_id: latestPrompt.claude_session_id,
|
||||
project: latestPrompt.project,
|
||||
prompt_number: latestPrompt.prompt_number,
|
||||
prompt_text: latestPrompt.prompt_text,
|
||||
created_at_epoch: latestPrompt.created_at_epoch
|
||||
}
|
||||
});
|
||||
|
||||
res.json({ status: 'initialized', sessionDbId, port: getWorkerPort() });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Session init failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
// Start activity indicator immediately when prompt arrives (work is about to begin)
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'processing_status',
|
||||
isProcessing: true
|
||||
});
|
||||
|
||||
// Sync user prompt to Chroma with error logging
|
||||
const chromaStart = Date.now();
|
||||
const promptText = latestPrompt.prompt_text;
|
||||
this.dbManager.getChromaSync().syncUserPrompt(
|
||||
latestPrompt.id,
|
||||
latestPrompt.sdk_session_id,
|
||||
latestPrompt.project,
|
||||
promptText,
|
||||
latestPrompt.prompt_number,
|
||||
latestPrompt.created_at_epoch
|
||||
).then(() => {
|
||||
const chromaDuration = Date.now() - chromaStart;
|
||||
const truncatedPrompt = promptText.length > 60
|
||||
? promptText.substring(0, 60) + '...'
|
||||
: promptText;
|
||||
logger.debug('CHROMA', 'User prompt synced', {
|
||||
promptId: latestPrompt.id,
|
||||
duration: `${chromaDuration}ms`,
|
||||
prompt: truncatedPrompt
|
||||
});
|
||||
}).catch(err => {
|
||||
logger.error('CHROMA', 'Failed to sync user_prompt', {
|
||||
promptId: latestPrompt.id,
|
||||
sessionId: sessionDbId
|
||||
}, err);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast processing status (based on queue depth)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Start SDK agent in background (pass worker ref for spinner control)
|
||||
logger.info('SESSION', 'Generator starting', {
|
||||
sessionId: sessionDbId,
|
||||
project: session.project,
|
||||
promptNum: session.lastPromptNumber
|
||||
});
|
||||
|
||||
session.generatorPromise = this.sdkAgent.startSession(session, this.workerService)
|
||||
.catch(err => {
|
||||
logger.failure('SDK', 'SDK agent error', { sessionId: sessionDbId }, err);
|
||||
})
|
||||
.finally(() => {
|
||||
// Clear generator reference when completed
|
||||
logger.info('SESSION', `Generator finished`, { sessionId: sessionDbId });
|
||||
session.generatorPromise = null;
|
||||
// Broadcast status change (generator finished, may stop spinner)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
});
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_started',
|
||||
sessionDbId,
|
||||
project: session.project
|
||||
});
|
||||
|
||||
res.json({ status: 'initialized', sessionDbId, port: getWorkerPort() });
|
||||
});
|
||||
|
||||
/**
|
||||
* Queue observations for processing
|
||||
* CRITICAL: Ensures SDK agent is running to process the queue (ALWAYS SAVE EVERYTHING)
|
||||
*/
|
||||
private handleObservations(req: Request, res: Response): void {
|
||||
try {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
const { tool_name, tool_input, tool_response, prompt_number, cwd } = req.body;
|
||||
private handleObservations = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||
if (sessionDbId === null) return;
|
||||
|
||||
this.sessionManager.queueObservation(sessionDbId, {
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_response,
|
||||
prompt_number,
|
||||
cwd
|
||||
});
|
||||
const { tool_name, tool_input, tool_response, prompt_number, cwd } = req.body;
|
||||
|
||||
// CRITICAL: Ensure SDK agent is running to consume the queue
|
||||
this.ensureGeneratorRunning(sessionDbId, 'observation');
|
||||
this.sessionManager.queueObservation(sessionDbId, {
|
||||
tool_name,
|
||||
tool_input,
|
||||
tool_response,
|
||||
prompt_number,
|
||||
cwd
|
||||
});
|
||||
|
||||
// Broadcast activity status (queue depth changed)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
// CRITICAL: Ensure SDK agent is running to consume the queue
|
||||
this.ensureGeneratorRunning(sessionDbId, 'observation');
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'observation_queued',
|
||||
sessionDbId
|
||||
});
|
||||
// Broadcast activity status (queue depth changed)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Observation queuing failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'observation_queued',
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Queue summarize request
|
||||
* CRITICAL: Ensures SDK agent is running to process the queue (ALWAYS SAVE EVERYTHING)
|
||||
*/
|
||||
private handleSummarize(req: Request, res: Response): void {
|
||||
try {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
const { last_user_message, last_assistant_message } = req.body;
|
||||
private handleSummarize = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||
if (sessionDbId === null) return;
|
||||
|
||||
this.sessionManager.queueSummarize(sessionDbId, last_user_message, last_assistant_message);
|
||||
const { last_user_message, last_assistant_message } = req.body;
|
||||
|
||||
// CRITICAL: Ensure SDK agent is running to consume the queue
|
||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||
this.sessionManager.queueSummarize(sessionDbId, last_user_message, last_assistant_message);
|
||||
|
||||
// Broadcast activity status (queue depth changed)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
// CRITICAL: Ensure SDK agent is running to consume the queue
|
||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Summarize queuing failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
// Broadcast activity status (queue depth changed)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Get session status
|
||||
*/
|
||||
private handleSessionStatus(req: Request, res: Response): void {
|
||||
try {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
const session = this.sessionManager.getSession(sessionDbId);
|
||||
private handleSessionStatus = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||
if (sessionDbId === null) return;
|
||||
|
||||
if (!session) {
|
||||
res.json({ status: 'not_found' });
|
||||
return;
|
||||
}
|
||||
const session = this.sessionManager.getSession(sessionDbId);
|
||||
|
||||
res.json({
|
||||
status: 'active',
|
||||
sessionDbId,
|
||||
project: session.project,
|
||||
queueLength: session.pendingMessages.length,
|
||||
uptime: Date.now() - session.startTime
|
||||
});
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Session status failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
if (!session) {
|
||||
res.json({ status: 'not_found' });
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
status: 'active',
|
||||
sessionDbId,
|
||||
project: session.project,
|
||||
queueLength: session.pendingMessages.length,
|
||||
uptime: Date.now() - session.startTime
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete a session
|
||||
*/
|
||||
private async handleSessionDelete(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
await this.sessionManager.deleteSession(sessionDbId);
|
||||
private handleSessionDelete = this.wrapHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||
if (sessionDbId === null) return;
|
||||
|
||||
// Mark session complete in database
|
||||
this.dbManager.markSessionComplete(sessionDbId);
|
||||
await this.sessionManager.deleteSession(sessionDbId);
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_completed',
|
||||
sessionDbId
|
||||
});
|
||||
// Mark session complete in database
|
||||
this.dbManager.markSessionComplete(sessionDbId);
|
||||
|
||||
res.json({ status: 'deleted' });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Session delete failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
}
|
||||
}
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_completed',
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ status: 'deleted' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Complete a session (backward compatibility for cleanup-hook)
|
||||
* cleanup-hook expects POST /sessions/:sessionDbId/complete instead of DELETE
|
||||
*/
|
||||
private async handleSessionComplete(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const sessionDbId = parseInt(req.params.sessionDbId, 10);
|
||||
if (isNaN(sessionDbId)) {
|
||||
res.status(400).json({ success: false, error: 'Invalid session ID' });
|
||||
return;
|
||||
}
|
||||
private handleSessionComplete = this.wrapHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const sessionDbId = this.parseIntParam(req, res, 'sessionDbId');
|
||||
if (sessionDbId === null) return;
|
||||
|
||||
await this.sessionManager.deleteSession(sessionDbId);
|
||||
await this.sessionManager.deleteSession(sessionDbId);
|
||||
|
||||
// Mark session complete in database
|
||||
this.dbManager.markSessionComplete(sessionDbId);
|
||||
// Mark session complete in database
|
||||
this.dbManager.markSessionComplete(sessionDbId);
|
||||
|
||||
// Broadcast processing status (based on queue depth)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
// Broadcast processing status (based on queue depth)
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_completed',
|
||||
timestamp: Date.now(),
|
||||
sessionDbId
|
||||
});
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_completed',
|
||||
timestamp: Date.now(),
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Session complete failed', {}, error as Error);
|
||||
res.status(500).json({ success: false, error: String(error) });
|
||||
}
|
||||
}
|
||||
res.json({ success: true });
|
||||
});
|
||||
|
||||
/**
|
||||
* Queue observations by claudeSessionId (post-tool-use-hook uses this)
|
||||
* POST /api/sessions/observations
|
||||
* Body: { claudeSessionId, tool_name, tool_input, tool_response, cwd }
|
||||
*/
|
||||
private handleObservationsByClaudeId(req: Request, res: Response): void {
|
||||
try {
|
||||
const { claudeSessionId, tool_name, tool_input, tool_response, cwd } = req.body;
|
||||
private handleObservationsByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { claudeSessionId, tool_name, tool_input, tool_response, cwd } = req.body;
|
||||
|
||||
if (!claudeSessionId) {
|
||||
res.status(400).json({ error: 'Missing claudeSessionId' });
|
||||
return;
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Get or create session
|
||||
const sessionDbId = store.createSDKSession(claudeSessionId, '', '');
|
||||
const promptNumber = store.getPromptCounter(sessionDbId);
|
||||
|
||||
// Privacy check: skip if user prompt was entirely private
|
||||
const userPrompt = store.getUserPrompt(claudeSessionId, promptNumber);
|
||||
if (!userPrompt || userPrompt.trim() === '') {
|
||||
logger.debug('HOOK', 'Skipping observation - user prompt was entirely private', {
|
||||
sessionId: sessionDbId,
|
||||
promptNumber,
|
||||
tool_name
|
||||
});
|
||||
res.json({ status: 'skipped', reason: 'private' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip memory tags from tool_input and tool_response
|
||||
let cleanedToolInput = '{}';
|
||||
let cleanedToolResponse = '{}';
|
||||
|
||||
try {
|
||||
cleanedToolInput = tool_input !== undefined
|
||||
? stripMemoryTagsFromJson(JSON.stringify(tool_input))
|
||||
: '{}';
|
||||
} catch (error) {
|
||||
cleanedToolInput = '{"error": "Failed to serialize tool_input"}';
|
||||
}
|
||||
|
||||
try {
|
||||
cleanedToolResponse = tool_response !== undefined
|
||||
? stripMemoryTagsFromJson(JSON.stringify(tool_response))
|
||||
: '{}';
|
||||
} catch (error) {
|
||||
cleanedToolResponse = '{"error": "Failed to serialize tool_response"}';
|
||||
}
|
||||
|
||||
// Queue observation
|
||||
this.sessionManager.queueObservation(sessionDbId, {
|
||||
tool_name,
|
||||
tool_input: cleanedToolInput,
|
||||
tool_response: cleanedToolResponse,
|
||||
prompt_number: promptNumber,
|
||||
cwd: cwd || ''
|
||||
});
|
||||
|
||||
// Ensure SDK agent is running
|
||||
this.ensureGeneratorRunning(sessionDbId, 'observation');
|
||||
|
||||
// Broadcast activity status
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'observation_queued',
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Observation by claudeId failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
if (!claudeSessionId) {
|
||||
return this.badRequest(res, 'Missing claudeSessionId');
|
||||
}
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Get or create session
|
||||
const sessionDbId = store.createSDKSession(claudeSessionId, '', '');
|
||||
const promptNumber = store.getPromptCounter(sessionDbId);
|
||||
|
||||
// Privacy check: skip if user prompt was entirely private
|
||||
const userPrompt = store.getUserPrompt(claudeSessionId, promptNumber);
|
||||
if (!userPrompt || userPrompt.trim() === '') {
|
||||
logger.debug('HOOK', 'Skipping observation - user prompt was entirely private', {
|
||||
sessionId: sessionDbId,
|
||||
promptNumber,
|
||||
tool_name
|
||||
});
|
||||
res.json({ status: 'skipped', reason: 'private' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Strip memory tags from tool_input and tool_response
|
||||
let cleanedToolInput = '{}';
|
||||
let cleanedToolResponse = '{}';
|
||||
|
||||
try {
|
||||
cleanedToolInput = tool_input !== undefined
|
||||
? stripMemoryTagsFromJson(JSON.stringify(tool_input))
|
||||
: '{}';
|
||||
} catch (error) {
|
||||
cleanedToolInput = '{"error": "Failed to serialize tool_input"}';
|
||||
}
|
||||
|
||||
try {
|
||||
cleanedToolResponse = tool_response !== undefined
|
||||
? stripMemoryTagsFromJson(JSON.stringify(tool_response))
|
||||
: '{}';
|
||||
} catch (error) {
|
||||
cleanedToolResponse = '{"error": "Failed to serialize tool_response"}';
|
||||
}
|
||||
|
||||
// Queue observation
|
||||
this.sessionManager.queueObservation(sessionDbId, {
|
||||
tool_name,
|
||||
tool_input: cleanedToolInput,
|
||||
tool_response: cleanedToolResponse,
|
||||
prompt_number: promptNumber,
|
||||
cwd: cwd || ''
|
||||
});
|
||||
|
||||
// Ensure SDK agent is running
|
||||
this.ensureGeneratorRunning(sessionDbId, 'observation');
|
||||
|
||||
// Broadcast activity status
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'observation_queued',
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Queue summarize by claudeSessionId (summary-hook uses this)
|
||||
@@ -390,47 +364,41 @@ export class SessionRoutes {
|
||||
*
|
||||
* Checks privacy, queues summarize request for SDK agent
|
||||
*/
|
||||
private handleSummarizeByClaudeId(req: Request, res: Response): void {
|
||||
try {
|
||||
const { claudeSessionId, last_user_message, last_assistant_message } = req.body;
|
||||
private handleSummarizeByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { claudeSessionId, last_user_message, last_assistant_message } = req.body;
|
||||
|
||||
if (!claudeSessionId) {
|
||||
res.status(400).json({ error: 'Missing claudeSessionId' });
|
||||
return;
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Get or create session
|
||||
const sessionDbId = store.createSDKSession(claudeSessionId, '', '');
|
||||
const promptNumber = store.getPromptCounter(sessionDbId);
|
||||
|
||||
// Privacy check: skip if user prompt was entirely private
|
||||
const userPrompt = store.getUserPrompt(claudeSessionId, promptNumber);
|
||||
if (!userPrompt || userPrompt.trim() === '') {
|
||||
logger.debug('HOOK', 'Skipping summary - user prompt was entirely private', {
|
||||
sessionId: sessionDbId,
|
||||
promptNumber
|
||||
});
|
||||
res.json({ status: 'skipped', reason: 'private' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue summarize
|
||||
this.sessionManager.queueSummarize(sessionDbId, last_user_message || '', last_assistant_message);
|
||||
|
||||
// Ensure SDK agent is running
|
||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||
|
||||
// Broadcast activity status
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Summarize by claudeId failed', {}, error as Error);
|
||||
res.status(500).json({ error: (error as Error).message });
|
||||
if (!claudeSessionId) {
|
||||
return this.badRequest(res, 'Missing claudeSessionId');
|
||||
}
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Get or create session
|
||||
const sessionDbId = store.createSDKSession(claudeSessionId, '', '');
|
||||
const promptNumber = store.getPromptCounter(sessionDbId);
|
||||
|
||||
// Privacy check: skip if user prompt was entirely private
|
||||
const userPrompt = store.getUserPrompt(claudeSessionId, promptNumber);
|
||||
if (!userPrompt || userPrompt.trim() === '') {
|
||||
logger.debug('HOOK', 'Skipping summary - user prompt was entirely private', {
|
||||
sessionId: sessionDbId,
|
||||
promptNumber
|
||||
});
|
||||
res.json({ status: 'skipped', reason: 'private' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Queue summarize
|
||||
this.sessionManager.queueSummarize(sessionDbId, last_user_message || '', last_assistant_message);
|
||||
|
||||
// Ensure SDK agent is running
|
||||
this.ensureGeneratorRunning(sessionDbId, 'summarize');
|
||||
|
||||
// Broadcast activity status
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
res.json({ status: 'queued' });
|
||||
});
|
||||
|
||||
/**
|
||||
* Complete session by claudeSessionId (cleanup-hook uses this)
|
||||
@@ -439,47 +407,41 @@ export class SessionRoutes {
|
||||
*
|
||||
* Marks session complete, stops SDK agent, broadcasts status
|
||||
*/
|
||||
private async handleSessionCompleteByClaudeId(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { claudeSessionId } = req.body;
|
||||
private handleSessionCompleteByClaudeId = this.wrapHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const { claudeSessionId } = req.body;
|
||||
|
||||
if (!claudeSessionId) {
|
||||
res.status(400).json({ success: false, error: 'Missing claudeSessionId' });
|
||||
return;
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Find session by claudeSessionId
|
||||
const session = store.findActiveSDKSession(claudeSessionId);
|
||||
if (!session) {
|
||||
// No active session - nothing to clean up (may have already been completed)
|
||||
res.json({ success: true, message: 'No active session found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionDbId = session.id;
|
||||
|
||||
// Delete from session manager (aborts SDK agent)
|
||||
await this.sessionManager.deleteSession(sessionDbId);
|
||||
|
||||
// Mark session complete in database
|
||||
this.dbManager.markSessionComplete(sessionDbId);
|
||||
|
||||
// Broadcast processing status
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_completed',
|
||||
timestamp: Date.now(),
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
logger.failure('WORKER', 'Session complete by claudeId failed', {}, error as Error);
|
||||
res.status(500).json({ success: false, error: String(error) });
|
||||
if (!claudeSessionId) {
|
||||
return this.badRequest(res, 'Missing claudeSessionId');
|
||||
}
|
||||
}
|
||||
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Find session by claudeSessionId
|
||||
const session = store.findActiveSDKSession(claudeSessionId);
|
||||
if (!session) {
|
||||
// No active session - nothing to clean up (may have already been completed)
|
||||
res.json({ success: true, message: 'No active session found' });
|
||||
return;
|
||||
}
|
||||
|
||||
const sessionDbId = session.id;
|
||||
|
||||
// Delete from session manager (aborts SDK agent)
|
||||
await this.sessionManager.deleteSession(sessionDbId);
|
||||
|
||||
// Mark session complete in database
|
||||
this.dbManager.markSessionComplete(sessionDbId);
|
||||
|
||||
// Broadcast processing status
|
||||
this.workerService.broadcastProcessingStatus();
|
||||
|
||||
// Broadcast SSE event
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'session_completed',
|
||||
timestamp: Date.now(),
|
||||
sessionDbId
|
||||
});
|
||||
|
||||
res.json({ success: true });
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user