mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: Add CodexLens Manager Page with tabbed interface for managing CodexLens features
feat: Implement ConflictTab component to display conflict resolution decisions in session detail feat: Create ImplPlanTab component to show implementation plan with modal viewer in session detail feat: Develop ReviewTab component to display review findings by dimension in session detail test: Add end-to-end tests for CodexLens Manager functionality including navigation, tab switching, and settings validation
This commit is contained in:
@@ -483,6 +483,35 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
}
|
||||
|
||||
// Handle GET request - return conversation with native session info
|
||||
// First check in-memory active executions (for running/recently completed)
|
||||
const activeExec = activeExecutions.get(executionId);
|
||||
if (activeExec) {
|
||||
// Return active execution data as conversation record format
|
||||
const activeConversation = {
|
||||
id: activeExec.id,
|
||||
tool: activeExec.tool,
|
||||
mode: activeExec.mode,
|
||||
created_at: new Date(activeExec.startTime).toISOString(),
|
||||
turn_count: 1,
|
||||
turns: [{
|
||||
turn: 1,
|
||||
timestamp: new Date(activeExec.startTime).toISOString(),
|
||||
prompt: activeExec.prompt,
|
||||
output: { stdout: activeExec.output, stderr: '' },
|
||||
duration_ms: activeExec.completedTimestamp
|
||||
? activeExec.completedTimestamp - activeExec.startTime
|
||||
: Date.now() - activeExec.startTime
|
||||
}],
|
||||
// Active execution flag for frontend to handle appropriately
|
||||
_active: true,
|
||||
_status: activeExec.status
|
||||
};
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(activeConversation));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Fall back to database query for saved conversations
|
||||
const conversation = getConversationDetailWithNativeInfo(projectPath, executionId);
|
||||
if (!conversation) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
|
||||
@@ -412,6 +412,116 @@ export async function handleHooksRoutes(ctx: HooksRouteContext): Promise<boolean
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Execute CCW CLI command and parse status
|
||||
if (pathname === '/api/hook/ccw-exec' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
if (typeof body !== 'object' || body === null) {
|
||||
return { error: 'Invalid request body', status: 400 };
|
||||
}
|
||||
|
||||
const { filePath, command = 'parse-status' } = body as { filePath?: unknown; command?: unknown };
|
||||
|
||||
if (typeof filePath !== 'string') {
|
||||
return { error: 'filePath is required', status: 400 };
|
||||
}
|
||||
|
||||
// Check if this is a CCW status.json file
|
||||
if (!filePath.includes('status.json') ||
|
||||
!filePath.match(/\.(ccw|ccw-coordinator|ccw-debug)\//)) {
|
||||
return { success: false, message: 'Not a CCW status file' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Execute CCW CLI command to parse status
|
||||
const result = await executeCliCommand('ccw', ['hook', 'parse-status', filePath]);
|
||||
|
||||
if (result.success) {
|
||||
const parsed = JSON.parse(result.output);
|
||||
return {
|
||||
success: true,
|
||||
...parsed
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: result.error
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[Hooks] Failed to execute CCW command:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Parse CCW status.json and return formatted status (fallback)
|
||||
if (pathname === '/api/hook/ccw-status' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
if (typeof body !== 'object' || body === null) {
|
||||
return { error: 'Invalid request body', status: 400 };
|
||||
}
|
||||
|
||||
const { filePath } = body as { filePath?: unknown };
|
||||
|
||||
if (typeof filePath !== 'string') {
|
||||
return { error: 'filePath is required', status: 400 };
|
||||
}
|
||||
|
||||
// Check if this is a CCW status.json file
|
||||
if (!filePath.includes('status.json') ||
|
||||
!filePath.match(/\.(ccw|ccw-coordinator|ccw-debug)\//)) {
|
||||
return { success: false, message: 'Not a CCW status file' };
|
||||
}
|
||||
|
||||
try {
|
||||
// Read and parse status.json
|
||||
if (!existsSync(filePath)) {
|
||||
return { success: false, message: 'Status file not found' };
|
||||
}
|
||||
|
||||
const statusContent = readFileSync(filePath, 'utf8');
|
||||
const status = JSON.parse(statusContent);
|
||||
|
||||
// Extract key information
|
||||
const sessionId = status.session_id || 'unknown';
|
||||
const workflow = status.workflow || status.mode || 'unknown';
|
||||
|
||||
// Find current command (running or last completed)
|
||||
let currentCommand = status.command_chain?.find((cmd: { status: string }) => cmd.status === 'running')?.command;
|
||||
if (!currentCommand) {
|
||||
const completed = status.command_chain?.filter((cmd: { status: string }) => cmd.status === 'completed');
|
||||
currentCommand = completed?.[completed.length - 1]?.command || 'unknown';
|
||||
}
|
||||
|
||||
// Find next command (first pending)
|
||||
const nextCommand = status.command_chain?.find((cmd: { status: string }) => cmd.status === 'pending')?.command || '无';
|
||||
|
||||
// Format status message
|
||||
const message = `📋 CCW Status [${sessionId}] (${workflow}): 当前处于 ${currentCommand},下一个命令 ${nextCommand}`;
|
||||
|
||||
return {
|
||||
success: true,
|
||||
message,
|
||||
sessionId,
|
||||
workflow,
|
||||
currentCommand,
|
||||
nextCommand
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[Hooks] Failed to parse CCW status:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
};
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// API: Get hooks configuration
|
||||
if (pathname === '/api/hooks' && req.method === 'GET') {
|
||||
const projectPathParam = url.searchParams.get('path');
|
||||
@@ -471,3 +581,63 @@ export async function handleHooksRoutes(ctx: HooksRouteContext): Promise<boolean
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Helper: Execute CLI Command
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Execute a CLI command and capture output
|
||||
* @param {string} command - Command name (e.g., 'ccw', 'npx')
|
||||
* @param {string[]} args - Command arguments
|
||||
* @returns {Promise<{success: boolean; output: string; error?: string}>}
|
||||
*/
|
||||
async function executeCliCommand(
|
||||
command: string,
|
||||
args: string[]
|
||||
): Promise<{ success: boolean; output: string; error?: string }> {
|
||||
return new Promise((resolve) => {
|
||||
let output = '';
|
||||
let errorOutput = '';
|
||||
|
||||
const child = spawn(command, args, {
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
timeout: 30000 // 30 second timeout
|
||||
});
|
||||
|
||||
if (child.stdout) {
|
||||
child.stdout.on('data', (data) => {
|
||||
output += data.toString();
|
||||
});
|
||||
}
|
||||
|
||||
if (child.stderr) {
|
||||
child.stderr.on('data', (data) => {
|
||||
errorOutput += data.toString();
|
||||
});
|
||||
}
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve({
|
||||
success: true,
|
||||
output: output.trim()
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: false,
|
||||
output: output.trim(),
|
||||
error: errorOutput.trim() || `Command failed with exit code ${code}`
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (err) => {
|
||||
resolve({
|
||||
success: false,
|
||||
output: '',
|
||||
error: (err as Error).message
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user