mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 08:29:42 +02:00
chore: bump version to 0.1439
This commit is contained in:
@@ -16,7 +16,7 @@ When user says "COM":
|
|||||||
1. Increment version in BOTH `package.json` AND `CLAUDE.md`
|
1. Increment version in BOTH `package.json` AND `CLAUDE.md`
|
||||||
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
||||||
|
|
||||||
**Version**: 0.1438 (must match `package.json`)
|
**Version**: 0.1439 (must match `package.json`)
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1438",
|
"version": "0.1439",
|
||||||
"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",
|
||||||
|
|||||||
@@ -338,6 +338,9 @@ export class Session extends EventEmitter {
|
|||||||
// Image watcher setting (per-session toggle)
|
// Image watcher setting (per-session toggle)
|
||||||
private _imageWatcherEnabled: boolean = true;
|
private _imageWatcherEnabled: boolean = true;
|
||||||
|
|
||||||
|
// Flicker filter setting (per-session toggle, applied on frontend)
|
||||||
|
private _flickerFilterEnabled: boolean = false;
|
||||||
|
|
||||||
// Timer tracking for cleanup (prevents memory leaks)
|
// Timer tracking for cleanup (prevents memory leaks)
|
||||||
private _autoCompactTimer: NodeJS.Timeout | null = null;
|
private _autoCompactTimer: NodeJS.Timeout | null = null;
|
||||||
private _autoClearTimer: NodeJS.Timeout | null = null;
|
private _autoClearTimer: NodeJS.Timeout | null = null;
|
||||||
@@ -694,6 +697,14 @@ export class Session extends EventEmitter {
|
|||||||
this._imageWatcherEnabled = enabled;
|
this._imageWatcherEnabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get flickerFilterEnabled(): boolean {
|
||||||
|
return this._flickerFilterEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
set flickerFilterEnabled(enabled: boolean) {
|
||||||
|
this._flickerFilterEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
isIdle(): boolean {
|
isIdle(): boolean {
|
||||||
return this._status === 'idle';
|
return this._status === 'idle';
|
||||||
}
|
}
|
||||||
@@ -734,6 +745,7 @@ export class Session extends EventEmitter {
|
|||||||
niceEnabled: this._niceConfig.enabled,
|
niceEnabled: this._niceConfig.enabled,
|
||||||
niceValue: this._niceConfig.niceValue,
|
niceValue: this._niceConfig.niceValue,
|
||||||
color: this._color,
|
color: this._color,
|
||||||
|
flickerFilterEnabled: this._flickerFilterEnabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -180,6 +180,8 @@ export interface SessionState {
|
|||||||
niceValue?: number;
|
niceValue?: number;
|
||||||
/** User-assigned color for visual differentiation */
|
/** User-assigned color for visual differentiation */
|
||||||
color?: SessionColor;
|
color?: SessionColor;
|
||||||
|
/** Flicker filter enabled (buffers output after screen clears) */
|
||||||
|
flickerFilterEnabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Global Stats Types ==========
|
// ========== Global Stats Types ==========
|
||||||
|
|||||||
+132
-2
@@ -631,6 +631,7 @@ class ClaudemanApp {
|
|||||||
// Once set, never recalculated. Persisted to localStorage and server.
|
// Once set, never recalculated. Persisted to localStorage and server.
|
||||||
this.subagentParentMap = new Map();
|
this.subagentParentMap = new Map();
|
||||||
this.ralphStatePanelCollapsed = true; // Default to collapsed
|
this.ralphStatePanelCollapsed = true; // Default to collapsed
|
||||||
|
this.ralphClosedSessions = new Set(); // Sessions where user explicitly closed Ralph panel
|
||||||
|
|
||||||
// Plan subagent windows (visible agents during plan generation)
|
// Plan subagent windows (visible agents during plan generation)
|
||||||
this.planSubagents = new Map(); // Map<agentId, { type, model, status, startTime, element, relativePos }>
|
this.planSubagents = new Map(); // Map<agentId, { type, model, status, startTime, element, relativePos }>
|
||||||
@@ -667,6 +668,11 @@ class ClaudemanApp {
|
|||||||
this._wasAtBottomBeforeWrite = true; // Default to true for sticky scroll
|
this._wasAtBottomBeforeWrite = true; // Default to true for sticky scroll
|
||||||
this.syncWaitTimeout = null; // Timeout for incomplete sync blocks
|
this.syncWaitTimeout = null; // Timeout for incomplete sync blocks
|
||||||
|
|
||||||
|
// Flicker filter state (buffers output after screen clears)
|
||||||
|
this.flickerFilterBuffer = '';
|
||||||
|
this.flickerFilterActive = false;
|
||||||
|
this.flickerFilterTimeout = null;
|
||||||
|
|
||||||
// Render debouncing
|
// Render debouncing
|
||||||
this.renderSessionTabsTimeout = null;
|
this.renderSessionTabsTimeout = null;
|
||||||
this.renderRalphStatePanelTimeout = null;
|
this.renderRalphStatePanelTimeout = null;
|
||||||
@@ -1053,6 +1059,44 @@ class ClaudemanApp {
|
|||||||
this._wasAtBottomBeforeWrite = this.isTerminalAtBottom();
|
this._wasAtBottomBeforeWrite = this.isTerminalAtBottom();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if flicker filter is enabled for current session
|
||||||
|
const session = this.activeSessionId ? this.sessions.get(this.activeSessionId) : null;
|
||||||
|
const flickerFilterEnabled = session?.flickerFilterEnabled ?? false;
|
||||||
|
|
||||||
|
// Flicker filter: detect screen clear patterns and buffer output
|
||||||
|
if (flickerFilterEnabled) {
|
||||||
|
// Detect Ink's screen clear patterns:
|
||||||
|
// - ESC[2J (clear entire screen)
|
||||||
|
// - ESC[H ESC[J (cursor home + clear to end)
|
||||||
|
// - ESC[?25l ESC[H (hide cursor + home - common Ink pattern)
|
||||||
|
const hasScreenClear = data.includes('\x1b[2J') ||
|
||||||
|
data.includes('\x1b[H\x1b[J') ||
|
||||||
|
(data.includes('\x1b[H') && data.includes('\x1b[?25l'));
|
||||||
|
|
||||||
|
if (hasScreenClear) {
|
||||||
|
// Screen clear detected - activate flicker filter
|
||||||
|
this.flickerFilterActive = true;
|
||||||
|
this.flickerFilterBuffer += data;
|
||||||
|
|
||||||
|
// Clear any existing timeout and set a new one
|
||||||
|
if (this.flickerFilterTimeout) {
|
||||||
|
clearTimeout(this.flickerFilterTimeout);
|
||||||
|
}
|
||||||
|
this.flickerFilterTimeout = setTimeout(() => {
|
||||||
|
this.flickerFilterTimeout = null;
|
||||||
|
this.flushFlickerBuffer();
|
||||||
|
}, SYNC_WAIT_TIMEOUT_MS); // 50ms buffer window
|
||||||
|
|
||||||
|
return; // Don't process normally, wait for buffer window
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.flickerFilterActive) {
|
||||||
|
// Filter is active, accumulate data
|
||||||
|
this.flickerFilterBuffer += data;
|
||||||
|
return; // Continue buffering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Accumulate raw data (may contain DEC 2026 markers)
|
// Accumulate raw data (may contain DEC 2026 markers)
|
||||||
this.pendingWrites += data;
|
this.pendingWrites += data;
|
||||||
|
|
||||||
@@ -1090,6 +1134,28 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flush the flicker filter buffer to the terminal.
|
||||||
|
* Called after the buffer window expires.
|
||||||
|
*/
|
||||||
|
flushFlickerBuffer() {
|
||||||
|
if (!this.flickerFilterBuffer) return;
|
||||||
|
|
||||||
|
// Transfer buffered data to normal pending writes
|
||||||
|
this.pendingWrites += this.flickerFilterBuffer;
|
||||||
|
this.flickerFilterBuffer = '';
|
||||||
|
this.flickerFilterActive = false;
|
||||||
|
|
||||||
|
// Trigger a normal flush
|
||||||
|
if (!this.writeFrameScheduled) {
|
||||||
|
this.writeFrameScheduled = true;
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
this.flushPendingWrites();
|
||||||
|
this.writeFrameScheduled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush pending writes to terminal, processing DEC 2026 sync markers.
|
* Flush pending writes to terminal, processing DEC 2026 sync markers.
|
||||||
* Strips markers and writes content atomically within a single frame.
|
* Strips markers and writes content atomically within a single frame.
|
||||||
@@ -1366,6 +1432,7 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
this.terminalBuffers.delete(data.id);
|
this.terminalBuffers.delete(data.id);
|
||||||
this.ralphStates.delete(data.id); // Clean up ralph state for this session
|
this.ralphStates.delete(data.id); // Clean up ralph state for this session
|
||||||
|
this.ralphClosedSessions.delete(data.id); // Clean up closed tracking for this session
|
||||||
this.projectInsights.delete(data.id); // Clean up project insights for this session
|
this.projectInsights.delete(data.id); // Clean up project insights for this session
|
||||||
this.closeSessionLogViewerWindows(data.id); // Close log viewer windows for this session
|
this.closeSessionLogViewerWindows(data.id); // Close log viewer windows for this session
|
||||||
this.closeSessionImagePopups(data.id); // Close image popup windows for this session
|
this.closeSessionImagePopups(data.id); // Close image popup windows for this session
|
||||||
@@ -1803,16 +1870,22 @@ class ClaudemanApp {
|
|||||||
// Ralph loop/todo events
|
// Ralph loop/todo events
|
||||||
addListener('session:ralphLoopUpdate', (e) => {
|
addListener('session:ralphLoopUpdate', (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
// Skip if user explicitly closed this session's Ralph panel
|
||||||
|
if (this.ralphClosedSessions.has(data.sessionId)) return;
|
||||||
this.updateRalphState(data.sessionId, { loop: data.state });
|
this.updateRalphState(data.sessionId, { loop: data.state });
|
||||||
});
|
});
|
||||||
|
|
||||||
addListener('session:ralphTodoUpdate', (e) => {
|
addListener('session:ralphTodoUpdate', (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
// Skip if user explicitly closed this session's Ralph panel
|
||||||
|
if (this.ralphClosedSessions.has(data.sessionId)) return;
|
||||||
this.updateRalphState(data.sessionId, { todos: data.todos });
|
this.updateRalphState(data.sessionId, { todos: data.todos });
|
||||||
});
|
});
|
||||||
|
|
||||||
addListener('session:ralphCompletionDetected', (e) => {
|
addListener('session:ralphCompletionDetected', (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
// Skip if user explicitly closed this session's Ralph panel
|
||||||
|
if (this.ralphClosedSessions.has(data.sessionId)) return;
|
||||||
// Prevent duplicate notifications for the same completion
|
// Prevent duplicate notifications for the same completion
|
||||||
const completionKey = `${data.sessionId}:${data.phrase}`;
|
const completionKey = `${data.sessionId}:${data.phrase}`;
|
||||||
if (this._shownCompletions?.has(completionKey)) {
|
if (this._shownCompletions?.has(completionKey)) {
|
||||||
@@ -1846,11 +1919,15 @@ class ClaudemanApp {
|
|||||||
// RALPH_STATUS block and circuit breaker events
|
// RALPH_STATUS block and circuit breaker events
|
||||||
addListener('session:ralphStatusUpdate', (e) => {
|
addListener('session:ralphStatusUpdate', (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
// Skip if user explicitly closed this session's Ralph panel
|
||||||
|
if (this.ralphClosedSessions.has(data.sessionId)) return;
|
||||||
this.updateRalphState(data.sessionId, { statusBlock: data.block });
|
this.updateRalphState(data.sessionId, { statusBlock: data.block });
|
||||||
});
|
});
|
||||||
|
|
||||||
addListener('session:circuitBreakerUpdate', (e) => {
|
addListener('session:circuitBreakerUpdate', (e) => {
|
||||||
const data = JSON.parse(e.data);
|
const data = JSON.parse(e.data);
|
||||||
|
// Skip if user explicitly closed this session's Ralph panel
|
||||||
|
if (this.ralphClosedSessions.has(data.sessionId)) return;
|
||||||
this.updateRalphState(data.sessionId, { circuitBreaker: data.status });
|
this.updateRalphState(data.sessionId, { circuitBreaker: data.status });
|
||||||
// Notify if circuit breaker opens
|
// Notify if circuit breaker opens
|
||||||
if (data.status.state === 'OPEN') {
|
if (data.status.state === 'OPEN') {
|
||||||
@@ -2222,6 +2299,13 @@ class ClaudemanApp {
|
|||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
}
|
}
|
||||||
this.idleTimers.clear();
|
this.idleTimers.clear();
|
||||||
|
// Clear flicker filter state
|
||||||
|
if (this.flickerFilterTimeout) {
|
||||||
|
clearTimeout(this.flickerFilterTimeout);
|
||||||
|
this.flickerFilterTimeout = null;
|
||||||
|
}
|
||||||
|
this.flickerFilterBuffer = '';
|
||||||
|
this.flickerFilterActive = false;
|
||||||
// Clear pending hooks
|
// Clear pending hooks
|
||||||
this.pendingHooks.clear();
|
this.pendingHooks.clear();
|
||||||
// Clear tab alerts
|
// Clear tab alerts
|
||||||
@@ -2250,8 +2334,8 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
data.sessions.forEach(s => {
|
data.sessions.forEach(s => {
|
||||||
this.sessions.set(s.id, s);
|
this.sessions.set(s.id, s);
|
||||||
// Load ralph state from session data
|
// Load ralph state from session data (only if not explicitly closed by user)
|
||||||
if (s.ralphLoop || s.ralphTodos) {
|
if ((s.ralphLoop || s.ralphTodos) && !this.ralphClosedSessions.has(s.id)) {
|
||||||
this.ralphStates.set(s.id, {
|
this.ralphStates.set(s.id, {
|
||||||
loop: s.ralphLoop || null,
|
loop: s.ralphLoop || null,
|
||||||
todos: s.ralphTodos || []
|
todos: s.ralphTodos || []
|
||||||
@@ -2842,6 +2926,14 @@ class ClaudemanApp {
|
|||||||
async selectSession(sessionId) {
|
async selectSession(sessionId) {
|
||||||
if (this.activeSessionId === sessionId) return;
|
if (this.activeSessionId === sessionId) return;
|
||||||
|
|
||||||
|
// Clean up flicker filter state when switching sessions
|
||||||
|
if (this.flickerFilterTimeout) {
|
||||||
|
clearTimeout(this.flickerFilterTimeout);
|
||||||
|
this.flickerFilterTimeout = null;
|
||||||
|
}
|
||||||
|
this.flickerFilterBuffer = '';
|
||||||
|
this.flickerFilterActive = false;
|
||||||
|
|
||||||
this.activeSessionId = sessionId;
|
this.activeSessionId = sessionId;
|
||||||
this.hideWelcome();
|
this.hideWelcome();
|
||||||
// Clear idle hooks on view, but keep action hooks until user interacts
|
// Clear idle hooks on view, but keep action hooks until user interacts
|
||||||
@@ -5754,6 +5846,9 @@ class ClaudemanApp {
|
|||||||
|
|
||||||
const sessionId = data.sessionId;
|
const sessionId = data.sessionId;
|
||||||
|
|
||||||
|
// Clear from closed set in case user previously closed Ralph for this session
|
||||||
|
this.ralphClosedSessions.delete(sessionId);
|
||||||
|
|
||||||
// Step 2: Configure Ralph tracker for this session
|
// Step 2: Configure Ralph tracker for this session
|
||||||
await fetch(`/api/sessions/${sessionId}/ralph-config`, {
|
await fetch(`/api/sessions/${sessionId}/ralph-config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -6932,6 +7027,7 @@ class ClaudemanApp {
|
|||||||
document.getElementById('modalAutoClearEnabled').checked = session.autoClearEnabled ?? false;
|
document.getElementById('modalAutoClearEnabled').checked = session.autoClearEnabled ?? false;
|
||||||
document.getElementById('modalAutoClearThreshold').value = session.autoClearThreshold ?? 140000;
|
document.getElementById('modalAutoClearThreshold').value = session.autoClearThreshold ?? 140000;
|
||||||
document.getElementById('modalImageWatcherEnabled').checked = session.imageWatcherEnabled ?? true;
|
document.getElementById('modalImageWatcherEnabled').checked = session.imageWatcherEnabled ?? true;
|
||||||
|
document.getElementById('modalFlickerFilterEnabled').checked = session.flickerFilterEnabled ?? false;
|
||||||
|
|
||||||
// Initialize color picker with current session color
|
// Initialize color picker with current session color
|
||||||
const currentColor = session.color || 'default';
|
const currentColor = session.color || 'default';
|
||||||
@@ -7010,6 +7106,26 @@ class ClaudemanApp {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async toggleFlickerFilter() {
|
||||||
|
if (!this.editingSessionId) return;
|
||||||
|
const enabled = document.getElementById('modalFlickerFilterEnabled').checked;
|
||||||
|
try {
|
||||||
|
await fetch(`/api/sessions/${this.editingSessionId}/flicker-filter`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ enabled })
|
||||||
|
});
|
||||||
|
// Update local session state
|
||||||
|
const session = this.sessions.get(this.editingSessionId);
|
||||||
|
if (session) {
|
||||||
|
session.flickerFilterEnabled = enabled;
|
||||||
|
}
|
||||||
|
this.showToast(`Flicker filter ${enabled ? 'enabled' : 'disabled'}`, 'success');
|
||||||
|
} catch (err) {
|
||||||
|
this.showToast('Failed to toggle flicker filter', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async autoSaveRespawnConfig() {
|
async autoSaveRespawnConfig() {
|
||||||
if (!this.editingSessionId) return;
|
if (!this.editingSessionId) return;
|
||||||
const config = {
|
const config = {
|
||||||
@@ -7715,6 +7831,11 @@ class ClaudemanApp {
|
|||||||
|
|
||||||
const config = this.getRalphConfig();
|
const config = this.getRalphConfig();
|
||||||
|
|
||||||
|
// If user is enabling Ralph, clear from closed set
|
||||||
|
if (config.enabled) {
|
||||||
|
this.ralphClosedSessions.delete(this.editingSessionId);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`, {
|
const res = await fetch(`/api/sessions/${this.editingSessionId}/ralph-config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -9045,6 +9166,9 @@ class ClaudemanApp {
|
|||||||
async closeRalphTracker() {
|
async closeRalphTracker() {
|
||||||
if (!this.activeSessionId) return;
|
if (!this.activeSessionId) return;
|
||||||
|
|
||||||
|
// Mark this session as explicitly closed - will stay hidden until user re-enables
|
||||||
|
this.ralphClosedSessions.add(this.activeSessionId);
|
||||||
|
|
||||||
// Disable tracker via API
|
// Disable tracker via API
|
||||||
await fetch(`/api/sessions/${this.activeSessionId}/ralph-config`, {
|
await fetch(`/api/sessions/${this.activeSessionId}/ralph-config`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -9343,6 +9467,12 @@ class ClaudemanApp {
|
|||||||
|
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
|
|
||||||
|
// If user explicitly closed this session's Ralph panel, keep it hidden
|
||||||
|
if (this.ralphClosedSessions.has(this.activeSessionId)) {
|
||||||
|
panel.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const state = this.ralphStates.get(this.activeSessionId);
|
const state = this.ralphStates.get(this.activeSessionId);
|
||||||
|
|
||||||
// Check if there's anything to show
|
// Check if there's anything to show
|
||||||
|
|||||||
@@ -583,6 +583,16 @@
|
|||||||
</label>
|
</label>
|
||||||
<span class="form-hint">Auto-popup new images (screenshots, generated files) in this session's directory</span>
|
<span class="form-hint">Auto-popup new images (screenshots, generated files) in this session's directory</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section-header">Performance</div>
|
||||||
|
<div class="form-row form-row-switch">
|
||||||
|
<label>Flicker Filter</label>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="modalFlickerFilterEnabled" onchange="app.toggleFlickerFilter()">
|
||||||
|
<span class="slider"></span>
|
||||||
|
</label>
|
||||||
|
<span class="form-hint">Buffers output after screen clears to reduce Ink UI flickering (adds ~50ms latency)</span>
|
||||||
|
</div>
|
||||||
</div><!-- End context-tab -->
|
</div><!-- End context-tab -->
|
||||||
|
|
||||||
<!-- Ralph Wiggum Tab Content -->
|
<!-- Ralph Wiggum Tab Content -->
|
||||||
|
|||||||
+39
-4
@@ -280,6 +280,7 @@ export class WebServer extends EventEmitter {
|
|||||||
private store = getStore();
|
private store = getStore();
|
||||||
private port: number;
|
private port: number;
|
||||||
private https: boolean;
|
private https: boolean;
|
||||||
|
private testMode: boolean;
|
||||||
private screenManager: ScreenManager;
|
private screenManager: ScreenManager;
|
||||||
// Terminal batching for performance
|
// Terminal batching for performance
|
||||||
private terminalBatches: Map<string, string> = new Map();
|
private terminalBatches: Map<string, string> = new Map();
|
||||||
@@ -325,10 +326,11 @@ export class WebServer extends EventEmitter {
|
|||||||
error: (error: Error, sessionId?: string) => void;
|
error: (error: Error, sessionId?: string) => void;
|
||||||
} | null = null;
|
} | null = null;
|
||||||
|
|
||||||
constructor(port: number = 3000, https: boolean = false) {
|
constructor(port: number = 3000, https: boolean = false, testMode: boolean = false) {
|
||||||
super();
|
super();
|
||||||
this.port = port;
|
this.port = port;
|
||||||
this.https = https;
|
this.https = https;
|
||||||
|
this.testMode = testMode;
|
||||||
|
|
||||||
if (https) {
|
if (https) {
|
||||||
const { key, cert } = getOrCreateSelfSignedCert();
|
const { key, cert } = getOrCreateSelfSignedCert();
|
||||||
@@ -1879,6 +1881,32 @@ export class WebServer extends EventEmitter {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle flicker filter for a session
|
||||||
|
this.app.post('/api/sessions/:id/flicker-filter', async (req) => {
|
||||||
|
const { id } = req.params as { id: string };
|
||||||
|
const body = req.body as { enabled: boolean };
|
||||||
|
const session = this.sessions.get(id);
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (body.enabled === undefined) {
|
||||||
|
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'enabled field is required');
|
||||||
|
}
|
||||||
|
|
||||||
|
session.flickerFilterEnabled = body.enabled;
|
||||||
|
this.persistSessionState(session);
|
||||||
|
this.broadcast('session:updated', this.getSessionStateWithRespawn(session));
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
flickerFilterEnabled: body.enabled,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// Quick run (create session, run prompt, return result, then cleanup)
|
// Quick run (create session, run prompt, return result, then cleanup)
|
||||||
this.app.post('/api/run', async (req) => {
|
this.app.post('/api/run', async (req) => {
|
||||||
// Prevent unbounded session creation
|
// Prevent unbounded session creation
|
||||||
@@ -4414,7 +4442,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
|||||||
|
|
||||||
// Restore screen sessions BEFORE accepting connections
|
// Restore screen sessions BEFORE accepting connections
|
||||||
// This prevents race conditions where clients connect before state is ready
|
// This prevents race conditions where clients connect before state is ready
|
||||||
await this.restoreScreenSessions();
|
// CRITICAL: Skip in test mode to prevent tests from picking up user sessions
|
||||||
|
if (!this.testMode) {
|
||||||
|
await this.restoreScreenSessions();
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up stale sessions from state file that don't have active screens
|
// Clean up stale sessions from state file that don't have active screens
|
||||||
this.cleanupStaleSessions();
|
this.cleanupStaleSessions();
|
||||||
@@ -4587,6 +4618,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
|||||||
niceValue: savedState.niceValue,
|
niceValue: savedState.niceValue,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
// Flicker filter (frontend-applied but persisted)
|
||||||
|
if (savedState.flickerFilterEnabled !== undefined) {
|
||||||
|
session.flickerFilterEnabled = savedState.flickerFilterEnabled;
|
||||||
|
}
|
||||||
// Respawn controller
|
// Respawn controller
|
||||||
if (savedState.respawnEnabled && savedState.respawnConfig) {
|
if (savedState.respawnEnabled && savedState.respawnConfig) {
|
||||||
try {
|
try {
|
||||||
@@ -4751,8 +4786,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function startWebServer(port: number = 3000, https: boolean = false): Promise<WebServer> {
|
export async function startWebServer(port: number = 3000, https: boolean = false, testMode: boolean = false): Promise<WebServer> {
|
||||||
const server = new WebServer(port, https);
|
const server = new WebServer(port, https, testMode);
|
||||||
await server.start();
|
await server.start();
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ describe('Agent-Browser Enhanced Tests', () => {
|
|||||||
closeBrowser();
|
closeBrowser();
|
||||||
|
|
||||||
// Start web server
|
// Start web server
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ describe('Browser Framework Comparison Benchmark', () => {
|
|||||||
|
|
||||||
// Start web servers for each framework
|
// Start web servers for each framework
|
||||||
for (const [name, port] of Object.entries(PORTS)) {
|
for (const [name, port] of Object.entries(PORTS)) {
|
||||||
const server = new WebServer(port);
|
const server = new WebServer(port, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
servers.push(server);
|
servers.push(server);
|
||||||
console.log(`✅ Started server for ${name} on port ${port}`);
|
console.log(`✅ Started server for ${name} on port ${port}`);
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ describe('Browser E2E Tests', () => {
|
|||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
closeBrowser();
|
closeBrowser();
|
||||||
|
|
||||||
server = new WebServer(TEST_PORT_BROWSER);
|
server = new WebServer(TEST_PORT_BROWSER, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
|
||||||
@@ -211,7 +211,7 @@ describe('SSE Events API', () => {
|
|||||||
let createdSessions: string[] = [];
|
let createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT_SSE);
|
server = new WebServer(TEST_PORT_SSE, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
}, 30000);
|
}, 30000);
|
||||||
@@ -280,7 +280,7 @@ describe('Hook Events API', () => {
|
|||||||
let createdSessions: string[] = [];
|
let createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT_HOOK);
|
server = new WebServer(TEST_PORT_HOOK, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
}, 30000);
|
}, 30000);
|
||||||
@@ -345,7 +345,7 @@ describe('Ralph API', () => {
|
|||||||
let createdSessions: string[] = [];
|
let createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT_RALPH);
|
server = new WebServer(TEST_PORT_RALPH, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
}, 30000);
|
}, 30000);
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ let setupComplete = false;
|
|||||||
describe('Playwright Direct API Tests', () => {
|
describe('Playwright Direct API Tests', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// Start web server
|
// Start web server
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ describe('Puppeteer/Chrome Tests', () => {
|
|||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
// Start web server
|
// Start web server
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export interface ServerFixture {
|
|||||||
* @returns ServerFixture with server instance and connection info
|
* @returns ServerFixture with server instance and connection info
|
||||||
*/
|
*/
|
||||||
export async function createServerFixture(port: number): Promise<ServerFixture> {
|
export async function createServerFixture(port: number): Promise<ServerFixture> {
|
||||||
const server = new WebServer(port);
|
const server = new WebServer(port, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
|
|
||||||
// Wait for server to be fully ready
|
// Wait for server to be fully ready
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('Edge Cases and Error Handling', () => {
|
|||||||
const createdCases: string[] = [];
|
const createdCases: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
@@ -261,7 +261,7 @@ describe('Concurrent Session Handling', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1);
|
server = new WebServer(TEST_PORT + 1, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
});
|
});
|
||||||
@@ -362,7 +362,7 @@ describe('API Request Validation', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 2);
|
server = new WebServer(TEST_PORT + 2, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 2}`;
|
baseUrl = `http://localhost:${TEST_PORT + 2}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ describe('File Link Click Tests', () => {
|
|||||||
testLogFile = join(testDir, 'test.log');
|
testLogFile = join(testDir, 'test.log');
|
||||||
writeFileSync(testLogFile, '=== Test Log Started ===\n');
|
writeFileSync(testLogFile, '=== Test Log Started ===\n');
|
||||||
|
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
|||||||
@@ -200,7 +200,7 @@ describe('Hook Event API', () => {
|
|||||||
let testSessionId: string;
|
let testSessionId: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
|
|
||||||
@@ -382,7 +382,7 @@ describe('Hook Data Sanitization', () => {
|
|||||||
let testSessionId: string;
|
let testSessionId: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1); // Port 3131
|
server = new WebServer(TEST_PORT + 1, false, true); // Port 3131
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ describe('Integration Flows', () => {
|
|||||||
const createdSessions: string[] = [];
|
const createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
@@ -278,7 +278,7 @@ describe('SSE Event Flow', () => {
|
|||||||
const createdSessions: string[] = [];
|
const createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1);
|
server = new WebServer(TEST_PORT + 1, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ describe('Quick Start API', () => {
|
|||||||
const createdCases: string[] = [];
|
const createdCases: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
@@ -147,7 +147,7 @@ describe('Session Management', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1);
|
server = new WebServer(TEST_PORT + 1, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
});
|
});
|
||||||
@@ -204,7 +204,7 @@ describe('Case Management', () => {
|
|||||||
const createdCases: string[] = [];
|
const createdCases: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 2);
|
server = new WebServer(TEST_PORT + 2, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 2}`;
|
baseUrl = `http://localhost:${TEST_PORT + 2}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ describe('Ralph Integration Tests', () => {
|
|||||||
const createdSessions: string[] = [];
|
const createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ describe('Scheduled Runs API', () => {
|
|||||||
const createdRuns: string[] = [];
|
const createdRuns: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
@@ -181,7 +181,7 @@ describe('Quick Run API', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1);
|
server = new WebServer(TEST_PORT + 1, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ describe('Session Cleanup', () => {
|
|||||||
const createdSessions: string[] = [];
|
const createdSessions: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
@@ -186,7 +186,7 @@ describe('Resource Management', () => {
|
|||||||
const createdCases: string[] = [];
|
const createdCases: string[] = [];
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1);
|
server = new WebServer(TEST_PORT + 1, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ describe('Interactive Session Lifecycle', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ describe('SSE Events', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT);
|
server = new WebServer(TEST_PORT, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT}`;
|
baseUrl = `http://localhost:${TEST_PORT}`;
|
||||||
});
|
});
|
||||||
@@ -180,7 +180,7 @@ describe('SSE Event Types', () => {
|
|||||||
let baseUrl: string;
|
let baseUrl: string;
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
server = new WebServer(TEST_PORT + 1);
|
server = new WebServer(TEST_PORT + 1, false, true);
|
||||||
await server.start();
|
await server.start();
|
||||||
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
baseUrl = `http://localhost:${TEST_PORT + 1}`;
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user