mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-15 02:42:45 +08:00
feat: add Terminal Dashboard components and state management
- Implement TerminalTabBar for session tab management with status indicators and alert badges. - Create TerminalWorkbench to combine TerminalTabBar and TerminalInstance for terminal session display. - Add localization support for terminal dashboard in English and Chinese. - Develop TerminalDashboardPage for the main layout of the terminal dashboard with a three-column structure. - Introduce Zustand stores for session management and issue/queue integration, handling session groups, terminal metadata, and alert management. - Create a monitor web worker for off-main-thread output analysis, detecting errors and stalls in terminal sessions. - Define TypeScript types for terminal dashboard state management and integration.
This commit is contained in:
154
ccw/frontend/src/workers/monitor.worker.ts
Normal file
154
ccw/frontend/src/workers/monitor.worker.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
// ========================================
|
||||
// Monitor Web Worker
|
||||
// ========================================
|
||||
// Off-main-thread rule-based output analysis for terminal sessions.
|
||||
// MVP rules:
|
||||
// 1. Keyword matching: /error|failed|exception/i -> critical alert
|
||||
// 2. Stall detection: no output for > 60s -> warning alert
|
||||
//
|
||||
// Message protocol:
|
||||
// IN: { type: 'output', sessionId: string, text: string }
|
||||
// IN: { type: 'reset', sessionId: string } -- reset session tracking
|
||||
// OUT: { type: 'alert', sessionId: string, severity: string, message: string }
|
||||
|
||||
// ========== Types ==========
|
||||
|
||||
interface OutputMessage {
|
||||
type: 'output';
|
||||
sessionId: string;
|
||||
text: string;
|
||||
}
|
||||
|
||||
interface ResetMessage {
|
||||
type: 'reset';
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
type IncomingMessage = OutputMessage | ResetMessage;
|
||||
|
||||
interface AlertMessage {
|
||||
type: 'alert';
|
||||
sessionId: string;
|
||||
severity: 'critical' | 'warning';
|
||||
message: string;
|
||||
}
|
||||
|
||||
interface KeywordRule {
|
||||
pattern: RegExp;
|
||||
severity: 'critical' | 'warning';
|
||||
label: string;
|
||||
}
|
||||
|
||||
interface SessionState {
|
||||
lastActivity: number;
|
||||
alertCount: number;
|
||||
/** Track stall alert to avoid repeated notifications per stall period */
|
||||
stallAlerted: boolean;
|
||||
}
|
||||
|
||||
// ========== Rules ==========
|
||||
|
||||
const KEYWORD_RULES: KeywordRule[] = [
|
||||
{
|
||||
pattern: /error|failed|exception/i,
|
||||
severity: 'critical',
|
||||
label: 'error keyword',
|
||||
},
|
||||
];
|
||||
|
||||
/** Stall threshold in milliseconds (60 seconds) */
|
||||
const STALL_THRESHOLD_MS = 60_000;
|
||||
|
||||
/** Stall check interval in milliseconds (15 seconds) */
|
||||
const STALL_CHECK_INTERVAL_MS = 15_000;
|
||||
|
||||
// ========== State ==========
|
||||
|
||||
const sessions = new Map<string, SessionState>();
|
||||
|
||||
// ========== Helpers ==========
|
||||
|
||||
function getOrCreateSession(sessionId: string): SessionState {
|
||||
let state = sessions.get(sessionId);
|
||||
if (!state) {
|
||||
state = {
|
||||
lastActivity: Date.now(),
|
||||
alertCount: 0,
|
||||
stallAlerted: false,
|
||||
};
|
||||
sessions.set(sessionId, state);
|
||||
}
|
||||
return state;
|
||||
}
|
||||
|
||||
function postAlert(alert: AlertMessage): void {
|
||||
self.postMessage(alert);
|
||||
}
|
||||
|
||||
// ========== Output Processing ==========
|
||||
|
||||
function processOutput(sessionId: string, text: string): void {
|
||||
const state = getOrCreateSession(sessionId);
|
||||
state.lastActivity = Date.now();
|
||||
// Reset stall alert flag on new output
|
||||
state.stallAlerted = false;
|
||||
|
||||
// Run keyword rules against text
|
||||
for (const rule of KEYWORD_RULES) {
|
||||
if (rule.pattern.test(text)) {
|
||||
state.alertCount++;
|
||||
postAlert({
|
||||
type: 'alert',
|
||||
sessionId,
|
||||
severity: rule.severity,
|
||||
message: `Detected ${rule.label} in output`,
|
||||
});
|
||||
// Only report first matching rule per chunk to avoid alert flood
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Stall Detection ==========
|
||||
|
||||
function checkStalls(): void {
|
||||
const now = Date.now();
|
||||
sessions.forEach((state, sessionId) => {
|
||||
if (state.stallAlerted) return;
|
||||
const elapsed = now - state.lastActivity;
|
||||
if (elapsed > STALL_THRESHOLD_MS) {
|
||||
state.stallAlerted = true;
|
||||
state.alertCount++;
|
||||
const seconds = Math.floor(elapsed / 1000);
|
||||
postAlert({
|
||||
type: 'alert',
|
||||
sessionId,
|
||||
severity: 'warning',
|
||||
message: `Session stalled: no output for ${seconds}s`,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ========== Message Handler ==========
|
||||
|
||||
self.onmessage = (event: MessageEvent<IncomingMessage>) => {
|
||||
const msg = event.data;
|
||||
switch (msg.type) {
|
||||
case 'output':
|
||||
processOutput(msg.sessionId, msg.text);
|
||||
break;
|
||||
case 'reset':
|
||||
sessions.delete(msg.sessionId);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
// ========== Periodic Stall Check ==========
|
||||
|
||||
const _stallInterval = setInterval(checkStalls, STALL_CHECK_INTERVAL_MS);
|
||||
|
||||
// Cleanup on worker termination (best-effort)
|
||||
self.addEventListener('close', () => {
|
||||
clearInterval(_stallInterval);
|
||||
});
|
||||
Reference in New Issue
Block a user