feat: add CCW Loop System for automated iterative workflow execution

Implements a complete loop execution system with multi-loop parallel support,
dashboard monitoring, and comprehensive security validation.

Core features:
- Loop orchestration engine (loop-manager, loop-state-manager)
- Multi-loop parallel execution with independent state management
- REST API endpoints for loop control (pause, resume, stop, retry)
- WebSocket real-time status updates
- Dashboard Loop Monitor view with live updates
- Security: path traversal protection and sandboxed JavaScript evaluation

Test coverage:
- 42 comprehensive tests covering multi-loop, API, WebSocket, security
- Security validation for success_condition injection attacks
- Edge case handling and end-to-end workflow tests
This commit is contained in:
catlog22
2026-01-21 22:55:24 +08:00
parent 64e064e775
commit d9f1d14d5e
28 changed files with 5912 additions and 17 deletions

View File

@@ -5,6 +5,64 @@ import type { Duplex } from 'stream';
// WebSocket clients for real-time notifications
export const wsClients = new Set<Duplex>();
/**
* WebSocket message types for Loop monitoring
*/
export type LoopMessageType =
| 'LOOP_STATE_UPDATE'
| 'LOOP_STEP_COMPLETED'
| 'LOOP_COMPLETED'
| 'LOOP_LOG_ENTRY';
/**
* Loop State Update - fired when loop status changes
*/
export interface LoopStateUpdateMessage {
type: 'LOOP_STATE_UPDATE';
loop_id: string;
status: 'created' | 'running' | 'paused' | 'completed' | 'failed';
current_iteration: number;
current_cli_step: number;
updated_at: string;
timestamp: string;
}
/**
* Loop Step Completed - fired when a CLI step finishes
*/
export interface LoopStepCompletedMessage {
type: 'LOOP_STEP_COMPLETED';
loop_id: string;
step_id: string;
exit_code: number;
duration_ms: number;
output: string;
timestamp: string;
}
/**
* Loop Completed - fired when entire loop finishes
*/
export interface LoopCompletedMessage {
type: 'LOOP_COMPLETED';
loop_id: string;
final_status: 'completed' | 'failed';
total_iterations: number;
reason?: string;
timestamp: string;
}
/**
* Loop Log Entry - fired for streaming log lines
*/
export interface LoopLogEntryMessage {
type: 'LOOP_LOG_ENTRY';
loop_id: string;
step_id: string;
line: string;
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;
@@ -196,3 +254,49 @@ export function extractSessionIdFromPath(filePath: string): string | null {
return null;
}
/**
* Loop-specific broadcast with throttling
* Throttles LOOP_STATE_UPDATE messages to avoid flooding clients
*/
let lastLoopBroadcast = 0;
const LOOP_BROADCAST_THROTTLE = 1000; // 1 second
export type LoopMessage =
| Omit<LoopStateUpdateMessage, 'timestamp'>
| Omit<LoopStepCompletedMessage, 'timestamp'>
| Omit<LoopCompletedMessage, 'timestamp'>
| Omit<LoopLogEntryMessage, 'timestamp'>;
/**
* Broadcast loop state update with throttling
*/
export function broadcastLoopUpdate(message: LoopMessage): void {
const now = Date.now();
// Throttle LOOP_STATE_UPDATE to reduce WebSocket traffic
if (message.type === 'LOOP_STATE_UPDATE' && now - lastLoopBroadcast < LOOP_BROADCAST_THROTTLE) {
return;
}
lastLoopBroadcast = now;
broadcastToClients({
...message,
timestamp: new Date().toISOString()
});
}
/**
* Broadcast loop log entry (no throttling)
* Used for streaming real-time logs to Dashboard
*/
export function broadcastLoopLog(loop_id: string, step_id: string, line: string): void {
broadcastToClients({
type: 'LOOP_LOG_ENTRY',
loop_id,
step_id,
line,
timestamp: new Date().toISOString()
});
}