mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
feat: add smart respawn integration and priority todos (Phase 2)
Smart Respawn Integration (2.5): - Check circuit breaker OPEN state before respawn (blocks cycle) - Check RALPH_STATUS EXIT_SIGNAL=true to stop respawn - Check STATUS=BLOCKED to pause respawn for human intervention - Use RECOMMENDATION from RALPH_STATUS in update prompt - Emit respawn:blocked SSE event with reason and details - Show notification when respawn is blocked - Visual blocked state with pulsing red indicator Priority-based Todos (part of 2.3): - Add RalphTodoPriority type (P0/P1/P2/null) - Parse priority from todo content (P0:, Critical:, High Priority, etc.) - Add priority field to RalphTodoItem interface - Update frontend to display priority badges with colors - Sort todos by priority (P0 > P1 > P2) then by status - Add CSS for priority badges and highlighted cards Bump version to 0.1391 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -38,7 +38,7 @@ Claudeman is a Claude Code session manager with a web interface and autonomous R
|
|||||||
|
|
||||||
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
|
When adding new features, always ask: "Will this maintain responsiveness with 20 sessions and 50 agent windows?"
|
||||||
|
|
||||||
**Version**: 0.1390 (must match `package.json`)
|
**Version**: 0.1391 (must match `package.json`)
|
||||||
|
|
||||||
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1390",
|
"version": "0.1391",
|
||||||
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
+38
-1
@@ -18,6 +18,7 @@ import {
|
|||||||
RalphTrackerState,
|
RalphTrackerState,
|
||||||
RalphTodoItem,
|
RalphTodoItem,
|
||||||
RalphTodoStatus,
|
RalphTodoStatus,
|
||||||
|
RalphTodoPriority,
|
||||||
RalphStatusBlock,
|
RalphStatusBlock,
|
||||||
RalphStatusValue,
|
RalphStatusValue,
|
||||||
RalphTestsStatus,
|
RalphTestsStatus,
|
||||||
@@ -1375,6 +1376,31 @@ export class RalphTracker extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse priority from todo content.
|
||||||
|
* Looks for patterns like: "P0:", "P1:", "P2:", "(P0)", "(P1)", "(P2)",
|
||||||
|
* "Critical:", "Blocker:", "High Priority:", etc.
|
||||||
|
*
|
||||||
|
* @param content - Todo content text
|
||||||
|
* @returns Parsed priority level or null
|
||||||
|
*/
|
||||||
|
private parsePriority(content: string): RalphTodoPriority {
|
||||||
|
const upper = content.toUpperCase();
|
||||||
|
|
||||||
|
// Direct P0/P1/P2 patterns
|
||||||
|
if (/\bP0\b|^\(P0\)|:?\s*P0\s*:|\bCRITICAL\b|\bBLOCKER\b/.test(upper)) {
|
||||||
|
return 'P0';
|
||||||
|
}
|
||||||
|
if (/\bP1\b|^\(P1\)|:?\s*P1\s*:|\bHIGH\s*PRIORITY\b/.test(upper)) {
|
||||||
|
return 'P1';
|
||||||
|
}
|
||||||
|
if (/\bP2\b|^\(P2\)|:?\s*P2\s*:|\bNICE\s*TO\s*HAVE\b|\bLOW\s*PRIORITY\b/.test(upper)) {
|
||||||
|
return 'P2';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new todo item or update an existing one.
|
* Add a new todo item or update an existing one.
|
||||||
*
|
*
|
||||||
@@ -1382,6 +1408,7 @@ export class RalphTracker extends EventEmitter {
|
|||||||
* - Content is cleaned (ANSI removed, whitespace collapsed)
|
* - Content is cleaned (ANSI removed, whitespace collapsed)
|
||||||
* - Content under 5 chars is skipped
|
* - Content under 5 chars is skipped
|
||||||
* - ID is generated from normalized content (stable hash)
|
* - ID is generated from normalized content (stable hash)
|
||||||
|
* - Priority is parsed from content (P0/P1/P2, Critical, High Priority, etc.)
|
||||||
* - Existing item: Updates status and timestamp
|
* - Existing item: Updates status and timestamp
|
||||||
* - New item: Adds to map, evicts oldest if at MAX_TODO_ITEMS
|
* - New item: Adds to map, evicts oldest if at MAX_TODO_ITEMS
|
||||||
*
|
*
|
||||||
@@ -1399,6 +1426,9 @@ export class RalphTracker extends EventEmitter {
|
|||||||
.trim();
|
.trim();
|
||||||
if (cleanContent.length < 5) return; // Skip very short content
|
if (cleanContent.length < 5) return; // Skip very short content
|
||||||
|
|
||||||
|
// Parse priority from content
|
||||||
|
const priority = this.parsePriority(cleanContent);
|
||||||
|
|
||||||
// Generate a stable ID from normalized content
|
// Generate a stable ID from normalized content
|
||||||
const id = this.generateTodoId(cleanContent);
|
const id = this.generateTodoId(cleanContent);
|
||||||
|
|
||||||
@@ -1407,6 +1437,8 @@ export class RalphTracker extends EventEmitter {
|
|||||||
// Update existing todo
|
// Update existing todo
|
||||||
existing.status = status;
|
existing.status = status;
|
||||||
existing.detectedAt = Date.now();
|
existing.detectedAt = Date.now();
|
||||||
|
// Update priority if parsed (don't overwrite with null)
|
||||||
|
if (priority) existing.priority = priority;
|
||||||
} else {
|
} else {
|
||||||
// Add new todo
|
// Add new todo
|
||||||
if (this._todos.size >= MAX_TODO_ITEMS) {
|
if (this._todos.size >= MAX_TODO_ITEMS) {
|
||||||
@@ -1422,6 +1454,7 @@ export class RalphTracker extends EventEmitter {
|
|||||||
content: cleanContent,
|
content: cleanContent,
|
||||||
status,
|
status,
|
||||||
detectedAt: Date.now(),
|
detectedAt: Date.now(),
|
||||||
|
priority,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1699,7 +1732,11 @@ export class RalphTracker extends EventEmitter {
|
|||||||
};
|
};
|
||||||
this._todos.clear();
|
this._todos.clear();
|
||||||
for (const todo of todos) {
|
for (const todo of todos) {
|
||||||
this._todos.set(todo.id, { ...todo });
|
// Backwards compatibility: ensure priority field exists
|
||||||
|
this._todos.set(todo.id, {
|
||||||
|
...todo,
|
||||||
|
priority: todo.priority ?? null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2315,6 +2315,38 @@ export class RespawnController extends EventEmitter {
|
|||||||
`tokensStable=${status.tokensStable}, ` +
|
`tokensStable=${status.tokensStable}, ` +
|
||||||
`noWorking=${status.workingPatternsAbsent}`);
|
`noWorking=${status.workingPatternsAbsent}`);
|
||||||
|
|
||||||
|
// ========== RALPH_STATUS Integration ==========
|
||||||
|
// Check circuit breaker status - if OPEN, pause respawn
|
||||||
|
const circuitBreaker = this.session.ralphTracker.circuitBreakerStatus;
|
||||||
|
if (circuitBreaker.state === 'OPEN') {
|
||||||
|
this.log(`Respawn blocked - Circuit breaker OPEN: ${circuitBreaker.reason}`);
|
||||||
|
this.logAction('ralph', `Circuit breaker OPEN: ${circuitBreaker.reason}`);
|
||||||
|
this.emit('respawnBlocked', { reason: 'circuit_breaker_open', details: circuitBreaker.reason });
|
||||||
|
this.setState('watching');
|
||||||
|
// Don't restart timers - wait for manual reset or circuit breaker resolution
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check RALPH_STATUS EXIT_SIGNAL - if true, loop is complete
|
||||||
|
const statusBlock = this.session.ralphTracker.lastStatusBlock;
|
||||||
|
if (statusBlock?.exitSignal) {
|
||||||
|
this.log(`Respawn paused - RALPH_STATUS EXIT_SIGNAL=true`);
|
||||||
|
this.logAction('ralph', `Exit signal detected: ${statusBlock.recommendation || 'Task complete'}`);
|
||||||
|
this.emit('respawnBlocked', { reason: 'exit_signal', details: statusBlock.recommendation || 'Task complete' });
|
||||||
|
this.setState('watching');
|
||||||
|
// Don't restart timers - loop is complete
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if STATUS=BLOCKED - trigger circuit breaker
|
||||||
|
if (statusBlock?.status === 'BLOCKED') {
|
||||||
|
this.log(`Respawn blocked - RALPH_STATUS reports BLOCKED`);
|
||||||
|
this.logAction('ralph', `Claude reported BLOCKED: ${statusBlock.recommendation || 'Needs human intervention'}`);
|
||||||
|
this.emit('respawnBlocked', { reason: 'status_blocked', details: statusBlock.recommendation || 'Needs human intervention' });
|
||||||
|
this.setState('watching');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Reset detection state
|
// Reset detection state
|
||||||
this.completionMessageTime = null;
|
this.completionMessageTime = null;
|
||||||
this.cancelCompletionConfirm();
|
this.cancelCompletionConfirm();
|
||||||
@@ -2344,6 +2376,7 @@ export class RespawnController extends EventEmitter {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Send the update docs prompt (first step of cycle).
|
* Send the update docs prompt (first step of cycle).
|
||||||
|
* Uses RALPH_STATUS RECOMMENDATION if available, otherwise falls back to configured prompt.
|
||||||
* @fires stepSent - With step 'update'
|
* @fires stepSent - With step 'update'
|
||||||
*/
|
*/
|
||||||
private sendUpdateDocs(): void {
|
private sendUpdateDocs(): void {
|
||||||
@@ -2356,10 +2389,21 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.config.interStepDelayMs,
|
this.config.interStepDelayMs,
|
||||||
() => {
|
() => {
|
||||||
this.stepTimer = null;
|
this.stepTimer = null;
|
||||||
const input = this.config.updatePrompt + '\r'; // \r triggers Enter in Ink/Claude CLI
|
|
||||||
this.logAction('command', `Sending: "${this.config.updatePrompt.substring(0, 50)}..."`);
|
// Use RALPH_STATUS RECOMMENDATION if available, otherwise fall back to config
|
||||||
|
const statusBlock = this.session.ralphTracker.lastStatusBlock;
|
||||||
|
let updatePrompt = this.config.updatePrompt;
|
||||||
|
|
||||||
|
if (statusBlock?.recommendation) {
|
||||||
|
// Append RECOMMENDATION to the update prompt for context
|
||||||
|
updatePrompt = `${this.config.updatePrompt}\n\nClaude's last recommendation: ${statusBlock.recommendation}`;
|
||||||
|
this.logAction('ralph', `Using RECOMMENDATION: ${statusBlock.recommendation.substring(0, 50)}...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const input = updatePrompt + '\r'; // \r triggers Enter in Ink/Claude CLI
|
||||||
|
this.logAction('command', `Sending: "${updatePrompt.substring(0, 50)}..."`);
|
||||||
this.session.writeViaScreen(input);
|
this.session.writeViaScreen(input);
|
||||||
this.emit('stepSent', 'update', this.config.updatePrompt);
|
this.emit('stepSent', 'update', updatePrompt);
|
||||||
this.setState('waiting_update');
|
this.setState('waiting_update');
|
||||||
this.promptDetected = false;
|
this.promptDetected = false;
|
||||||
this.workingDetected = false;
|
this.workingDetected = false;
|
||||||
|
|||||||
+8
-8
@@ -718,6 +718,12 @@ export interface RalphTrackerState {
|
|||||||
elapsedHours: number | null;
|
elapsedHours: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Priority levels for todo items.
|
||||||
|
* Matches @fix_plan.md format (P0=critical, P1=high, P2=normal).
|
||||||
|
*/
|
||||||
|
export type RalphTodoPriority = 'P0' | 'P1' | 'P2' | null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A detected todo item from Claude Code output
|
* A detected todo item from Claude Code output
|
||||||
*/
|
*/
|
||||||
@@ -730,6 +736,8 @@ export interface RalphTodoItem {
|
|||||||
status: RalphTodoStatus;
|
status: RalphTodoStatus;
|
||||||
/** Timestamp when detected */
|
/** Timestamp when detected */
|
||||||
detectedAt: number;
|
detectedAt: number;
|
||||||
|
/** Priority level (P0=critical, P1=high, P2=normal) */
|
||||||
|
priority: RalphTodoPriority;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -878,14 +886,6 @@ export function createInitialCircuitBreakerStatus(): CircuitBreakerStatus {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Priority Todo Types ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Priority levels for todo items.
|
|
||||||
* Matches @fix_plan.md format.
|
|
||||||
*/
|
|
||||||
export type RalphTodoPriority = 'P0' | 'P1' | 'P2' | null;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates initial Ralph tracker state
|
* Creates initial Ralph tracker state
|
||||||
* @returns Fresh Ralph tracker state with defaults
|
* @returns Fresh Ralph tracker state with defaults
|
||||||
|
|||||||
+70
-9
@@ -1294,6 +1294,33 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.eventSource.addEventListener('respawn:blocked', (e) => {
|
||||||
|
const data = JSON.parse(e.data);
|
||||||
|
const session = this.sessions.get(data.sessionId);
|
||||||
|
const reasonMap = {
|
||||||
|
circuit_breaker_open: 'Circuit Breaker Open',
|
||||||
|
exit_signal: 'Exit Signal Detected',
|
||||||
|
status_blocked: 'Claude Reported BLOCKED',
|
||||||
|
};
|
||||||
|
const title = reasonMap[data.reason] || 'Respawn Blocked';
|
||||||
|
this.notificationManager?.notify({
|
||||||
|
urgency: 'critical',
|
||||||
|
category: 'respawn-blocked',
|
||||||
|
sessionId: data.sessionId,
|
||||||
|
sessionName: session?.name || data.sessionId?.slice(0, 8),
|
||||||
|
title,
|
||||||
|
message: data.details,
|
||||||
|
});
|
||||||
|
// Update respawn panel to show blocked state
|
||||||
|
if (data.sessionId === this.activeSessionId) {
|
||||||
|
const stateEl = document.getElementById('respawnStateLabel');
|
||||||
|
if (stateEl) {
|
||||||
|
stateEl.textContent = title;
|
||||||
|
stateEl.classList.add('respawn-blocked');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.eventSource.addEventListener('respawn:stepSent', (_e) => {
|
this.eventSource.addEventListener('respawn:stepSent', (_e) => {
|
||||||
// Step info is shown via state label (e.g., "Sending prompt", "Clearing context")
|
// Step info is shown via state label (e.g., "Sending prompt", "Clearing context")
|
||||||
});
|
});
|
||||||
@@ -3000,7 +3027,10 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
updateRespawnBanner(state) {
|
updateRespawnBanner(state) {
|
||||||
this.$('respawnState').textContent = this.getStateLabel(state);
|
const stateEl = this.$('respawnState');
|
||||||
|
stateEl.textContent = this.getStateLabel(state);
|
||||||
|
// Clear blocked state when state changes (resumed from blocked)
|
||||||
|
stateEl.classList.remove('respawn-blocked');
|
||||||
}
|
}
|
||||||
|
|
||||||
updateDetectionDisplay(detection) {
|
updateDetectionDisplay(detection) {
|
||||||
@@ -5531,10 +5561,14 @@ class ClaudemanApp {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort: in_progress first, then pending, then completed
|
// Sort: by priority (P0 > P1 > P2 > null), then by status (in_progress > pending > completed)
|
||||||
|
const priorityOrder = { 'P0': 0, 'P1': 1, 'P2': 2, null: 3 };
|
||||||
|
const statusOrder = { in_progress: 0, pending: 1, completed: 2 };
|
||||||
const sorted = [...todos].sort((a, b) => {
|
const sorted = [...todos].sort((a, b) => {
|
||||||
const order = { in_progress: 0, pending: 1, completed: 2 };
|
const priA = priorityOrder[a.priority] ?? 3;
|
||||||
return (order[a.status] || 1) - (order[b.status] || 1);
|
const priB = priorityOrder[b.priority] ?? 3;
|
||||||
|
if (priA !== priB) return priA - priB;
|
||||||
|
return (statusOrder[a.status] || 1) - (statusOrder[b.status] || 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Incremental DOM update - reuse existing elements where possible
|
// Incremental DOM update - reuse existing elements where possible
|
||||||
@@ -5548,11 +5582,13 @@ class ClaudemanApp {
|
|||||||
sorted.forEach((todo, i) => {
|
sorted.forEach((todo, i) => {
|
||||||
const card = existingCards[i];
|
const card = existingCards[i];
|
||||||
const statusClass = `task-${todo.status.replace('_', '-')}`;
|
const statusClass = `task-${todo.status.replace('_', '-')}`;
|
||||||
|
const priorityClass = todo.priority ? `task-priority-${todo.priority.toLowerCase()}` : '';
|
||||||
const icon = this.getRalphTaskIcon(todo.status);
|
const icon = this.getRalphTaskIcon(todo.status);
|
||||||
|
|
||||||
// Update class if changed
|
// Update class if changed
|
||||||
if (!card.classList.contains(statusClass)) {
|
const newClass = `ralph-task-card ${statusClass} ${priorityClass}`.trim();
|
||||||
card.className = `ralph-task-card ${statusClass}`;
|
if (card.className !== newClass) {
|
||||||
|
card.className = newClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update icon if changed
|
// Update icon if changed
|
||||||
@@ -5561,6 +5597,22 @@ class ClaudemanApp {
|
|||||||
iconEl.textContent = icon;
|
iconEl.textContent = icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update priority badge
|
||||||
|
let badgeEl = card.querySelector('.ralph-task-priority');
|
||||||
|
if (todo.priority) {
|
||||||
|
if (!badgeEl) {
|
||||||
|
badgeEl = document.createElement('span');
|
||||||
|
badgeEl.className = `ralph-task-priority priority-${todo.priority.toLowerCase()}`;
|
||||||
|
card.insertBefore(badgeEl, card.querySelector('.ralph-task-content'));
|
||||||
|
}
|
||||||
|
if (badgeEl.textContent !== todo.priority) {
|
||||||
|
badgeEl.textContent = todo.priority;
|
||||||
|
badgeEl.className = `ralph-task-priority priority-${todo.priority.toLowerCase()}`;
|
||||||
|
}
|
||||||
|
} else if (badgeEl) {
|
||||||
|
badgeEl.remove();
|
||||||
|
}
|
||||||
|
|
||||||
// Update content if changed
|
// Update content if changed
|
||||||
const contentEl = card.querySelector('.ralph-task-content');
|
const contentEl = card.querySelector('.ralph-task-content');
|
||||||
if (contentEl && contentEl.textContent !== todo.content) {
|
if (contentEl && contentEl.textContent !== todo.content) {
|
||||||
@@ -5572,18 +5624,27 @@ class ClaudemanApp {
|
|||||||
sorted.forEach(todo => {
|
sorted.forEach(todo => {
|
||||||
const card = document.createElement('div');
|
const card = document.createElement('div');
|
||||||
const statusClass = `task-${todo.status.replace('_', '-')}`;
|
const statusClass = `task-${todo.status.replace('_', '-')}`;
|
||||||
card.className = `ralph-task-card ${statusClass}`;
|
const priorityClass = todo.priority ? `task-priority-${todo.priority.toLowerCase()}` : '';
|
||||||
|
card.className = `ralph-task-card ${statusClass} ${priorityClass}`.trim();
|
||||||
|
|
||||||
const iconSpan = document.createElement('span');
|
const iconSpan = document.createElement('span');
|
||||||
iconSpan.className = 'ralph-task-icon';
|
iconSpan.className = 'ralph-task-icon';
|
||||||
iconSpan.textContent = this.getRalphTaskIcon(todo.status);
|
iconSpan.textContent = this.getRalphTaskIcon(todo.status);
|
||||||
|
card.appendChild(iconSpan);
|
||||||
|
|
||||||
|
// Add priority badge if present
|
||||||
|
if (todo.priority) {
|
||||||
|
const prioritySpan = document.createElement('span');
|
||||||
|
prioritySpan.className = `ralph-task-priority priority-${todo.priority.toLowerCase()}`;
|
||||||
|
prioritySpan.textContent = todo.priority;
|
||||||
|
card.appendChild(prioritySpan);
|
||||||
|
}
|
||||||
|
|
||||||
const contentSpan = document.createElement('span');
|
const contentSpan = document.createElement('span');
|
||||||
contentSpan.className = 'ralph-task-content';
|
contentSpan.className = 'ralph-task-content';
|
||||||
contentSpan.textContent = todo.content;
|
contentSpan.textContent = todo.content;
|
||||||
|
|
||||||
card.appendChild(iconSpan);
|
|
||||||
card.appendChild(contentSpan);
|
card.appendChild(contentSpan);
|
||||||
|
|
||||||
fragment.appendChild(card);
|
fragment.appendChild(card);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -620,6 +620,17 @@ body {
|
|||||||
font-size: 0.7rem;
|
font-size: 0.7rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.respawn-state.respawn-blocked {
|
||||||
|
background: rgba(244, 67, 54, 0.3);
|
||||||
|
color: #f44336;
|
||||||
|
animation: respawn-blocked-pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes respawn-blocked-pulse {
|
||||||
|
0%, 100% { opacity: 1; }
|
||||||
|
50% { opacity: 0.6; }
|
||||||
|
}
|
||||||
|
|
||||||
/* Respawn spacer removed - using flex layout */
|
/* Respawn spacer removed - using flex layout */
|
||||||
|
|
||||||
.respawn-cycles {
|
.respawn-cycles {
|
||||||
@@ -1305,6 +1316,40 @@ body {
|
|||||||
color: var(--text-dim);
|
color: var(--text-dim);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Priority badges */
|
||||||
|
.ralph-task-priority {
|
||||||
|
font-size: 0.55rem;
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.1rem 0.25rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ralph-task-priority.priority-p0 {
|
||||||
|
background: rgba(244, 67, 54, 0.3);
|
||||||
|
color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ralph-task-priority.priority-p1 {
|
||||||
|
background: rgba(255, 193, 7, 0.3);
|
||||||
|
color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ralph-task-priority.priority-p2 {
|
||||||
|
background: rgba(33, 150, 243, 0.2);
|
||||||
|
color: #2196f3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Priority-based card highlighting */
|
||||||
|
.ralph-task-card.task-priority-p0 {
|
||||||
|
border-left-color: #f44336;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ralph-task-card.task-priority-p1:not(.task-completed) {
|
||||||
|
border-left-color: #ffc107;
|
||||||
|
}
|
||||||
|
|
||||||
/* Empty state */
|
/* Empty state */
|
||||||
.ralph-state-empty {
|
.ralph-state-empty {
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
|
|||||||
@@ -2532,6 +2532,13 @@ export class WebServer extends EventEmitter {
|
|||||||
this.broadcast('respawn:cycleCompleted', { sessionId, cycleNumber });
|
this.broadcast('respawn:cycleCompleted', { sessionId, cycleNumber });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
controller.on('respawnBlocked', (data: { reason: string; details: string }) => {
|
||||||
|
this.broadcast('respawn:blocked', { sessionId, reason: data.reason, details: data.details });
|
||||||
|
// Track in run summary (lazy lookup)
|
||||||
|
const tracker = getTracker();
|
||||||
|
if (tracker) tracker.recordWarning(`Respawn blocked: ${data.reason}`, data.details);
|
||||||
|
});
|
||||||
|
|
||||||
controller.on('stepSent', (step: string, input: string) => {
|
controller.on('stepSent', (step: string, input: string) => {
|
||||||
this.broadcast('respawn:stepSent', { sessionId, step, input });
|
this.broadcast('respawn:stepSent', { sessionId, step, input });
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user