mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
fix(orchestrator): complete remaining high/medium priority fixes
Backend (orchestrator-routes.ts): - Added broadcastExecutionStatusMessage helper for specific message types - Added EXECUTION_PAUSED, EXECUTION_RESUMED, EXECUTION_STOPPED broadcasts - Added CLI_SESSION_UNLOCKED broadcast on execution completion/failure - Added sessionKey to ExecutionState interface for tracking - Added totalSteps to EXECUTION_STARTED WebSocket message Frontend (executionMonitorStore.ts): - Added EXECUTION_FAILED message type handling - Added totalSteps extraction from EXECUTION_STARTED payload - Implemented pauseExecution, resumeExecution, stopExecution API calls - Replaced TODO console.log with actual fetch API calls Frontend (useWebSocket.ts): - Added import for executionMonitorStore - Added EXECUTION_* message routing to executionMonitorStore
This commit is contained in:
@@ -13,6 +13,10 @@ import {
|
|||||||
handleSessionLockedMessage,
|
handleSessionLockedMessage,
|
||||||
handleSessionUnlockedMessage,
|
handleSessionUnlockedMessage,
|
||||||
} from '@/stores/sessionManagerStore';
|
} from '@/stores/sessionManagerStore';
|
||||||
|
import {
|
||||||
|
useExecutionMonitorStore,
|
||||||
|
type ExecutionWSMessage,
|
||||||
|
} from '@/stores/executionMonitorStore';
|
||||||
import {
|
import {
|
||||||
OrchestratorMessageSchema,
|
OrchestratorMessageSchema,
|
||||||
type OrchestratorWebSocketMessage,
|
type OrchestratorWebSocketMessage,
|
||||||
@@ -330,6 +334,13 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle EXECUTION messages (from orchestrator execution-in-session)
|
||||||
|
if (data.type?.startsWith('EXECUTION_')) {
|
||||||
|
const handleExecutionMessage = useExecutionMonitorStore.getState().handleExecutionMessage;
|
||||||
|
handleExecutionMessage(data as ExecutionWSMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Handle A2UI surface messages
|
// Handle A2UI surface messages
|
||||||
if (data.type === 'a2ui-surface') {
|
if (data.type === 'a2ui-surface') {
|
||||||
const parsed = SurfaceUpdateSchema.safeParse(data.payload);
|
const parsed = SurfaceUpdateSchema.safeParse(data.payload);
|
||||||
|
|||||||
@@ -44,7 +44,8 @@ export type ExecutionWSMessageType =
|
|||||||
| 'EXECUTION_PAUSED'
|
| 'EXECUTION_PAUSED'
|
||||||
| 'EXECUTION_RESUMED'
|
| 'EXECUTION_RESUMED'
|
||||||
| 'EXECUTION_STOPPED'
|
| 'EXECUTION_STOPPED'
|
||||||
| 'EXECUTION_COMPLETED';
|
| 'EXECUTION_COMPLETED'
|
||||||
|
| 'EXECUTION_FAILED';
|
||||||
|
|
||||||
export interface ExecutionWSMessage {
|
export interface ExecutionWSMessage {
|
||||||
type: ExecutionWSMessageType;
|
type: ExecutionWSMessageType;
|
||||||
@@ -54,9 +55,11 @@ export interface ExecutionWSMessage {
|
|||||||
sessionKey: string;
|
sessionKey: string;
|
||||||
stepId?: string;
|
stepId?: string;
|
||||||
stepName?: string;
|
stepName?: string;
|
||||||
|
totalSteps?: number;
|
||||||
progress?: number;
|
progress?: number;
|
||||||
output?: string;
|
output?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
|
reason?: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -99,7 +102,7 @@ export const useExecutionMonitorStore = create<ExecutionMonitorStore>()(
|
|||||||
|
|
||||||
handleExecutionMessage: (msg: ExecutionWSMessage) => {
|
handleExecutionMessage: (msg: ExecutionWSMessage) => {
|
||||||
const { type, payload } = msg;
|
const { type, payload } = msg;
|
||||||
const { executionId, flowId, sessionKey, stepId, stepName, output, error, timestamp } = payload;
|
const { executionId, flowId, sessionKey, stepId, stepName, totalSteps, output, error, timestamp } = payload;
|
||||||
|
|
||||||
set((state) => {
|
set((state) => {
|
||||||
const existing = state.activeExecutions[executionId];
|
const existing = state.activeExecutions[executionId];
|
||||||
@@ -115,7 +118,7 @@ export const useExecutionMonitorStore = create<ExecutionMonitorStore>()(
|
|||||||
flowName: stepName || 'Workflow',
|
flowName: stepName || 'Workflow',
|
||||||
sessionKey,
|
sessionKey,
|
||||||
status: 'running',
|
status: 'running',
|
||||||
totalSteps: 0,
|
totalSteps: totalSteps || 0,
|
||||||
completedSteps: 0,
|
completedSteps: 0,
|
||||||
steps: [],
|
steps: [],
|
||||||
startedAt: timestamp,
|
startedAt: timestamp,
|
||||||
@@ -233,6 +236,15 @@ export const useExecutionMonitorStore = create<ExecutionMonitorStore>()(
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
case 'EXECUTION_FAILED':
|
||||||
|
if (!existing) return state;
|
||||||
|
return {
|
||||||
|
activeExecutions: {
|
||||||
|
...state.activeExecutions,
|
||||||
|
[executionId]: { ...existing, status: 'failed', completedAt: timestamp },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
@@ -243,19 +255,49 @@ export const useExecutionMonitorStore = create<ExecutionMonitorStore>()(
|
|||||||
set({ currentExecutionId: executionId }, false, 'selectExecution');
|
set({ currentExecutionId: executionId }, false, 'selectExecution');
|
||||||
},
|
},
|
||||||
|
|
||||||
pauseExecution: (executionId: string) => {
|
pauseExecution: async (executionId: string) => {
|
||||||
// TODO: Call API to pause execution
|
try {
|
||||||
console.log('[ExecutionMonitor] Pause execution:', executionId);
|
const response = await fetch(`/api/orchestrator/executions/${executionId}/pause`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('[ExecutionMonitor] Pause failed:', result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ExecutionMonitor] Pause error:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
resumeExecution: (executionId: string) => {
|
resumeExecution: async (executionId: string) => {
|
||||||
// TODO: Call API to resume execution
|
try {
|
||||||
console.log('[ExecutionMonitor] Resume execution:', executionId);
|
const response = await fetch(`/api/orchestrator/executions/${executionId}/resume`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('[ExecutionMonitor] Resume failed:', result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ExecutionMonitor] Resume error:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
stopExecution: (executionId: string) => {
|
stopExecution: async (executionId: string) => {
|
||||||
// TODO: Call API to stop execution
|
try {
|
||||||
console.log('[ExecutionMonitor] Stop execution:', executionId);
|
const response = await fetch(`/api/orchestrator/executions/${executionId}/stop`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
if (!result.success) {
|
||||||
|
console.error('[ExecutionMonitor] Stop failed:', result.error);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[ExecutionMonitor] Stop error:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setPanelOpen: (open: boolean) => {
|
setPanelOpen: (open: boolean) => {
|
||||||
|
|||||||
@@ -305,6 +305,8 @@ export interface ExecutionState {
|
|||||||
startedAt?: string;
|
startedAt?: string;
|
||||||
completedAt?: string;
|
completedAt?: string;
|
||||||
currentNodeId?: string;
|
currentNodeId?: string;
|
||||||
|
/** Session key if execution is running in a PTY session */
|
||||||
|
sessionKey?: string;
|
||||||
variables: Record<string, unknown>;
|
variables: Record<string, unknown>;
|
||||||
nodeStates: Record<string, NodeExecutionState>;
|
nodeStates: Record<string, NodeExecutionState>;
|
||||||
logs: ExecutionLog[];
|
logs: ExecutionLog[];
|
||||||
@@ -1190,6 +1192,54 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Helper to broadcast specific execution status messages (for frontend executionMonitorStore)
|
||||||
|
const broadcastExecutionStatusMessage = (
|
||||||
|
execution: ExecutionState,
|
||||||
|
sessionKey?: string
|
||||||
|
): void => {
|
||||||
|
const timestamp = new Date().toISOString();
|
||||||
|
|
||||||
|
// Map execution status to specific message types
|
||||||
|
const messageTypeMap: Record<string, string> = {
|
||||||
|
paused: 'EXECUTION_PAUSED',
|
||||||
|
running: 'EXECUTION_RESUMED',
|
||||||
|
completed: 'EXECUTION_COMPLETED',
|
||||||
|
failed: 'EXECUTION_FAILED',
|
||||||
|
};
|
||||||
|
|
||||||
|
const messageType = messageTypeMap[execution.status];
|
||||||
|
if (messageType) {
|
||||||
|
try {
|
||||||
|
broadcastToClients({
|
||||||
|
type: messageType,
|
||||||
|
payload: {
|
||||||
|
executionId: execution.id,
|
||||||
|
flowId: execution.flowId,
|
||||||
|
status: execution.status,
|
||||||
|
timestamp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Ignore broadcast errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Broadcast CLI_SESSION_UNLOCKED when execution completes or fails
|
||||||
|
if ((execution.status === 'completed' || execution.status === 'failed') && sessionKey) {
|
||||||
|
try {
|
||||||
|
broadcastToClients({
|
||||||
|
type: 'CLI_SESSION_UNLOCKED',
|
||||||
|
payload: {
|
||||||
|
sessionKey,
|
||||||
|
timestamp,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
// Ignore broadcast errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ==== EXECUTE FLOW ====
|
// ==== EXECUTE FLOW ====
|
||||||
// POST /api/orchestrator/flows/:id/execute
|
// POST /api/orchestrator/flows/:id/execute
|
||||||
if (pathname.match(/^\/api\/orchestrator\/flows\/[^/]+\/execute$/) && req.method === 'POST') {
|
if (pathname.match(/^\/api\/orchestrator\/flows\/[^/]+\/execute$/) && req.method === 'POST') {
|
||||||
@@ -1370,6 +1420,7 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
|||||||
flowId: flowId,
|
flowId: flowId,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
startedAt: now,
|
startedAt: now,
|
||||||
|
sessionKey: sessionKey,
|
||||||
variables: { ...flow.variables, ...inputVariables },
|
variables: { ...flow.variables, ...inputVariables },
|
||||||
nodeStates,
|
nodeStates,
|
||||||
logs: [{
|
logs: [{
|
||||||
@@ -1393,6 +1444,7 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
|||||||
flowId: flowId,
|
flowId: flowId,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
stepName: flow.name,
|
stepName: flow.name,
|
||||||
|
totalSteps: flow.nodes.length,
|
||||||
timestamp: now
|
timestamp: now
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1484,6 +1536,7 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
|||||||
|
|
||||||
await writeExecutionStorage(workflowDir, execution);
|
await writeExecutionStorage(workflowDir, execution);
|
||||||
broadcastExecutionStateUpdate(execution);
|
broadcastExecutionStateUpdate(execution);
|
||||||
|
broadcastExecutionStatusMessage(execution);
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({
|
res.end(JSON.stringify({
|
||||||
@@ -1567,6 +1620,7 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
|||||||
|
|
||||||
await writeExecutionStorage(workflowDir, execution);
|
await writeExecutionStorage(workflowDir, execution);
|
||||||
broadcastExecutionStateUpdate(execution);
|
broadcastExecutionStateUpdate(execution);
|
||||||
|
broadcastExecutionStatusMessage(execution);
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({
|
res.end(JSON.stringify({
|
||||||
@@ -1652,6 +1706,18 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
|||||||
|
|
||||||
await writeExecutionStorage(workflowDir, execution);
|
await writeExecutionStorage(workflowDir, execution);
|
||||||
broadcastExecutionStateUpdate(execution);
|
broadcastExecutionStateUpdate(execution);
|
||||||
|
broadcastExecutionStatusMessage(execution, execution.sessionKey);
|
||||||
|
|
||||||
|
// Broadcast EXECUTION_STOPPED for frontend executionMonitorStore
|
||||||
|
broadcastToClients({
|
||||||
|
type: 'EXECUTION_STOPPED',
|
||||||
|
payload: {
|
||||||
|
executionId: execution.id,
|
||||||
|
flowId: execution.flowId,
|
||||||
|
reason: 'User requested stop',
|
||||||
|
timestamp: now,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||||
res.end(JSON.stringify({
|
res.end(JSON.stringify({
|
||||||
|
|||||||
Reference in New Issue
Block a user