mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat(orchestrator): redesign orchestrator page as template editor with terminal execution
Phase 1: Orchestrator Simplification - Remove ExecutionMonitor from OrchestratorPage - Replace "Run Workflow" button with "Send to Terminal" button - Update i18n texts for template editor context Phase 2: Session Lock Mechanism - Add 'locked' status to TerminalStatus type - Extend TerminalMeta with isLocked, lockReason, lockedByExecutionId, lockedAt - Implement lockSession/unlockSession in sessionManagerStore - Create SessionLockConfirmDialog component for input interception Phase 3: Execution Monitor Panel - Create executionMonitorStore for execution state management - Create ExecutionMonitorPanel component with step progress display - Add execution panel to DashboardToolbar and TerminalDashboardPage - Support WebSocket message handling for execution updates Phase 4: Execution Bridge - Add POST /api/orchestrator/flows/:id/execute-in-session endpoint - Create useExecuteFlowInSession hook for frontend API calls - Broadcast EXECUTION_STARTED and CLI_SESSION_LOCKED WebSocket messages - Lock session when execution starts, unlock on completion
This commit is contained in:
@@ -11,12 +11,13 @@
|
||||
* - POST /api/orchestrator/flows/:id/duplicate - Duplicate flow
|
||||
*
|
||||
* Execution Control Endpoints:
|
||||
* - POST /api/orchestrator/flows/:id/execute - Start flow execution
|
||||
* - POST /api/orchestrator/executions/:execId/pause - Pause execution
|
||||
* - POST /api/orchestrator/executions/:execId/resume - Resume execution
|
||||
* - POST /api/orchestrator/executions/:execId/stop - Stop execution
|
||||
* - GET /api/orchestrator/executions/:execId - Get execution state
|
||||
* - GET /api/orchestrator/executions/:execId/logs - Get execution logs
|
||||
* - POST /api/orchestrator/flows/:id/execute - Start flow execution
|
||||
* - POST /api/orchestrator/flows/:id/execute-in-session - Start flow execution in PTY session
|
||||
* - POST /api/orchestrator/executions/:execId/pause - Pause execution
|
||||
* - POST /api/orchestrator/executions/:execId/resume - Resume execution
|
||||
* - POST /api/orchestrator/executions/:execId/stop - Stop execution
|
||||
* - GET /api/orchestrator/executions/:execId - Get execution state
|
||||
* - GET /api/orchestrator/executions/:execId/logs - Get execution logs
|
||||
*
|
||||
* Template Management Endpoints:
|
||||
* - GET /api/orchestrator/templates - List local + builtin templates
|
||||
@@ -1277,6 +1278,134 @@ export async function handleOrchestratorRoutes(ctx: RouteContext): Promise<boole
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==== EXECUTE FLOW IN SESSION ====
|
||||
// POST /api/orchestrator/flows/:id/execute-in-session
|
||||
if (pathname.match(/^\/api\/orchestrator\/flows\/[^/]+\/execute-in-session$/) && req.method === 'POST') {
|
||||
const flowId = pathname.split('/').slice(-2)[0];
|
||||
if (!flowId || !isValidFlowId(flowId)) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ success: false, error: 'Invalid flow ID format' }));
|
||||
return true;
|
||||
}
|
||||
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
const {
|
||||
sessionConfig,
|
||||
sessionKey: existingSessionKey,
|
||||
variables: inputVariables,
|
||||
stepTimeout,
|
||||
errorStrategy = 'pause'
|
||||
} = body as {
|
||||
sessionConfig?: {
|
||||
tool?: string;
|
||||
model?: string;
|
||||
preferredShell?: string;
|
||||
};
|
||||
sessionKey?: string;
|
||||
variables?: Record<string, unknown>;
|
||||
stepTimeout?: number;
|
||||
errorStrategy?: 'pause' | 'skip' | 'stop';
|
||||
};
|
||||
|
||||
try {
|
||||
// Verify flow exists
|
||||
const flow = await readFlowStorage(workflowDir, flowId);
|
||||
if (!flow) {
|
||||
return { success: false, error: 'Flow not found', status: 404 };
|
||||
}
|
||||
|
||||
// Generate execution ID
|
||||
const execId = generateExecutionId();
|
||||
const now = new Date().toISOString();
|
||||
|
||||
// Determine session key
|
||||
let sessionKey = existingSessionKey;
|
||||
if (!sessionKey) {
|
||||
// Create new session if not provided
|
||||
// This would typically call the session manager
|
||||
sessionKey = `cli-session-${Date.now()}-${randomBytes(4).toString('hex')}`;
|
||||
}
|
||||
|
||||
// Create execution state
|
||||
const nodeStates: Record<string, NodeExecutionState> = {};
|
||||
for (const node of flow.nodes) {
|
||||
nodeStates[node.id] = {
|
||||
status: 'pending'
|
||||
};
|
||||
}
|
||||
|
||||
const execution: ExecutionState = {
|
||||
id: execId,
|
||||
flowId: flowId,
|
||||
status: 'pending',
|
||||
startedAt: now,
|
||||
variables: { ...flow.variables, ...inputVariables },
|
||||
nodeStates,
|
||||
logs: [{
|
||||
timestamp: now,
|
||||
level: 'info',
|
||||
message: `Execution started in session: ${sessionKey}`
|
||||
}]
|
||||
};
|
||||
|
||||
// Save execution state
|
||||
await writeExecutionStorage(workflowDir, execution);
|
||||
|
||||
// Broadcast execution created
|
||||
broadcastExecutionStateUpdate(execution);
|
||||
|
||||
// Broadcast EXECUTION_STARTED to WebSocket clients
|
||||
if (wsBroadcast) {
|
||||
wsBroadcast({
|
||||
type: 'EXECUTION_STARTED',
|
||||
payload: {
|
||||
executionId: execId,
|
||||
flowId: flowId,
|
||||
sessionKey: sessionKey,
|
||||
stepName: flow.name,
|
||||
timestamp: now
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Lock the session (via WebSocket broadcast for frontend to handle)
|
||||
if (wsBroadcast) {
|
||||
wsBroadcast({
|
||||
type: 'CLI_SESSION_LOCKED',
|
||||
payload: {
|
||||
sessionKey: sessionKey,
|
||||
reason: `Executing workflow: ${flow.name}`,
|
||||
executionId: execId,
|
||||
timestamp: now
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: Implement actual step-by-step execution in PTY session
|
||||
// For now, mark as running and let the frontend handle the orchestration
|
||||
execution.status = 'running';
|
||||
await writeExecutionStorage(workflowDir, execution);
|
||||
broadcastExecutionStateUpdate(execution);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
executionId: execution.id,
|
||||
flowId: execution.flowId,
|
||||
sessionKey: sessionKey,
|
||||
status: execution.status,
|
||||
totalSteps: flow.nodes.length,
|
||||
startedAt: execution.startedAt
|
||||
},
|
||||
message: 'Execution started in session'
|
||||
};
|
||||
} catch (error) {
|
||||
return { success: false, error: (error as Error).message, status: 500 };
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
// ==== PAUSE EXECUTION ====
|
||||
// POST /api/orchestrator/executions/:execId/pause
|
||||
if (pathname.match(/^\/api\/orchestrator\/executions\/[^/]+\/pause$/) && req.method === 'POST') {
|
||||
|
||||
Reference in New Issue
Block a user