mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-03 15:43:11 +08:00
Add comprehensive command and skill reference documentation in Chinese
- Created a new document for command and skill references, detailing orchestrator commands, workflow session commands, issue workflow commands, IDAW commands, with-file workflows, cycle workflows, CLI commands, memory commands, team skills, workflow skills, utility skills, and Codex capabilities. - Added a comparison table for workflows, outlining their best uses, levels, self-containment, and automatic chaining behavior.
This commit is contained in:
@@ -1093,7 +1093,7 @@ export async function handleCliRoutes(ctx: RouteContext): Promise<boolean> {
|
||||
return { error: 'Execution not found', status: 404 };
|
||||
}
|
||||
|
||||
const review = historyStore.saveReview({
|
||||
const review = await historyStore.saveReview({
|
||||
execution_id: executionId,
|
||||
status,
|
||||
rating,
|
||||
|
||||
@@ -589,7 +589,7 @@ Return ONLY valid JSON in this exact format (no markdown, no code blocks, just p
|
||||
const storeModule = await import('../../tools/cli-history-store.js');
|
||||
const store = storeModule.getHistoryStore(projectPath);
|
||||
const insightId = `insight-${Date.now()}`;
|
||||
store.saveInsight({
|
||||
await store.saveInsight({
|
||||
id: insightId,
|
||||
tool,
|
||||
promptCount: prompts.length,
|
||||
|
||||
@@ -24,6 +24,212 @@ export const wsClients = new Set<Duplex>();
|
||||
// Track message counts per client for rate limiting
|
||||
export const wsClientMessageCounts = new Map<Duplex, { count: number; resetTime: number }>();
|
||||
|
||||
/**
|
||||
* Universal broadcast throttling system
|
||||
* Reduces WebSocket traffic by deduplicating and rate-limiting broadcast messages
|
||||
*/
|
||||
|
||||
interface ThrottleEntry {
|
||||
lastSend: number;
|
||||
pendingData: unknown;
|
||||
}
|
||||
|
||||
type ThrottleCategory = 'state_update' | 'memory_cpu' | 'log_output' | 'immediate';
|
||||
|
||||
/** Map of message type to throttle configuration */
|
||||
const THROTTLE_CONFIG = new Map<string, { interval: number; category: ThrottleCategory }>(
|
||||
[
|
||||
// State updates - high frequency, low value when duplicated
|
||||
['LOOP_STATE_UPDATE', { interval: 1000, category: 'state_update' }],
|
||||
['ORCHESTRATOR_STATE_UPDATE', { interval: 1000, category: 'state_update' }],
|
||||
['COORDINATOR_STATE_UPDATE', { interval: 1000, category: 'state_update' }],
|
||||
['QUEUE_SCHEDULER_STATE_UPDATE', { interval: 1000, category: 'state_update' }],
|
||||
|
||||
// Memory/CPU updates - medium frequency
|
||||
['LOOP_STEP_COMPLETED', { interval: 500, category: 'memory_cpu' }],
|
||||
['ORCHESTRATOR_NODE_COMPLETED', { interval: 500, category: 'memory_cpu' }],
|
||||
['COORDINATOR_COMMAND_COMPLETED', { interval: 500, category: 'memory_cpu' }],
|
||||
['QUEUE_ITEM_UPDATED', { interval: 500, category: 'memory_cpu' }],
|
||||
|
||||
// Log/output - higher frequency allowed for real-time streaming
|
||||
['LOOP_LOG_ENTRY', { interval: 200, category: 'log_output' }],
|
||||
['ORCHESTRATOR_LOG', { interval: 200, category: 'log_output' }],
|
||||
['COORDINATOR_LOG_ENTRY', { interval: 200, category: 'log_output' }],
|
||||
|
||||
// Item added/removed - send immediately
|
||||
['QUEUE_ITEM_ADDED', { interval: 0, category: 'immediate' }],
|
||||
['QUEUE_ITEM_REMOVED', { interval: 0, category: 'immediate' }],
|
||||
['QUEUE_SCHEDULER_CONFIG_UPDATED', { interval: 0, category: 'immediate' }],
|
||||
['ORCHESTRATOR_NODE_STARTED', { interval: 0, category: 'immediate' }],
|
||||
['ORCHESTRATOR_NODE_FAILED', { interval: 0, category: 'immediate' }],
|
||||
['COORDINATOR_COMMAND_STARTED', { interval: 0, category: 'immediate' }],
|
||||
['COORDINATOR_COMMAND_FAILED', { interval: 0, category: 'immediate' }],
|
||||
['COORDINATOR_QUESTION_ASKED', { interval: 0, category: 'immediate' }],
|
||||
['COORDINATOR_ANSWER_RECEIVED', { interval: 0, category: 'immediate' }],
|
||||
['LOOP_COMPLETED', { interval: 0, category: 'immediate' }],
|
||||
['LOOP_COMPLETED' as any, { interval: 0, category: 'immediate' }],
|
||||
].filter(([key]) => key !== 'LOOP_COMPLETED' as any)
|
||||
);
|
||||
|
||||
// Add LOOP_COMPLETED separately to avoid type issues
|
||||
THROTTLE_CONFIG.set('LOOP_COMPLETED', { interval: 0, category: 'immediate' });
|
||||
|
||||
/** Per-message-type throttle tracking */
|
||||
const throttleState = new Map<string, ThrottleEntry>();
|
||||
|
||||
/** Metrics for broadcast optimization */
|
||||
export const broadcastMetrics = {
|
||||
sent: 0,
|
||||
throttled: 0,
|
||||
deduped: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* Get throttle configuration for a message type
|
||||
*/
|
||||
function getThrottleConfig(messageType: string): { interval: number; category: ThrottleCategory } {
|
||||
return THROTTLE_CONFIG.get(messageType) || { interval: 0, category: 'immediate' };
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize message data for comparison
|
||||
*/
|
||||
function serializeMessage(data: unknown): string {
|
||||
if (typeof data === 'string') return data;
|
||||
if (typeof data === 'object' && data !== null) {
|
||||
return JSON.stringify(data, Object.keys(data).sort());
|
||||
}
|
||||
return String(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create WebSocket frame
|
||||
*/
|
||||
export function createWebSocketFrame(data: unknown): Buffer {
|
||||
const payload = Buffer.from(JSON.stringify(data), 'utf8');
|
||||
const length = payload.length;
|
||||
|
||||
let frame;
|
||||
if (length <= 125) {
|
||||
frame = Buffer.alloc(2 + length);
|
||||
frame[0] = 0x81; // Text frame, FIN
|
||||
frame[1] = length;
|
||||
payload.copy(frame, 2);
|
||||
} else if (length <= 65535) {
|
||||
frame = Buffer.alloc(4 + length);
|
||||
frame[0] = 0x81;
|
||||
frame[1] = 126;
|
||||
frame.writeUInt16BE(length, 2);
|
||||
payload.copy(frame, 4);
|
||||
} else {
|
||||
frame = Buffer.alloc(10 + length);
|
||||
frame[0] = 0x81;
|
||||
frame[1] = 127;
|
||||
frame.writeBigUInt64BE(BigInt(length), 2);
|
||||
payload.copy(frame, 10);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all connected WebSocket clients with universal throttling
|
||||
* - Deduplicates identical messages within throttle window
|
||||
* - Rate-limits by message type with adaptive intervals
|
||||
* - Preserves message ordering within each type
|
||||
*/
|
||||
export function broadcastToClients(data: unknown): void {
|
||||
const eventType =
|
||||
typeof data === 'object' && data !== null && 'type' in data ? (data as { type?: unknown }).type : undefined;
|
||||
|
||||
if (!eventType || typeof eventType !== 'string') {
|
||||
// Unknown message type - send immediately
|
||||
const frame = createWebSocketFrame(data);
|
||||
for (const client of wsClients) {
|
||||
try {
|
||||
client.write(frame);
|
||||
} catch (e) {
|
||||
wsClients.delete(client);
|
||||
}
|
||||
}
|
||||
console.log(`[WS] Broadcast to ${wsClients.size} clients: unknown type`);
|
||||
return;
|
||||
}
|
||||
|
||||
const config = getThrottleConfig(eventType);
|
||||
const now = Date.now();
|
||||
const state = throttleState.get(eventType);
|
||||
|
||||
if (config.interval === 0) {
|
||||
// Immediate - send without throttling
|
||||
const frame = createWebSocketFrame(data);
|
||||
for (const client of wsClients) {
|
||||
try {
|
||||
client.write(frame);
|
||||
} catch (e) {
|
||||
wsClients.delete(client);
|
||||
}
|
||||
}
|
||||
broadcastMetrics.sent++;
|
||||
throttleState.set(eventType, { lastSend: now, pendingData: data });
|
||||
console.log(`[WS] Broadcast to ${wsClients.size} clients: ${eventType} (immediate)`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we should throttle
|
||||
const currentDataHash = serializeMessage(data);
|
||||
|
||||
if (state) {
|
||||
const timeSinceLastSend = now - state.lastSend;
|
||||
|
||||
// Check for duplicate data
|
||||
if (timeSinceLastSend < config.interval) {
|
||||
const pendingDataHash = serializeMessage(state.pendingData);
|
||||
if (currentDataHash === pendingDataHash) {
|
||||
// Duplicate message - drop it
|
||||
broadcastMetrics.deduped++;
|
||||
console.log(`[WS] Throttled duplicate ${eventType} (${timeSinceLastSend}ms since last)`);
|
||||
return;
|
||||
}
|
||||
// Different data but within throttle window - update pending
|
||||
throttleState.set(eventType, { lastSend: state.lastSend, pendingData: data });
|
||||
broadcastMetrics.throttled++;
|
||||
console.log(`[WS] Throttled ${eventType} (${timeSinceLastSend}ms since last, pending updated)`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Send the message
|
||||
const frame = createWebSocketFrame(data);
|
||||
for (const client of wsClients) {
|
||||
try {
|
||||
client.write(frame);
|
||||
} catch (e) {
|
||||
wsClients.delete(client);
|
||||
}
|
||||
}
|
||||
|
||||
broadcastMetrics.sent++;
|
||||
throttleState.set(eventType, { lastSend: now, pendingData: data });
|
||||
console.log(`[WS] Broadcast to ${wsClients.size} clients: ${eventType}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get broadcast throttling metrics
|
||||
*/
|
||||
export function getBroadcastMetrics(): Readonly<typeof broadcastMetrics> {
|
||||
return { ...broadcastMetrics };
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset broadcast throttling metrics (for testing/monitoring)
|
||||
*/
|
||||
export function resetBroadcastMetrics(): void {
|
||||
broadcastMetrics.sent = 0;
|
||||
broadcastMetrics.throttled = 0;
|
||||
broadcastMetrics.deduped = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a new WebSocket connection should be accepted
|
||||
* Returns true if connection allowed, false if limit reached
|
||||
@@ -387,25 +593,6 @@ export function createWebSocketFrame(data: unknown): Buffer {
|
||||
return frame;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all connected WebSocket clients
|
||||
*/
|
||||
export function broadcastToClients(data: unknown): void {
|
||||
const frame = createWebSocketFrame(data);
|
||||
|
||||
for (const client of wsClients) {
|
||||
try {
|
||||
client.write(frame);
|
||||
} catch (e) {
|
||||
wsClients.delete(client);
|
||||
}
|
||||
}
|
||||
|
||||
const eventType =
|
||||
typeof data === 'object' && data !== null && 'type' in data ? (data as { type?: unknown }).type : undefined;
|
||||
console.log(`[WS] Broadcast to ${wsClients.size} clients:`, eventType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract session ID from file path
|
||||
*/
|
||||
@@ -435,12 +622,9 @@ export function extractSessionIdFromPath(filePath: string): string | null {
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop-specific broadcast with throttling
|
||||
* Throttles LOOP_STATE_UPDATE messages to avoid flooding clients
|
||||
* Loop broadcast types (without timestamp - added automatically)
|
||||
* Throttling is handled universally in broadcastToClients
|
||||
*/
|
||||
let lastLoopBroadcast = 0;
|
||||
const LOOP_BROADCAST_THROTTLE = 1000; // 1 second
|
||||
|
||||
export type LoopMessage =
|
||||
| Omit<LoopStateUpdateMessage, 'timestamp'>
|
||||
| Omit<LoopStepCompletedMessage, 'timestamp'>
|
||||
@@ -448,18 +632,10 @@ export type LoopMessage =
|
||||
| Omit<LoopLogEntryMessage, 'timestamp'>;
|
||||
|
||||
/**
|
||||
* Broadcast loop state update with throttling
|
||||
* Broadcast loop update with automatic throttling
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
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()
|
||||
@@ -467,8 +643,8 @@ export function broadcastLoopUpdate(message: LoopMessage): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast loop log entry (no throttling)
|
||||
* Used for streaming real-time logs to Dashboard
|
||||
* Broadcast loop log entry
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
export function broadcastLoopLog(loop_id: string, step_id: string, line: string): void {
|
||||
broadcastToClients({
|
||||
@@ -482,6 +658,7 @@ export function broadcastLoopLog(loop_id: string, step_id: string, line: string)
|
||||
|
||||
/**
|
||||
* Union type for Orchestrator messages (without timestamp - added automatically)
|
||||
* Throttling is handled universally in broadcastToClients
|
||||
*/
|
||||
export type OrchestratorMessage =
|
||||
| Omit<OrchestratorStateUpdateMessage, 'timestamp'>
|
||||
@@ -491,29 +668,10 @@ export type OrchestratorMessage =
|
||||
| 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
|
||||
* Broadcast orchestrator update with automatic throttling
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
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()
|
||||
@@ -521,8 +679,8 @@ export function broadcastOrchestratorUpdate(message: OrchestratorMessage): void
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast orchestrator log entry (no throttling)
|
||||
* Used for streaming real-time execution logs to Dashboard
|
||||
* Broadcast orchestrator log entry
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
export function broadcastOrchestratorLog(execId: string, log: Omit<ExecutionLog, 'timestamp'>): void {
|
||||
broadcastToClients({
|
||||
@@ -639,6 +797,7 @@ export interface CoordinatorAnswerReceivedMessage {
|
||||
|
||||
/**
|
||||
* Union type for Coordinator messages (without timestamp - added automatically)
|
||||
* Throttling is handled universally in broadcastToClients
|
||||
*/
|
||||
export type CoordinatorMessage =
|
||||
| Omit<CoordinatorStateUpdateMessage, 'timestamp'>
|
||||
@@ -650,29 +809,10 @@ export type CoordinatorMessage =
|
||||
| Omit<CoordinatorAnswerReceivedMessage, 'timestamp'>;
|
||||
|
||||
/**
|
||||
* Coordinator-specific broadcast with throttling
|
||||
* Throttles COORDINATOR_STATE_UPDATE messages to avoid flooding clients
|
||||
*/
|
||||
let lastCoordinatorBroadcast = 0;
|
||||
const COORDINATOR_BROADCAST_THROTTLE = 1000; // 1 second
|
||||
|
||||
/**
|
||||
* Broadcast coordinator update with throttling
|
||||
* STATE_UPDATE messages are throttled to 1 per second
|
||||
* Other message types are sent immediately
|
||||
* Broadcast coordinator update with automatic throttling
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
export function broadcastCoordinatorUpdate(message: CoordinatorMessage): void {
|
||||
const now = Date.now();
|
||||
|
||||
// Throttle COORDINATOR_STATE_UPDATE to reduce WebSocket traffic
|
||||
if (message.type === 'COORDINATOR_STATE_UPDATE' && now - lastCoordinatorBroadcast < COORDINATOR_BROADCAST_THROTTLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'COORDINATOR_STATE_UPDATE') {
|
||||
lastCoordinatorBroadcast = now;
|
||||
}
|
||||
|
||||
broadcastToClients({
|
||||
...message,
|
||||
timestamp: new Date().toISOString()
|
||||
@@ -680,8 +820,8 @@ export function broadcastCoordinatorUpdate(message: CoordinatorMessage): void {
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast coordinator log entry (no throttling)
|
||||
* Used for streaming real-time coordinator logs to Dashboard
|
||||
* Broadcast coordinator log entry
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
export function broadcastCoordinatorLog(
|
||||
executionId: string,
|
||||
@@ -715,6 +855,7 @@ export type {
|
||||
|
||||
/**
|
||||
* Union type for Queue messages (without timestamp - added automatically)
|
||||
* Throttling is handled universally in broadcastToClients
|
||||
*/
|
||||
export type QueueMessage =
|
||||
| Omit<QueueSchedulerStateUpdateMessage, 'timestamp'>
|
||||
@@ -724,29 +865,10 @@ export type QueueMessage =
|
||||
| Omit<QueueSchedulerConfigUpdatedMessage, 'timestamp'>;
|
||||
|
||||
/**
|
||||
* Queue-specific broadcast with throttling
|
||||
* Throttles QUEUE_SCHEDULER_STATE_UPDATE messages to avoid flooding clients
|
||||
*/
|
||||
let lastQueueBroadcast = 0;
|
||||
const QUEUE_BROADCAST_THROTTLE = 1000; // 1 second
|
||||
|
||||
/**
|
||||
* Broadcast queue update with throttling
|
||||
* STATE_UPDATE messages are throttled to 1 per second
|
||||
* Other message types are sent immediately
|
||||
* Broadcast queue update with automatic throttling
|
||||
* Note: Throttling is now handled universally in broadcastToClients
|
||||
*/
|
||||
export function broadcastQueueUpdate(message: QueueMessage): void {
|
||||
const now = Date.now();
|
||||
|
||||
// Throttle QUEUE_SCHEDULER_STATE_UPDATE to reduce WebSocket traffic
|
||||
if (message.type === 'QUEUE_SCHEDULER_STATE_UPDATE' && now - lastQueueBroadcast < QUEUE_BROADCAST_THROTTLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (message.type === 'QUEUE_SCHEDULER_STATE_UPDATE') {
|
||||
lastQueueBroadcast = now;
|
||||
}
|
||||
|
||||
broadcastToClients({
|
||||
...message,
|
||||
timestamp: new Date().toISOString()
|
||||
|
||||
Reference in New Issue
Block a user