mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-09 02:24:11 +08:00
feat: implement FlowExecutor for executing flow definitions with DAG traversal and node execution
This commit is contained in:
103
ccw/src/core/routes/dashboard-routes.ts
Normal file
103
ccw/src/core/routes/dashboard-routes.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Dashboard Routes Module
|
||||
* Provides API endpoints for dashboard initialization and configuration
|
||||
*
|
||||
* Endpoints:
|
||||
* - GET /api/dashboard/init - Returns initial dashboard data (projectPath, recentPaths, platform, initialData)
|
||||
*/
|
||||
|
||||
import type { RouteContext } from './types.js';
|
||||
import { getRecentPaths, normalizePathForDisplay } from '../../utils/path-resolver.js';
|
||||
|
||||
/**
|
||||
* Dashboard initialization response structure
|
||||
*/
|
||||
interface DashboardInitResponse {
|
||||
projectPath: string;
|
||||
recentPaths: string[];
|
||||
platform: string;
|
||||
initialData: {
|
||||
generatedAt: string;
|
||||
activeSessions: unknown[];
|
||||
archivedSessions: unknown[];
|
||||
liteTasks: {
|
||||
litePlan: unknown[];
|
||||
liteFix: unknown[];
|
||||
multiCliPlan: unknown[];
|
||||
};
|
||||
reviewData: {
|
||||
dimensions: Record<string, unknown>;
|
||||
};
|
||||
projectOverview: null;
|
||||
statistics: {
|
||||
totalSessions: number;
|
||||
activeSessions: number;
|
||||
totalTasks: number;
|
||||
completedTasks: number;
|
||||
reviewFindings: number;
|
||||
litePlanCount: number;
|
||||
liteFixCount: number;
|
||||
multiCliPlanCount: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dashboard routes
|
||||
* @returns true if route was handled, false otherwise
|
||||
*/
|
||||
export async function handleDashboardRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
const { pathname, req, res, initialPath } = ctx;
|
||||
|
||||
// GET /api/dashboard/init - Return initial dashboard data
|
||||
if (pathname === '/api/dashboard/init' && req.method === 'GET') {
|
||||
try {
|
||||
const response: DashboardInitResponse = {
|
||||
projectPath: normalizePathForDisplay(initialPath),
|
||||
recentPaths: getRecentPaths(),
|
||||
platform: process.platform,
|
||||
initialData: {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: {
|
||||
litePlan: [],
|
||||
liteFix: [],
|
||||
multiCliPlan: []
|
||||
},
|
||||
reviewData: {
|
||||
dimensions: {}
|
||||
},
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0,
|
||||
multiCliPlanCount: 0
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
data: response,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
return true;
|
||||
} catch (error) {
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: false,
|
||||
error: (error as Error).message
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
1688
ccw/src/core/routes/orchestrator-routes.ts
Normal file
1688
ccw/src/core/routes/orchestrator-routes.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -34,6 +34,8 @@ import { handleLoopRoutes } from './routes/loop-routes.js';
|
||||
import { handleLoopV2Routes, initializeCliToolsCache } from './routes/loop-v2-routes.js';
|
||||
import { handleTestLoopRoutes } from './routes/test-loop-routes.js';
|
||||
import { handleTaskRoutes } from './routes/task-routes.js';
|
||||
import { handleDashboardRoutes } from './routes/dashboard-routes.js';
|
||||
import { handleOrchestratorRoutes } from './routes/orchestrator-routes.js';
|
||||
|
||||
// Import WebSocket handling
|
||||
import { handleWebSocketUpgrade, broadcastToClients, extractSessionIdFromPath } from './websocket.js';
|
||||
@@ -514,6 +516,11 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
|
||||
if (await handleNavStatusRoutes(routeContext)) return;
|
||||
}
|
||||
|
||||
// Dashboard routes (/api/dashboard/*) - Dashboard initialization
|
||||
if (pathname.startsWith('/api/dashboard/')) {
|
||||
if (await handleDashboardRoutes(routeContext)) return;
|
||||
}
|
||||
|
||||
// CLI routes (/api/cli/*)
|
||||
if (pathname.startsWith('/api/cli/')) {
|
||||
// CLI Settings routes first (more specific path /api/cli/settings/*)
|
||||
@@ -577,6 +584,11 @@ export async function startServer(options: ServerOptions = {}): Promise<http.Ser
|
||||
if (await handleCcwRoutes(routeContext)) return;
|
||||
}
|
||||
|
||||
// Orchestrator routes (/api/orchestrator/*)
|
||||
if (pathname.startsWith('/api/orchestrator/')) {
|
||||
if (await handleOrchestratorRoutes(routeContext)) return;
|
||||
}
|
||||
|
||||
// Loop V2 routes (/api/loops/v2/*) - must be checked before v1
|
||||
if (pathname.startsWith('/api/loops/v2')) {
|
||||
if (await handleLoopV2Routes(routeContext)) return;
|
||||
|
||||
1154
ccw/src/core/services/flow-executor.ts
Normal file
1154
ccw/src/core/services/flow-executor.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -63,6 +63,79 @@ export interface LoopLogEntryMessage {
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrator WebSocket message types
|
||||
*/
|
||||
export type OrchestratorMessageType =
|
||||
| 'ORCHESTRATOR_STATE_UPDATE'
|
||||
| 'ORCHESTRATOR_NODE_STARTED'
|
||||
| 'ORCHESTRATOR_NODE_COMPLETED'
|
||||
| 'ORCHESTRATOR_NODE_FAILED'
|
||||
| 'ORCHESTRATOR_LOG';
|
||||
|
||||
/**
|
||||
* Execution log entry for Orchestrator
|
||||
*/
|
||||
export interface ExecutionLog {
|
||||
timestamp: string;
|
||||
level: 'info' | 'warn' | 'error' | 'debug';
|
||||
nodeId?: string;
|
||||
message: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrator State Update - fired when execution status changes
|
||||
*/
|
||||
export interface OrchestratorStateUpdateMessage {
|
||||
type: 'ORCHESTRATOR_STATE_UPDATE';
|
||||
execId: string;
|
||||
status: 'pending' | 'running' | 'paused' | 'completed' | 'failed';
|
||||
currentNodeId?: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrator Node Started - fired when a node begins execution
|
||||
*/
|
||||
export interface OrchestratorNodeStartedMessage {
|
||||
type: 'ORCHESTRATOR_NODE_STARTED';
|
||||
execId: string;
|
||||
nodeId: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrator Node Completed - fired when a node finishes successfully
|
||||
*/
|
||||
export interface OrchestratorNodeCompletedMessage {
|
||||
type: 'ORCHESTRATOR_NODE_COMPLETED';
|
||||
execId: string;
|
||||
nodeId: string;
|
||||
result?: unknown;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrator Node Failed - fired when a node encounters an error
|
||||
*/
|
||||
export interface OrchestratorNodeFailedMessage {
|
||||
type: 'ORCHESTRATOR_NODE_FAILED';
|
||||
execId: string;
|
||||
nodeId: string;
|
||||
error: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orchestrator Log - fired for execution log entries
|
||||
*/
|
||||
export interface OrchestratorLogMessage {
|
||||
type: 'ORCHESTRATOR_LOG';
|
||||
execId: string;
|
||||
log: ExecutionLog;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
export function handleWebSocketUpgrade(req: IncomingMessage, socket: Duplex, _head: Buffer): void {
|
||||
const header = req.headers['sec-websocket-key'];
|
||||
const key = Array.isArray(header) ? header[0] : header;
|
||||
@@ -300,3 +373,59 @@ export function broadcastLoopLog(loop_id: string, step_id: string, line: string)
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Union type for Orchestrator messages (without timestamp - added automatically)
|
||||
*/
|
||||
export type OrchestratorMessage =
|
||||
| Omit<OrchestratorStateUpdateMessage, 'timestamp'>
|
||||
| Omit<OrchestratorNodeStartedMessage, 'timestamp'>
|
||||
| Omit<OrchestratorNodeCompletedMessage, 'timestamp'>
|
||||
| Omit<OrchestratorNodeFailedMessage, 'timestamp'>
|
||||
| Omit<OrchestratorLogMessage, 'timestamp'>;
|
||||
|
||||
/**
|
||||
* Orchestrator-specific broadcast with throttling
|
||||
* Throttles ORCHESTRATOR_STATE_UPDATE messages to avoid flooding clients
|
||||
*/
|
||||
let lastOrchestratorBroadcast = 0;
|
||||
const ORCHESTRATOR_BROADCAST_THROTTLE = 1000; // 1 second
|
||||
|
||||
/**
|
||||
* Broadcast orchestrator update with throttling
|
||||
* STATE_UPDATE messages are throttled to 1 per second
|
||||
* Other message types are sent immediately
|
||||
*/
|
||||
export function broadcastOrchestratorUpdate(message: OrchestratorMessage): void {
|
||||
const now = Date.now();
|
||||
|
||||
// Throttle ORCHESTRATOR_STATE_UPDATE to reduce WebSocket traffic
|
||||
if (message.type === 'ORCHESTRATOR_STATE_UPDATE' && now - lastOrchestratorBroadcast < ORCHESTRATOR_BROADCAST_THROTTLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'ORCHESTRATOR_STATE_UPDATE') {
|
||||
lastOrchestratorBroadcast = now;
|
||||
}
|
||||
|
||||
broadcastToClients({
|
||||
...message,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast orchestrator log entry (no throttling)
|
||||
* Used for streaming real-time execution logs to Dashboard
|
||||
*/
|
||||
export function broadcastOrchestratorLog(execId: string, log: Omit<ExecutionLog, 'timestamp'>): void {
|
||||
broadcastToClients({
|
||||
type: 'ORCHESTRATOR_LOG',
|
||||
execId,
|
||||
log: {
|
||||
...log,
|
||||
timestamp: new Date().toISOString()
|
||||
},
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user