Merge branch 'pr-1472' into integration/validation-batch
# Conflicts: # plugin/scripts/context-generator.cjs # plugin/scripts/mcp-server.cjs # plugin/scripts/worker-service.cjs # plugin/ui/viewer-bundle.js # src/cli/handlers/context.ts # src/services/sqlite/SessionStore.ts # src/services/sqlite/migrations/runner.ts # src/services/worker-service.ts # src/shared/SettingsDefaultsManager.ts
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
|
||||
import { Request, Response } from 'express';
|
||||
import { logger } from '../../../utils/logger.js';
|
||||
import { AppError } from '../../server/ErrorHandler.js';
|
||||
|
||||
export abstract class BaseRouteHandler {
|
||||
/**
|
||||
@@ -78,9 +79,22 @@ export abstract class BaseRouteHandler {
|
||||
* Checks headersSent to avoid "Cannot set headers after they are sent" errors
|
||||
*/
|
||||
protected handleError(res: Response, error: Error, context?: string): void {
|
||||
// [APPROVED OVERRIDE]: Worker routes need centralized AppError translation so
|
||||
// status/code/details stay consistent across every HTTP handler.
|
||||
logger.failure('WORKER', context || 'Request failed', {}, error);
|
||||
if (!res.headersSent) {
|
||||
res.status(500).json({ error: error.message });
|
||||
const statusCode = error instanceof AppError ? error.statusCode : 500;
|
||||
const response: Record<string, unknown> = { error: error.message };
|
||||
|
||||
if (error instanceof AppError && error.code) {
|
||||
response.code = error.code;
|
||||
}
|
||||
|
||||
if (error instanceof AppError && error.details !== undefined) {
|
||||
response.details = error.details;
|
||||
}
|
||||
|
||||
res.status(statusCode).json(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,8 +66,8 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
* Get paginated observations
|
||||
*/
|
||||
private handleGetObservations = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getObservations(offset, limit, project);
|
||||
const { offset, limit, project, platformSource } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getObservations(offset, limit, project, platformSource);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
@@ -75,8 +75,8 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
* Get paginated summaries
|
||||
*/
|
||||
private handleGetSummaries = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getSummaries(offset, limit, project);
|
||||
const { offset, limit, project, platformSource } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getSummaries(offset, limit, project, platformSource);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
@@ -84,8 +84,8 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
* Get paginated user prompts
|
||||
*/
|
||||
private handleGetPrompts = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { offset, limit, project } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getPrompts(offset, limit, project);
|
||||
const { offset, limit, project, platformSource } = this.parsePaginationParams(req);
|
||||
const result = this.paginationHelper.getPrompts(offset, limit, project, platformSource);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
@@ -256,19 +256,19 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
* GET /api/projects
|
||||
*/
|
||||
private handleGetProjects = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const db = this.dbManager.getSessionStore().db;
|
||||
const store = this.dbManager.getSessionStore();
|
||||
const platformSource = req.query.platformSource as string | undefined;
|
||||
|
||||
const rows = db.prepare(`
|
||||
SELECT DISTINCT project
|
||||
FROM observations
|
||||
WHERE project IS NOT NULL
|
||||
GROUP BY project
|
||||
ORDER BY MAX(created_at_epoch) DESC
|
||||
`).all() as Array<{ project: string }>;
|
||||
if (platformSource) {
|
||||
res.json({
|
||||
projects: store.getAllProjects(platformSource),
|
||||
sources: [platformSource],
|
||||
projectsBySource: { [platformSource]: store.getAllProjects(platformSource) }
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const projects = rows.map(row => row.project);
|
||||
|
||||
res.json({ projects });
|
||||
res.json(store.getProjectCatalog());
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -299,12 +299,13 @@ export class DataRoutes extends BaseRouteHandler {
|
||||
/**
|
||||
* Parse pagination parameters from request query
|
||||
*/
|
||||
private parsePaginationParams(req: Request): { offset: number; limit: number; project?: string } {
|
||||
private parsePaginationParams(req: Request): { offset: number; limit: number; project?: string; platformSource?: string } {
|
||||
const offset = parseInt(req.query.offset as string, 10) || 0;
|
||||
const limit = Math.min(parseInt(req.query.limit as string, 10) || 20, 100); // Max 100
|
||||
const project = req.query.project as string | undefined;
|
||||
const platformSource = req.query.platformSource as string | undefined;
|
||||
|
||||
return { offset, limit, project };
|
||||
return { offset, limit, project, platformSource };
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -168,6 +168,7 @@ export class SearchRoutes extends BaseRouteHandler {
|
||||
*/
|
||||
private handleContextPreview = this.wrapHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const projectName = req.query.project as string;
|
||||
const platformSource = req.query.platformSource as string | undefined;
|
||||
|
||||
if (!projectName) {
|
||||
this.badRequest(res, 'Project parameter is required');
|
||||
@@ -184,7 +185,9 @@ export class SearchRoutes extends BaseRouteHandler {
|
||||
const contextText = await generateContext(
|
||||
{
|
||||
session_id: 'preview-' + Date.now(),
|
||||
cwd: cwd
|
||||
cwd: cwd,
|
||||
projects: [projectName],
|
||||
platform_source: platformSource
|
||||
},
|
||||
true // forHuman=true for ANSI terminal output
|
||||
);
|
||||
@@ -210,6 +213,7 @@ export class SearchRoutes extends BaseRouteHandler {
|
||||
const projectsParam = (req.query.projects as string) || (req.query.project as string);
|
||||
const forHuman = req.query.colors === 'true';
|
||||
const full = req.query.full === 'true';
|
||||
const platformSource = req.query.platformSource as string | undefined;
|
||||
|
||||
if (!projectsParam) {
|
||||
this.badRequest(res, 'Project(s) parameter is required');
|
||||
@@ -237,7 +241,8 @@ export class SearchRoutes extends BaseRouteHandler {
|
||||
session_id: 'context-inject-' + Date.now(),
|
||||
cwd: cwd,
|
||||
projects: projects,
|
||||
full
|
||||
full,
|
||||
platform_source: platformSource
|
||||
},
|
||||
forHuman
|
||||
);
|
||||
|
||||
@@ -22,6 +22,8 @@ import { PrivacyCheckValidator } from '../../validation/PrivacyCheckValidator.js
|
||||
import { SettingsDefaultsManager } from '../../../../shared/SettingsDefaultsManager.js';
|
||||
import { USER_SETTINGS_PATH } from '../../../../shared/paths.js';
|
||||
import { getProcessBySession, ensureProcessExit } from '../../ProcessRegistry.js';
|
||||
import { getProjectName } from '../../../../utils/project-name.js';
|
||||
import { normalizePlatformSource } from '../../../../shared/platform-source.js';
|
||||
|
||||
export class SessionRoutes extends BaseRouteHandler {
|
||||
private completionHandler: SessionCompletionHandler;
|
||||
@@ -354,6 +356,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
id: latestPrompt.id,
|
||||
content_session_id: latestPrompt.content_session_id,
|
||||
project: latestPrompt.project,
|
||||
platform_source: latestPrompt.platform_source,
|
||||
prompt_number: latestPrompt.prompt_number,
|
||||
prompt_text: latestPrompt.prompt_text,
|
||||
created_at_epoch: latestPrompt.created_at_epoch
|
||||
@@ -503,6 +506,8 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
*/
|
||||
private handleObservationsByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { contentSessionId, tool_name, tool_input, tool_response, cwd } = req.body;
|
||||
const platformSource = normalizePlatformSource(req.body.platformSource);
|
||||
const project = typeof cwd === 'string' && cwd.trim() ? getProjectName(cwd) : '';
|
||||
|
||||
if (!contentSessionId) {
|
||||
return this.badRequest(res, 'Missing contentSessionId');
|
||||
@@ -537,7 +542,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Get or create session
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, '', '');
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, project, '', undefined, platformSource);
|
||||
const promptNumber = store.getPromptNumberFromUserPrompts(contentSessionId);
|
||||
|
||||
// Privacy check: skip if user prompt was entirely private
|
||||
@@ -601,6 +606,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
*/
|
||||
private handleSummarizeByClaudeId = this.wrapHandler((req: Request, res: Response): void => {
|
||||
const { contentSessionId, last_assistant_message } = req.body;
|
||||
const platformSource = normalizePlatformSource(req.body.platformSource);
|
||||
|
||||
if (!contentSessionId) {
|
||||
return this.badRequest(res, 'Missing contentSessionId');
|
||||
@@ -609,7 +615,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Get or create session
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, '', '');
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, '', '', undefined, platformSource);
|
||||
const promptNumber = store.getPromptNumberFromUserPrompts(contentSessionId);
|
||||
|
||||
// Privacy check: skip if user prompt was entirely private
|
||||
@@ -682,6 +688,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
*/
|
||||
private handleCompleteByClaudeId = this.wrapHandler(async (req: Request, res: Response): Promise<void> => {
|
||||
const { contentSessionId } = req.body;
|
||||
const platformSource = normalizePlatformSource(req.body.platformSource);
|
||||
|
||||
logger.info('HTTP', '→ POST /api/sessions/complete', { contentSessionId });
|
||||
|
||||
@@ -693,7 +700,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
|
||||
// Look up sessionDbId from contentSessionId (createSDKSession is idempotent)
|
||||
// Pass empty strings - we only need the ID lookup, not to create a new session
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, '', '');
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, '', '', undefined, platformSource);
|
||||
|
||||
// Check if session is in the active sessions map
|
||||
const activeSession = this.sessionManager.getSession(sessionDbId);
|
||||
@@ -739,11 +746,13 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
// may omit prompt/project in their payload (#838, #1049)
|
||||
const project = req.body.project || 'unknown';
|
||||
const prompt = req.body.prompt || '[media prompt]';
|
||||
const platformSource = req.body.platformSource || 'claude';
|
||||
const customTitle = req.body.customTitle || undefined;
|
||||
|
||||
logger.info('HTTP', 'SessionRoutes: handleSessionInitByClaudeId called', {
|
||||
contentSessionId,
|
||||
project,
|
||||
platformSource,
|
||||
prompt_length: prompt?.length,
|
||||
customTitle
|
||||
});
|
||||
@@ -756,7 +765,7 @@ export class SessionRoutes extends BaseRouteHandler {
|
||||
const store = this.dbManager.getSessionStore();
|
||||
|
||||
// Step 1: Create/get SDK session (idempotent INSERT OR IGNORE)
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, project, prompt, customTitle);
|
||||
const sessionDbId = store.createSDKSession(contentSessionId, project, prompt, customTitle, platformSource);
|
||||
|
||||
// Verify session creation with DB lookup
|
||||
const dbSession = store.getSessionById(sessionDbId);
|
||||
|
||||
@@ -76,11 +76,13 @@ export class ViewerRoutes extends BaseRouteHandler {
|
||||
// Add client to broadcaster
|
||||
this.sseBroadcaster.addClient(res);
|
||||
|
||||
// Send initial_load event with projects list
|
||||
const allProjects = this.dbManager.getSessionStore().getAllProjects();
|
||||
// Send initial_load event with project/source catalog
|
||||
const projectCatalog = this.dbManager.getSessionStore().getProjectCatalog();
|
||||
this.sseBroadcaster.broadcast({
|
||||
type: 'initial_load',
|
||||
projects: allProjects,
|
||||
projects: projectCatalog.projects,
|
||||
sources: projectCatalog.sources,
|
||||
projectsBySource: projectCatalog.projectsBySource,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user