From 8f0bbf33d6302b7c5eb4129bd8c947286db49acf Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 4 Feb 2026 07:24:32 +0100 Subject: [PATCH] chore: bump version to 0.1477 Location: Ko Samui Co-Authored-By: Claude Opus 4.5 --- CLAUDE.md | 166 ++------- docs/terminal-anti-flicker.md | 138 +++++++ package.json | 2 +- src/ai-checker-base.ts | 67 +++- src/file-stream-manager.ts | 15 +- src/plan-orchestrator.ts | 15 +- src/ralph-loop.ts | 34 +- src/ralph-tracker.ts | 172 +++++---- src/respawn-controller.ts | 39 ++ src/screen-manager.ts | 37 +- src/session.ts | 78 ++-- src/state-store.ts | 38 +- src/subagent-watcher.ts | 88 ++++- src/task-tracker.ts | 65 +++- src/transcript-watcher.ts | 11 + src/types.ts | 17 +- src/utils/index.ts | 1 + src/utils/lru-map.ts | 13 + src/utils/type-safety.ts | 35 ++ src/web/public/app.js | 25 +- src/web/schemas.ts | 141 ++++++++ src/web/server.ts | 340 ++++++++++++------ test/e2e/e2e.config.ts | 1 + .../baselines/agent-windows-visible.png | Bin 0 -> 92565 bytes .../screenshots/baselines/respawn-active.png | Bin 0 -> 68790 bytes .../e2e/workflows/mobile-comprehensive.e2e.ts | 4 +- test/e2e/workflows/plan-generation.e2e.ts | 4 +- 27 files changed, 1098 insertions(+), 448 deletions(-) create mode 100644 docs/terminal-anti-flicker.md create mode 100644 src/utils/type-safety.ts create mode 100644 src/web/schemas.ts create mode 100644 test/e2e/screenshots/baselines/agent-windows-visible.png create mode 100644 test/e2e/screenshots/baselines/respawn-active.png diff --git a/CLAUDE.md b/CLAUDE.md index fa484486..09564094 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -26,7 +26,7 @@ When user says "COM": 1. Increment version in BOTH `package.json` AND `CLAUDE.md` (verify they match with `grep version package.json && grep Version 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` -**Version**: 0.1476 (must match `package.json` for npm publish) +**Version**: 0.1477 (must match `package.json` for npm publish) ## Project Overview @@ -174,31 +174,9 @@ journalctl --user -u claudeman-web -f ## Default Settings -UI defaults are optimized for minimal distraction. Set in `src/web/public/app.js` (using `??` operator). +UI defaults are set in `src/web/public/app.js` using `??` fallbacks. To change defaults, edit `openAppSettings()` and `apply*Visibility()` functions. -**Display Settings** (default values): -| Setting | Default | Description | -|---------|---------|-------------| -| `showFontControls` | `false` | Font size controls in header | -| `showSystemStats` | `true` | CPU/memory stats in header | -| `showTokenCount` | `true` | Token counter in header | -| `showCost` | `false` | Cost display | -| `showMonitor` | `true` | Monitor panel | -| `showProjectInsights` | `false` | Project insights panel | -| `showFileBrowser` | `false` | File browser panel | -| `showSubagents` | `false` | Subagent windows panel | - -**Tracking Settings**: -| Setting | Default | Description | -|---------|---------|-------------| -| `ralphTrackerEnabled` | `false` | Ralph/Todo loop tracking | -| `subagentTrackingEnabled` | `true` | Background agent monitoring | -| `subagentActiveTabOnly` | `true` | Show subagents only for active session | -| `imageWatcherEnabled` | `false` | Watch for image file creation | - -**Notification Defaults**: Browser notifications enabled, audio alerts disabled. Critical events (permission prompts, questions) notify by default; info events (respawn cycles, token milestones) are silent. - -To change defaults, edit the `??` fallback values in `openAppSettings()` and `apply*Visibility()` functions. +**Key defaults:** Most panels hidden (monitor, subagents shown), notifications enabled (audio disabled), subagent tracking on, Ralph tracking off. ## Testing @@ -231,6 +209,18 @@ curl localhost:3000/api/sessions/:id/run-summary | jq # Session timeline **Avoid port 3000 during E2E tests** — tests use ports 3183-3193 (see `test/e2e/e2e.config.ts`). +## Troubleshooting + +| Problem | Check | Fix | +|---------|-------|-----| +| Session won't start | `screen -ls` for orphans | Kill orphaned screens, check Claude CLI installed | +| Port 3000 in use | `lsof -i :3000` | Kill conflicting process or use `--port` flag | +| SSE not connecting | Browser console for errors | Check CORS, ensure server running | +| Respawn not triggering | Session settings → Respawn enabled? | Enable respawn, check idle timeout config | +| Terminal blank on tab switch | Network tab for `/api/sessions/:id/buffer` | Check session exists, restart server | +| Tests failing on screen limits | `screen -ls \| wc -l` | Clean up test screens: `screen -ls \| grep test \| awk '{print $1}' \| xargs -I{} screen -X -S {} quit` | +| State not persisting | `cat ~/.claudeman/state.json` | Check file permissions, disk space | + ## Performance Constraints The app must stay fast with 20 sessions and 50 agent windows: @@ -241,132 +231,17 @@ The app must stay fast with 20 sessions and 50 agent windows: ## Terminal Anti-Flicker System -Claude Code uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals), which redraws the entire screen on every state change. Without special handling, users see constant flickering. Claudeman implements a 6-layer anti-flicker pipeline: +Claude Code uses Ink (React for terminals), which redraws the screen on every state change. Claudeman implements a 6-layer anti-flicker pipeline for smooth 60fps output: ``` -PTY Output → Server Batching → DEC 2026 Wrap → SSE → Client rAF → Sync Parser → xterm.js +PTY Output → Server Batching (16-50ms) → DEC 2026 Wrap → SSE → Client rAF → xterm.js ``` -### Layer Details +**Key functions:** `server.ts:batchTerminalData()`, `server.ts:flushTerminalBatches()`, `app.js:batchTerminalWrite()`, `app.js:extractSyncSegments()` -| Layer | Location | Technique | Latency | -|-------|----------|-----------|---------| -| **1. Server Batching** | `server.ts:batchTerminalData()` | Adaptive 16-50ms collection window | 16-50ms | -| **2. DEC Mode 2026** | `server.ts:flushTerminalBatches()` | Wraps with `\x1b[?2026h`...`\x1b[?2026l` | 0ms | -| **3. SSE Broadcast** | `server.ts:broadcast()` | JSON serialize once, send to all clients | 0ms | -| **4. Client rAF** | `app.js:batchTerminalWrite()` | `requestAnimationFrame` batching | 0-16ms | -| **5. Sync Block Parser** | `app.js:extractSyncSegments()` | Strips DEC 2026 markers, waits for complete blocks | 0-50ms | -| **6. Chunked Loading** | `app.js:chunkedTerminalWrite()` | 64KB/frame for large buffers | variable | +**Typical latency:** 16-32ms. Optional per-session flicker filter adds ~50ms for problematic terminals. -### Server-Side Implementation (`server.ts`) - -**Constants:** -```typescript -const TERMINAL_BATCH_INTERVAL = 16; // Base: 60fps -const BATCH_FLUSH_THRESHOLD = 32 * 1024; // Flush immediately if >32KB -const DEC_SYNC_START = '\x1b[?2026h'; // Begin synchronized update -const DEC_SYNC_END = '\x1b[?2026l'; // End synchronized update -``` - -**Adaptive Batching** (`batchTerminalData()`): -- Tracks event frequency per session via `lastTerminalEventTime` Map -- Event gap <10ms → 50ms batch window (rapid-fire Ink redraws) -- Event gap <20ms → 32ms batch window -- Otherwise → 16ms (60fps) -- Flushes immediately if batch exceeds 32KB for responsiveness - -**Flush Logic** (`flushTerminalBatches()`): -```typescript -const syncData = DEC_SYNC_START + data + DEC_SYNC_END; -this.broadcast('session:terminal', { id: sessionId, data: syncData }); -``` - -### Client-Side Implementation (`app.js`) - -**batchTerminalWrite(data):** -1. Checks if flicker filter is enabled (optional, per-session) -2. If flicker filter active: buffers screen-clear patterns (`ESC[2J`, `ESC[H ESC[J`, `ESC[nA`) -3. Accumulates data in `pendingWrites` -4. Schedules `requestAnimationFrame` if not already scheduled -5. On rAF callback: checks for incomplete sync blocks (start without end) -6. If incomplete: waits up to 50ms via `syncWaitTimeout` -7. Calls `flushPendingWrites()` when complete - -**extractSyncSegments(data):** -- Parses DEC 2026 markers, returns array of content segments -- Content before sync blocks returned as-is -- Content inside sync blocks returned without markers -- Incomplete blocks (start without end) returned with marker for next chunk - -**flushPendingWrites():** -```javascript -const segments = extractSyncSegments(this.pendingWrites); -this.pendingWrites = ''; // Clear before writing -for (const segment of segments) { - if (segment && !segment.startsWith(DEC_SYNC_START)) { - this.terminal.write(segment); // Skip incomplete blocks (start with marker) - } -} -``` -Note: Segments starting with `DEC_SYNC_START` are incomplete blocks awaiting more data. These are skipped (discarded if timeout forces flush). - -**chunkedTerminalWrite(buffer, chunkSize=128KB):** -- For large buffer restoration (session switch, reconnect) -- Writes 128KB per `requestAnimationFrame` to avoid UI jank -- Strips any embedded DEC 2026 markers from historical data - -**selectSession() optimizations:** -- Starts buffer fetch immediately before other setup -- Shows "Loading session..." indicator while fetching -- Parallelizes session attach with buffer fetch -- Fire-and-forget resize (doesn't block tab switch) - -### Optional Flicker Filter - -Per-session toggle via Session Settings. Adds ~50ms latency but eliminates remaining flicker on problematic terminals. - -**Detection patterns:** -- `ESC[2J` — Clear entire screen -- `ESC[H ESC[J` — Cursor home + clear to end -- `ESC[?25l ESC[H` — Hide cursor + home (Ink pattern) -- `ESC[nA` (n≥1) — Cursor up (Ink line redraw) - -When detected, buffers 50ms of subsequent output before flushing atomically. - -### Responsiveness Considerations - -**Latency sources:** -| Source | Best Case | Worst Case | Notes | -|--------|-----------|------------|-------| -| Server batching | 0ms (flush) | 50ms (rapid events) | Immediate flush if >32KB | -| Sync block wait | 0ms | 50ms | Only if marker split across packets | -| Flicker filter | 0ms (disabled) | 50ms (enabled) | Optional per-session | -| rAF scheduling | 0ms | 16ms | Display refresh sync | -| **Total** | **0ms** | **~115ms** | Worst case rare in practice | - -**Typical latency:** 16-32ms (server batch + rAF) - -**Edge cases handled:** -- Incomplete sync blocks: 50ms timeout forces flush (content discarded to prevent freeze) -- Large buffers: Chunked writing prevents UI freeze -- Server shutdown: Skips batching via `_isStopping` flag -- Session switch: Clears flicker filter state, pending writes, and sync timeout (prevents cross-session data bleed) -- SSE reconnect: `handleInit()` clears all pending write state - -**Trade-off:** If a sync block is split across SSE packets and the end marker doesn't arrive within 50ms, the incomplete content is discarded. This prioritizes responsiveness over completeness. In practice this is rare since the server always sends complete `SYNC_START...SYNC_END` pairs and SSE typically delivers them atomically. - -### Files Involved - -| File | Key Functions | -|------|---------------| -| `src/web/server.ts` | `batchTerminalData()`, `flushTerminalBatches()`, `broadcast()` | -| `src/web/public/app.js` | `batchTerminalWrite()`, `extractSyncSegments()`, `flushPendingWrites()`, `flushFlickerBuffer()`, `chunkedTerminalWrite()` | - -### DEC Mode 2026 Compatibility - -Terminals that natively support DEC 2026 will buffer and render atomically. Terminals that don't support it ignore the escape sequences harmlessly. xterm.js doesn't support DEC 2026 natively, so the client implements its own buffering by parsing the markers. - -**Supporting terminals:** WezTerm, Kitty, Ghostty, iTerm2 3.5+, Windows Terminal, VSCode terminal +See `docs/terminal-anti-flicker.md` for full implementation details (adaptive batching, DEC 2026 markers, edge cases). ## Resource Limits @@ -396,6 +271,7 @@ Use `LRUMap` for bounded caches with eviction, `StaleExpirationMap` for TTL-base | **Respawn state machine** | `docs/respawn-state-machine.md` | | **Ralph Loop guide** | `docs/ralph-wiggum-guide.md` | | **Claude Code hooks** | `docs/claude-code-hooks-reference.md` | +| **Terminal anti-flicker** | `docs/terminal-anti-flicker.md` | | **Browser/E2E testing** | `docs/browser-testing-guide.md` | | **API routes** | `src/web/server.ts:buildServer()` or README.md (full endpoint tables) | | **SSE events** | Search `broadcast(` in `server.ts` | diff --git a/docs/terminal-anti-flicker.md b/docs/terminal-anti-flicker.md new file mode 100644 index 00000000..0d69781d --- /dev/null +++ b/docs/terminal-anti-flicker.md @@ -0,0 +1,138 @@ +# Terminal Anti-Flicker System + +Claude Code uses [Ink](https://github.com/vadimdemedes/ink) (React for terminals), which redraws the entire screen on every state change. Without special handling, users see constant flickering. Claudeman implements a 6-layer anti-flicker pipeline. + +## Pipeline Overview + +``` +PTY Output → Server Batching → DEC 2026 Wrap → SSE → Client rAF → Sync Parser → xterm.js +``` + +| Layer | Location | Technique | Latency | +|-------|----------|-----------|---------| +| **1. Server Batching** | `server.ts:batchTerminalData()` | Adaptive 16-50ms collection window | 16-50ms | +| **2. DEC Mode 2026** | `server.ts:flushTerminalBatches()` | Wraps with `\x1b[?2026h`...`\x1b[?2026l` | 0ms | +| **3. SSE Broadcast** | `server.ts:broadcast()` | JSON serialize once, send to all clients | 0ms | +| **4. Client rAF** | `app.js:batchTerminalWrite()` | `requestAnimationFrame` batching | 0-16ms | +| **5. Sync Block Parser** | `app.js:extractSyncSegments()` | Strips DEC 2026 markers, waits for complete blocks | 0-50ms | +| **6. Chunked Loading** | `app.js:chunkedTerminalWrite()` | 64KB/frame for large buffers | variable | + +## Server-Side Implementation (`server.ts`) + +### Constants + +```typescript +const TERMINAL_BATCH_INTERVAL = 16; // Base: 60fps +const BATCH_FLUSH_THRESHOLD = 32 * 1024; // Flush immediately if >32KB +const DEC_SYNC_START = '\x1b[?2026h'; // Begin synchronized update +const DEC_SYNC_END = '\x1b[?2026l'; // End synchronized update +``` + +### Adaptive Batching (`batchTerminalData()`) + +- Tracks event frequency per session via `lastTerminalEventTime` Map +- Event gap <10ms → 50ms batch window (rapid-fire Ink redraws) +- Event gap <20ms → 32ms batch window +- Otherwise → 16ms (60fps) +- Flushes immediately if batch exceeds 32KB for responsiveness + +### Flush Logic (`flushTerminalBatches()`) + +```typescript +const syncData = DEC_SYNC_START + data + DEC_SYNC_END; +this.broadcast('session:terminal', { id: sessionId, data: syncData }); +``` + +## Client-Side Implementation (`app.js`) + +### `batchTerminalWrite(data)` + +1. Checks if flicker filter is enabled (optional, per-session) +2. If flicker filter active: buffers screen-clear patterns (`ESC[2J`, `ESC[H ESC[J`, `ESC[nA`) +3. Accumulates data in `pendingWrites` +4. Schedules `requestAnimationFrame` if not already scheduled +5. On rAF callback: checks for incomplete sync blocks (start without end) +6. If incomplete: waits up to 50ms via `syncWaitTimeout` +7. Calls `flushPendingWrites()` when complete + +### `extractSyncSegments(data)` + +- Parses DEC 2026 markers, returns array of content segments +- Content before sync blocks returned as-is +- Content inside sync blocks returned without markers +- Incomplete blocks (start without end) returned with marker for next chunk + +### `flushPendingWrites()` + +```javascript +const segments = extractSyncSegments(this.pendingWrites); +this.pendingWrites = ''; // Clear before writing +for (const segment of segments) { + if (segment && !segment.startsWith(DEC_SYNC_START)) { + terminal.write(segment); // Skip incomplete blocks (start with marker) + } +} +``` + +Note: Segments starting with `DEC_SYNC_START` are incomplete blocks awaiting more data. These are skipped (discarded if timeout forces flush). + +### `chunkedTerminalWrite(buffer, chunkSize=128KB)` + +- For large buffer restoration (session switch, reconnect) +- Writes 128KB per `requestAnimationFrame` to avoid UI jank +- Strips any embedded DEC 2026 markers from historical data + +### `selectSession()` Optimizations + +- Starts buffer fetch immediately before other setup +- Shows "Loading session..." indicator while fetching +- Parallelizes session attach with buffer fetch +- Fire-and-forget resize (doesn't block tab switch) + +## Optional Flicker Filter + +Per-session toggle via Session Settings. Adds ~50ms latency but eliminates remaining flicker on problematic terminals. + +### Detection Patterns + +- `ESC[2J` — Clear entire screen +- `ESC[H ESC[J` — Cursor home + clear to end +- `ESC[?25l ESC[H` — Hide cursor + home (Ink pattern) +- `ESC[nA` (n≥1) — Cursor up (Ink line redraw) + +When detected, buffers 50ms of subsequent output before flushing atomically. + +## Latency Analysis + +| Source | Best Case | Worst Case | Notes | +|--------|-----------|------------|-------| +| Server batching | 0ms (flush) | 50ms (rapid events) | Immediate flush if >32KB | +| Sync block wait | 0ms | 50ms | Only if marker split across packets | +| Flicker filter | 0ms (disabled) | 50ms (enabled) | Optional per-session | +| rAF scheduling | 0ms | 16ms | Display refresh sync | +| **Total** | **0ms** | **~115ms** | Worst case rare in practice | + +**Typical latency:** 16-32ms (server batch + rAF) + +## Edge Cases + +- **Incomplete sync blocks**: 50ms timeout forces flush (content discarded to prevent freeze) +- **Large buffers**: Chunked writing prevents UI freeze +- **Server shutdown**: Skips batching via `_isStopping` flag +- **Session switch**: Clears flicker filter state, pending writes, and sync timeout (prevents cross-session data bleed) +- **SSE reconnect**: `handleInit()` clears all pending write state + +**Trade-off:** If a sync block is split across SSE packets and the end marker doesn't arrive within 50ms, the incomplete content is discarded. This prioritizes responsiveness over completeness. In practice this is rare since the server always sends complete `SYNC_START...SYNC_END` pairs and SSE typically delivers them atomically. + +## DEC Mode 2026 Compatibility + +Terminals that natively support DEC 2026 will buffer and render atomically. Terminals that don't support it ignore the escape sequences harmlessly. xterm.js doesn't support DEC 2026 natively, so the client implements its own buffering by parsing the markers. + +**Supporting terminals:** WezTerm, Kitty, Ghostty, iTerm2 3.5+, Windows Terminal, VSCode terminal + +## Files Involved + +| File | Key Functions | +|------|---------------| +| `src/web/server.ts` | `batchTerminalData()`, `flushTerminalBatches()`, `broadcast()` | +| `src/web/public/app.js` | `batchTerminalWrite()`, `extractSyncSegments()`, `flushPendingWrites()`, `flushFlickerBuffer()`, `chunkedTerminalWrite()` | diff --git a/package.json b/package.json index fc819427..5b1de108 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1476", + "version": "0.1477", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "type": "module", "main": "dist/index.js", diff --git a/src/ai-checker-base.ts b/src/ai-checker-base.ts index a4db9153..f26b6a53 100644 --- a/src/ai-checker-base.ts +++ b/src/ai-checker-base.ts @@ -31,6 +31,28 @@ import { EventEmitter } from 'node:events'; import { getAugmentedPath } from './session.js'; import { ANSI_ESCAPE_PATTERN_SIMPLE } from './utils/index.js'; +// ========== Security Validation ========== + +/** + * Validates that a model name is safe for shell use. + * Model names should only contain alphanumeric characters, hyphens, underscores, and dots. + */ +function isValidModelName(model: string): boolean { + if (!model || typeof model !== 'string') return false; + // Allow: alphanumeric, hyphens, underscores, dots, slashes (for model paths like claude/opus-4.5) + // Max length 100 to prevent abuse + return /^[a-zA-Z0-9._/-]+$/.test(model) && model.length <= 100; +} + +/** + * Validates that a screen name is safe for shell use. + * Screen names should only contain alphanumeric characters, hyphens, and underscores. + */ +function isValidScreenName(screenName: string): boolean { + if (!screenName || typeof screenName !== 'string') return false; + return /^[a-zA-Z0-9_-]+$/.test(screenName) && screenName.length <= 100; +} + // ========== Types ========== /** Base configuration shared by all AI checkers */ @@ -351,6 +373,11 @@ export abstract class AiCheckerBase< // ========== Private Methods ========== private async runCheck(terminalBuffer: string): Promise { + // Security: Validate model name before use in shell commands + if (!isValidModelName(this.config.model)) { + throw new Error(`Invalid model name: ${this.config.model.substring(0, 50)}`); + } + // Prepare the terminal buffer (strip ANSI, trim to maxContextChars) const stripped = terminalBuffer.replace(ANSI_ESCAPE_PATTERN_SIMPLE, ''); const trimmed = stripped.length > this.config.maxContextChars @@ -367,6 +394,11 @@ export abstract class AiCheckerBase< this.checkPromptFile = join(tmpdir(), `${this.tempFilePrefix}-prompt-${shortId}-${timestamp}.txt`); this.checkScreenName = `${this.screenNamePrefix}${shortId}`; + // Security: Validate screen name before use in shell commands + if (!isValidScreenName(this.checkScreenName)) { + throw new Error(`Invalid screen name generated: ${this.checkScreenName.substring(0, 50)}`); + } + // Ensure output temp file exists (empty) so we can poll it writeFileSync(this.checkTempFile, ''); @@ -375,16 +407,17 @@ export abstract class AiCheckerBase< writeFileSync(this.checkPromptFile, prompt); // Build the command - read prompt from file via stdin to avoid argument size limits - const modelArg = `--model ${this.config.model}`; + // Quote model name to prevent command injection (model names should be simple alphanumeric but be safe) + const modelArg = `--model "${this.config.model.replace(/"/g, '\\"')}"`; const augmentedPath = getAugmentedPath(); const claudeCmd = `cat "${this.checkPromptFile}" | claude -p ${modelArg} --output-format text`; const fullCmd = `export PATH="${augmentedPath}"; ${claudeCmd} > "${this.checkTempFile}" 2>&1; echo "${this.doneMarker}" >> "${this.checkTempFile}"; rm -f "${this.checkPromptFile}"`; // Spawn screen try { - // Kill any leftover screen with this name first + // Kill any leftover screen with this name first (screen name already validated above) try { - execSync(`screen -X -S ${this.checkScreenName} quit 2>/dev/null`, { timeout: 3000 }); + execSync(`screen -X -S "${this.checkScreenName}" quit 2>/dev/null`, { timeout: 3000 }); } catch { // No existing screen, that's fine } @@ -407,9 +440,12 @@ export abstract class AiCheckerBase< const startTime = this.checkStartTime; this.checkResolve = resolve; + // Guard flag to prevent both poll and timeout from resolving (race condition) + let resolved = false; + this.checkPollTimer = setInterval(() => { - if (this.checkCancelled) { - // Cancel was already handled by cancel() calling resolve + if (this.checkCancelled || resolved) { + // Cancel or already resolved - stop polling return; } @@ -417,19 +453,21 @@ export abstract class AiCheckerBase< if (!this.checkTempFile || !existsSync(this.checkTempFile)) return; const content = readFileSync(this.checkTempFile, 'utf-8'); if (content.includes(this.doneMarker)) { + resolved = true; // Mark as resolved first to prevent timeout race const durationMs = Date.now() - startTime; const result = this.parseOutput(content, durationMs); this.checkResolve = null; resolve(result); } } catch { - // File might not be ready yet, keep polling + // File might not be ready yet or was deleted during cleanup, keep polling } }, POLL_INTERVAL_MS); // Set timeout this.checkTimeoutTimer = setTimeout(() => { - if (this._status === 'checking' && !this.checkCancelled) { + if (this._status === 'checking' && !this.checkCancelled && !resolved) { + resolved = true; // Mark as resolved first to prevent poll race this.checkResolve = null; reject(new Error(`${this.checkDescription} timed out after ${this.config.checkTimeoutMs}ms`)); } @@ -467,12 +505,21 @@ export abstract class AiCheckerBase< this.checkTimeoutTimer = null; } - // Kill the screen + // Kill the screen with fallback for stubborn processes if (this.checkScreenName) { + const screenName = this.checkScreenName; + // Screen name was validated before use, but still quote for defense-in-depth try { - execSync(`screen -X -S ${this.checkScreenName} quit 2>/dev/null`, { timeout: 3000 }); + execSync(`screen -X -S "${screenName}" quit 2>/dev/null`, { timeout: 2000 }); } catch { - // Screen may already be dead + // First attempt failed - try force kill via pkill as fallback + // Escape regex metacharacters in screen name to prevent pattern injection + const escapedName = screenName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + try { + execSync(`pkill -f "SCREEN.*${escapedName}" 2>/dev/null`, { timeout: 1000 }); + } catch { + // Screen may already be dead or no matching process + } } this.checkScreenName = null; } diff --git a/src/file-stream-manager.ts b/src/file-stream-manager.ts index d1a9bf13..187e1136 100644 --- a/src/file-stream-manager.ts +++ b/src/file-stream-manager.ts @@ -169,7 +169,9 @@ export class FileStreamManager extends EventEmitter { error: `File too large (${Math.round(stats.size / 1024 / 1024)}MB > ${MAX_FILE_SIZE / 1024 / 1024}MB limit)`, }; } - } catch { + } catch (err) { + const errorCode = err instanceof Error && 'code' in err ? (err as NodeJS.ErrnoException).code : 'UNKNOWN'; + console.warn(`[FileStreamManager] Failed to stat file "${absolutePath}" (${errorCode}):`, err instanceof Error ? err.message : String(err)); return { success: false, error: 'File not found or not accessible' }; } @@ -250,17 +252,26 @@ export class FileStreamManager extends EventEmitter { stream.active = false; + // Remove all event listeners to prevent memory leaks (closures hold references) + stream.process.stdout?.removeAllListeners(); + stream.process.stderr?.removeAllListeners(); + stream.process.removeAllListeners(); + // Kill the tail process try { stream.process.kill('SIGTERM'); // Force kill after 1 second if still running - setTimeout(() => { + const forceKillTimer = setTimeout(() => { try { stream.process.kill('SIGKILL'); } catch { // Already dead } }, 1000); + // Clear the force kill timer if process exits naturally + stream.process.once('exit', () => { + clearTimeout(forceKillTimer); + }); } catch { // Process may have already exited } diff --git a/src/plan-orchestrator.ts b/src/plan-orchestrator.ts index 3ddfb83c..1ec8b3a1 100644 --- a/src/plan-orchestrator.ts +++ b/src/plan-orchestrator.ts @@ -253,15 +253,18 @@ export class PlanOrchestrator { return md; } - cancel(): void { + async cancel(): Promise { this.cancelled = true; + // Stop all running sessions and await cleanup to prevent PTY process leaks + const stopPromises: Promise[] = []; for (const session of this.runningSessions) { - try { - session.stop(); - } catch (err) { - console.error('[PlanOrchestrator] Failed to stop session during cancel:', err); - } + stopPromises.push( + session.stop().catch((err) => { + console.error('[PlanOrchestrator] Failed to stop session during cancel:', err); + }) + ); } + await Promise.all(stopPromises); this.runningSessions.clear(); } diff --git a/src/ralph-loop.ts b/src/ralph-loop.ts index a996886f..23e6baca 100644 --- a/src/ralph-loop.ts +++ b/src/ralph-loop.ts @@ -184,6 +184,11 @@ export class RalphLoop extends EventEmitter { this.tasksCompleted = 0; this.tasksGenerated = 0; + // Re-setup event handlers if they were cleaned up during stop() + if (!this.sessionEventHandlers) { + this.setupEventHandlers(); + } + this.store.setRalphLoopState({ status: 'running', startedAt: this.startedAt, @@ -209,6 +214,9 @@ export class RalphLoop extends EventEmitter { this.loopTimer = null; } + // Clean up event handlers to prevent memory leaks + this.cleanupEventHandlers(); + this.store.setRalphLoopState({ status: 'stopped', lastCheckAt: Date.now(), @@ -251,10 +259,15 @@ export class RalphLoop extends EventEmitter { this.tick() .catch((err) => { - this.emit('error', err); + // Only emit if still running (stop() may have been called during tick()) + if (this._status === 'running') { + this.emit('error', err); + } }) .finally(() => { - if (this._status === 'running') { + // Guard: only reschedule if still running AND no timer is pending + // (prevents race where stop() clears timer between our check and setTimeout) + if (this._status === 'running' && this.loopTimer === null) { this.loopTimer = setTimeout(() => this.runLoop(), this.pollIntervalMs); } }); @@ -263,13 +276,13 @@ export class RalphLoop extends EventEmitter { private async tick(): Promise { this.store.setRalphLoopState({ lastCheckAt: Date.now() }); - // Check for timed out tasks - await this.checkTimeouts(); + // Run independent checks in parallel for better performance + await Promise.all([ + this.checkTimeouts(), + this.assignTasks(), + ]); - // Assign tasks to idle sessions - await this.assignTasks(); - - // Check if we should auto-generate tasks + // Check if we should auto-generate tasks (depends on assignment results) if (this.autoGenerateTasks && this.shouldGenerateTasks()) { await this.generateFollowUpTasks(); } @@ -480,3 +493,8 @@ export function destroyRalphLoop(): void { loopInstance = null; } } + +// Ensure cleanup on process exit (prevents orphaned event handlers and circular references) +process.on('exit', () => { + destroyRalphLoop(); +}); diff --git a/src/ralph-tracker.ts b/src/ralph-tracker.ts index f8aa61ce..9adb99dd 100644 --- a/src/ralph-tracker.ts +++ b/src/ralph-tracker.ts @@ -398,6 +398,55 @@ const COMPLETION_INDICATOR_PATTERNS = [ /project\s+(?:is\s+)?(?:completed?|done|finished)/i, ]; +// ---------- Priority Detection Patterns ---------- +// Pre-compiled for performance; avoids repeated allocation in parsePriority() + +/** P0 (Critical) priority patterns - highest severity issues */ +const P0_PRIORITY_PATTERNS = [ + /\bP0\b|\(P0\)|:?\s*P0\s*:/, // Explicit P0 + /\bCRITICAL\b/, // Critical keyword + /\bBLOCKER\b/, // Blocker + /\bURGENT\b/, // Urgent + /\bSECURITY\b/, // Security issues + /\bCRASH(?:ES|ING)?\b/, // Crash, crashes, crashing + /\bBROKEN\b/, // Broken + /\bDATA\s*LOSS\b/, // Data loss + /\bPRODUCTION\s*(?:DOWN|ISSUE|BUG)\b/, // Production issues + /\bHOTFIX\b/, // Hotfix + /\bSEVERITY\s*1\b/, // Severity 1 +]; + +/** P1 (High) priority patterns - important issues requiring attention */ +const P1_PRIORITY_PATTERNS = [ + /\bP1\b|\(P1\)|:?\s*P1\s*:/, // Explicit P1 + /\bHIGH\s*PRIORITY\b/, // High priority + /\bIMPORTANT\b/, // Important + /\bBUG\b/, // Bug + /\bFIX\b/, // Fix (as task type) + /\bERROR\b/, // Error + /\bFAIL(?:S|ED|ING|URE)?\b/, // Fail variants + /\bREGRESSION\b/, // Regression + /\bMUST\s*(?:HAVE|FIX|DO)\b/, // Must have/fix/do + /\bSEVERITY\s*2\b/, // Severity 2 + /\bREQUIRED\b/, // Required +]; + +/** P2 (Medium) priority patterns - lower priority improvements */ +const P2_PRIORITY_PATTERNS = [ + /\bP2\b|\(P2\)|:?\s*P2\s*:/, // Explicit P2 + /\bNICE\s*TO\s*HAVE\b/, // Nice to have + /\bLOW\s*PRIORITY\b/, // Low priority + /\bREFACTOR\b/, // Refactor + /\bCLEANUP\b/, // Cleanup + /\bIMPROVE(?:MENT)?\b/, // Improve/Improvement + /\bOPTIMIZ(?:E|ATION)\b/, // Optimize/Optimization + /\bCONSIDER\b/, // Consider + /\bWOULD\s*BE\s*NICE\b/, // Would be nice + /\bENHANCE(?:MENT)?\b/, // Enhance/Enhancement + /\bTECH(?:NICAL)?\s*DEBT\b/, // Tech debt + /\bDOCUMENT(?:ATION)?\b/, // Documentation +]; + // ========== Event Types ========== /** @@ -553,6 +602,8 @@ export class RalphTracker extends EventEmitter { /** File watcher for @fix_plan.md */ private _fixPlanWatcher: FSWatcher | null = null; + /** Error handler for FSWatcher (stored for cleanup to prevent memory leak) */ + private _fixPlanWatcherErrorHandler: ((err: Error) => void) | null = null; /** Debounce timer for file change events */ private _fixPlanReloadTimer: NodeJS.Timeout | null = null; @@ -610,8 +661,8 @@ export class RalphTracker extends EventEmitter { /** Whether stall warning has been emitted */ private _iterationStallWarned: boolean = false; - /** Alternate completion phrases (P1-003: multi-phrase support) */ - private _alternateCompletionPhrases: string[] = []; + /** Alternate completion phrases (P1-003: multi-phrase support) - Set for O(1) lookup */ + private _alternateCompletionPhrases: Set = new Set(); // ========== P1-009: Progress Estimation ========== @@ -644,9 +695,9 @@ export class RalphTracker extends EventEmitter { * @param phrase - Additional phrase that can trigger completion */ addAlternateCompletionPhrase(phrase: string): void { - if (!this._alternateCompletionPhrases.includes(phrase)) { - this._alternateCompletionPhrases.push(phrase); - this._loopState.alternateCompletionPhrases = [...this._alternateCompletionPhrases]; + if (!this._alternateCompletionPhrases.has(phrase)) { + this._alternateCompletionPhrases.add(phrase); + this._loopState.alternateCompletionPhrases = Array.from(this._alternateCompletionPhrases); this.emit('loopUpdate', this.loopState); } } @@ -656,10 +707,8 @@ export class RalphTracker extends EventEmitter { * @param phrase - Phrase to remove */ removeAlternateCompletionPhrase(phrase: string): void { - const index = this._alternateCompletionPhrases.indexOf(phrase); - if (index !== -1) { - this._alternateCompletionPhrases.splice(index, 1); - this._loopState.alternateCompletionPhrases = [...this._alternateCompletionPhrases]; + if (this._alternateCompletionPhrases.delete(phrase)) { + this._loopState.alternateCompletionPhrases = Array.from(this._alternateCompletionPhrases); this.emit('loopUpdate', this.loopState); } } @@ -785,6 +834,15 @@ export class RalphTracker extends EventEmitter { this.handleFixPlanChange(); }); } + // Add error handler to prevent unhandled errors and clean up on failure + // Store handler reference for proper cleanup in stopWatchingFixPlan() + if (this._fixPlanWatcher) { + this._fixPlanWatcherErrorHandler = (err: Error) => { + console.log(`[RalphTracker] FSWatcher error for @fix_plan.md: ${err.message}`); + this.stopWatchingFixPlan(); + }; + this._fixPlanWatcher.on('error', this._fixPlanWatcherErrorHandler); + } } catch (err) { console.log(`[RalphTracker] Could not watch @fix_plan.md: ${err}`); } @@ -810,6 +868,11 @@ export class RalphTracker extends EventEmitter { */ stopWatchingFixPlan(): void { if (this._fixPlanWatcher) { + // Remove error handler before closing to prevent memory leak + if (this._fixPlanWatcherErrorHandler) { + this._fixPlanWatcher.off('error', this._fixPlanWatcherErrorHandler); + this._fixPlanWatcherErrorHandler = null; + } this._fixPlanWatcher.close(); this._fixPlanWatcher = null; } @@ -1448,7 +1511,8 @@ export class RalphTracker extends EventEmitter { // Check if the count matches our todo count (e.g., "All 8 files created") const countMatch = line.match(ALL_COUNT_PATTERN); - const mentionedCount = countMatch ? parseInt(countMatch[1]) : null; + const parsedCount = countMatch ? parseInt(countMatch[1], 10) : NaN; + const mentionedCount = Number.isNaN(parsedCount) ? null : parsedCount; const todoCount = this._todos.size; // If a count is mentioned, it should match our todo count (within reason) @@ -1494,7 +1558,8 @@ export class RalphTracker extends EventEmitter { // Only act on explicit task number references like "Task 8 is done" const taskNumMatch = line.match(/task\s*#?(\d+)/i); if (taskNumMatch) { - const taskNum = parseInt(taskNumMatch[1]); + const taskNum = parseInt(taskNumMatch[1], 10); + if (Number.isNaN(taskNum)) return; // Find the nth todo (by order) and mark it complete let count = 0; for (const [_id, todo] of this._todos) { @@ -1850,8 +1915,8 @@ export class RalphTracker extends EventEmitter { // Check for max iterations setting const maxIterMatch = line.match(MAX_ITERATIONS_PATTERN); if (maxIterMatch) { - const maxIter = parseInt(maxIterMatch[1]); - if (!isNaN(maxIter) && maxIter > 0) { + const maxIter = parseInt(maxIterMatch[1], 10); + if (!Number.isNaN(maxIter) && maxIter > 0) { this._loopState.maxIterations = maxIter; this._loopState.lastActivity = Date.now(); // Use debounced emit for settings changes @@ -1863,10 +1928,11 @@ export class RalphTracker extends EventEmitter { const iterMatch = line.match(ITERATION_PATTERN); if (iterMatch) { // Pattern captures: group 1&2 for "Iteration X/Y", group 3&4 for "[X/Y]" - const currentIter = parseInt(iterMatch[1] || iterMatch[3]); - const maxIter = iterMatch[2] || iterMatch[4] ? parseInt(iterMatch[2] || iterMatch[4]) : null; + const currentIter = parseInt(iterMatch[1] || iterMatch[3], 10); + const maxIterStr = iterMatch[2] || iterMatch[4]; + const maxIter = maxIterStr ? parseInt(maxIterStr, 10) : null; - if (!isNaN(currentIter)) { + if (!Number.isNaN(currentIter)) { this.activateLoopIfNeeded(); // Track iteration changes for stall detection if (currentIter !== this._lastObservedIteration) { @@ -1892,7 +1958,7 @@ export class RalphTracker extends EventEmitter { } } this._loopState.cycleCount = currentIter; - if (maxIter !== null && !isNaN(maxIter)) { + if (maxIter !== null && !Number.isNaN(maxIter)) { this._loopState.maxIterations = maxIter; } this._loopState.lastActivity = Date.now(); @@ -1913,8 +1979,8 @@ export class RalphTracker extends EventEmitter { // Check for cycle count (legacy pattern) const cycleMatch = line.match(CYCLE_PATTERN); if (cycleMatch) { - const cycleNum = parseInt(cycleMatch[1] || cycleMatch[2]); - if (!isNaN(cycleNum) && cycleNum > this._loopState.cycleCount) { + const cycleNum = parseInt(cycleMatch[1] || cycleMatch[2], 10); + if (!Number.isNaN(cycleNum) && cycleNum > this._loopState.cycleCount) { this._loopState.cycleCount = cycleNum; this._loopState.lastActivity = Date.now(); // Use debounced emit for cycle updates @@ -2119,68 +2185,23 @@ export class RalphTracker extends EventEmitter { private parsePriority(content: string): RalphTodoPriority { const upper = content.toUpperCase(); - // P0 patterns - Critical issues - const p0Patterns = [ - /\bP0\b|\(P0\)|:?\s*P0\s*:/, // Explicit P0 - /\bCRITICAL\b/, // Critical keyword - /\bBLOCKER\b/, // Blocker - /\bURGENT\b/, // Urgent - /\bSECURITY\b/, // Security issues - /\bCRASH(?:ES|ING)?\b/, // Crash, crashes, crashing - /\bBROKEN\b/, // Broken - /\bDATA\s*LOSS\b/, // Data loss - /\bPRODUCTION\s*(?:DOWN|ISSUE|BUG)\b/, // Production issues - /\bHOTFIX\b/, // Hotfix - /\bSEVERITY\s*1\b/, // Severity 1 - ]; - - // P1 patterns - High priority issues - const p1Patterns = [ - /\bP1\b|\(P1\)|:?\s*P1\s*:/, // Explicit P1 - /\bHIGH\s*PRIORITY\b/, // High priority - /\bIMPORTANT\b/, // Important - /\bBUG\b/, // Bug - /\bFIX\b/, // Fix (as task type) - /\bERROR\b/, // Error - /\bFAIL(?:S|ED|ING|URE)?\b/, // Fail variants - /\bREGRESSION\b/, // Regression - /\bMUST\s*(?:HAVE|FIX|DO)\b/, // Must have/fix/do - /\bSEVERITY\s*2\b/, // Severity 2 - /\bREQUIRED\b/, // Required - ]; - - // P2 patterns - Lower priority - const p2Patterns = [ - /\bP2\b|\(P2\)|:?\s*P2\s*:/, // Explicit P2 - /\bNICE\s*TO\s*HAVE\b/, // Nice to have - /\bLOW\s*PRIORITY\b/, // Low priority - /\bREFACTOR\b/, // Refactor - /\bCLEANUP\b/, // Cleanup - /\bIMPROVE(?:MENT)?\b/, // Improve/Improvement - /\bOPTIMIZ(?:E|ATION)\b/, // Optimize/Optimization - /\bCONSIDER\b/, // Consider - /\bWOULD\s*BE\s*NICE\b/, // Would be nice - /\bENHANCE(?:MENT)?\b/, // Enhance/Enhancement - /\bTECH(?:NICAL)?\s*DEBT\b/, // Tech debt - /\bDOCUMENT(?:ATION)?\b/, // Documentation - ]; - // Check P0 first (highest priority wins) - for (const pattern of p0Patterns) { + // Uses pre-compiled module-level patterns for performance + for (const pattern of P0_PRIORITY_PATTERNS) { if (pattern.test(upper)) { return 'P0'; } } // Check P1 - for (const pattern of p1Patterns) { + for (const pattern of P1_PRIORITY_PATTERNS) { if (pattern.test(upper)) { return 'P1'; } } // Check P2 - for (const pattern of p2Patterns) { + for (const pattern of P2_PRIORITY_PATTERNS) { if (pattern.test(upper)) { return 'P2'; } @@ -2281,12 +2302,17 @@ export class RalphTracker extends EventEmitter { return; } - // Add new todo - if (this._todos.size >= MAX_TODOS_PER_SESSION) { - // Remove oldest todo to make room + // Add new todo with guaranteed eviction if at capacity + // Use while loop to ensure we always have room (handles edge case where findOldestTodo returns undefined) + while (this._todos.size >= MAX_TODOS_PER_SESSION) { const oldest = this.findOldestTodo(); if (oldest) { this._todos.delete(oldest.id); + } else { + // Safety valve: if somehow no oldest found, clear a random entry + const firstKey = this._todos.keys().next().value; + if (firstKey) this._todos.delete(firstKey); + else break; // Map is empty somehow, exit loop } } @@ -2985,7 +3011,7 @@ export class RalphTracker extends EventEmitter { const tasksMatch = trimmedLine.match(RALPH_TASKS_COMPLETED_PATTERN); if (tasksMatch) { const value = parseInt(tasksMatch[1], 10); - if (!isNaN(value) && value >= 0) { + if (!Number.isNaN(value) && value >= 0) { block.tasksCompletedThisLoop = value; } else { parseErrors.push(`Invalid TASKS_COMPLETED_THIS_LOOP value: "${tasksMatch[1]}". Expected: non-negative integer`); @@ -2997,7 +3023,7 @@ export class RalphTracker extends EventEmitter { const filesMatch = trimmedLine.match(RALPH_FILES_MODIFIED_PATTERN); if (filesMatch) { const value = parseInt(filesMatch[1], 10); - if (!isNaN(value) && value >= 0) { + if (!Number.isNaN(value) && value >= 0) { block.filesModified = value; } else { parseErrors.push(`Invalid FILES_MODIFIED value: "${filesMatch[1]}". Expected: non-negative integer`); diff --git a/src/respawn-controller.ts b/src/respawn-controller.ts index 14b6aaa7..20bfbebe 100644 --- a/src/respawn-controller.ts +++ b/src/respawn-controller.ts @@ -43,6 +43,7 @@ import { BufferAccumulator } from './utils/buffer-accumulator.js'; import { ANSI_ESCAPE_PATTERN_SIMPLE, TOKEN_PATTERN, + assertNever, } from './utils/index.js'; import { MAX_RESPAWN_BUFFER_SIZE, @@ -1323,9 +1324,14 @@ export class RespawnController extends EventEmitter { * * If in 'watching' state, immediately checks for idle condition. * Otherwise, continues from current state. + * Re-setups terminal listener if it was removed (e.g., after stop()). */ resume(): void { this.log('Resuming respawn'); + // Re-setup terminal listener if it was removed (robustness for stop->resume case) + if (!this.terminalHandler) { + this.setupTerminalListener(); + } if (this._state === 'watching') { this.checkIdleAndMaybeStart(); } @@ -1410,6 +1416,7 @@ export class RespawnController extends EventEmitter { // In waiting states, also use confirmation timer (same detection logic) // This ensures we wait for Claude to finish before proceeding + // Note: 'watching' is already handled above and returns early switch (this._state) { case 'waiting_update': this.startStepConfirmTimer('update'); @@ -1423,6 +1430,19 @@ export class RespawnController extends EventEmitter { case 'waiting_kickstart': this.startStepConfirmTimer('kickstart'); break; + // Non-waiting states: completion message is ignored + case 'confirming_idle': + case 'ai_checking': + case 'sending_update': + case 'sending_clear': + case 'sending_init': + case 'monitoring_init': + case 'sending_kickstart': + case 'stopped': + // Completion message during these states is ignored + break; + default: + assertNever(this._state, `Unhandled RespawnState in completion detection: ${this._state}`); } return; } @@ -1512,6 +1532,19 @@ export class RespawnController extends EventEmitter { case 'waiting_kickstart': this.startStepConfirmTimer('kickstart'); break; + // Non-waiting states: prompt detection is informational only + case 'watching': + case 'confirming_idle': + case 'ai_checking': + case 'sending_update': + case 'sending_clear': + case 'sending_init': + case 'sending_kickstart': + case 'stopped': + // Prompt detection during these states doesn't trigger action + break; + default: + assertNever(this._state, `Unhandled RespawnState in prompt detection: ${this._state}`); } } } @@ -3243,6 +3276,12 @@ export class RespawnController extends EventEmitter { case 'error': agg.errorCycles++; break; + case 'cancelled': + // Cancelled cycles don't count towards any specific category + // but are still counted in totalCycles + break; + default: + assertNever(metrics.outcome, `Unhandled CycleOutcome: ${metrics.outcome}`); } // Recalculate averages using all recent metrics diff --git a/src/screen-manager.ts b/src/screen-manager.ts index 1e1b5fe7..275302fc 100644 --- a/src/screen-manager.ts +++ b/src/screen-manager.ts @@ -136,6 +136,10 @@ function isValidPath(path: string): boolean { path.includes('"') || path.includes('\n') || path.includes('\r')) { return false; } + // Check for path traversal attempts (prevents escaping workingDir) + if (path.includes('..')) { + return false; + } return SAFE_PATH_PATTERN.test(path); } @@ -268,8 +272,9 @@ export class ScreenManager extends EventEmitter { // Base command for the mode (just the executable, env vars are exported separately) // For Claude sessions: pass --session-id to use the SAME ID as the Claudeman session // This ensures subagents (discovered via file path) can be directly matched to the correct tab + // Note: sessionId is quoted for safety even though it should be a valid UUID const baseCmd = mode === 'claude' - ? `claude --dangerously-skip-permissions --session-id ${sessionId}` + ? `claude --dangerously-skip-permissions --session-id "${sessionId}"` : '$SHELL'; // Apply nice priority if configured @@ -355,7 +360,7 @@ export class ScreenManager extends EventEmitter { timeout: EXEC_TIMEOUT_MS }).trim(); if (output) { - for (const childPid of output.split('\n').map(p => parseInt(p, 10)).filter(p => !isNaN(p))) { + for (const childPid of output.split('\n').map(p => parseInt(p, 10)).filter(p => !Number.isNaN(p))) { pids.push(childPid); // Recursively get grandchildren pids.push(...this.getChildPids(childPid)); @@ -381,7 +386,8 @@ export class ScreenManager extends EventEmitter { // Verify all PIDs are dead, with retry private async verifyProcessesDead(pids: number[], maxWaitMs: number = 1000): Promise { const startTime = Date.now(); - const checkInterval = 50; + // 100ms interval balances responsiveness with CPU usage (was 50ms, too aggressive) + const checkInterval = 100; while (Date.now() - startTime < maxWaitMs) { const aliveCount = pids.filter(pid => this.isProcessAlive(pid)).length; @@ -463,7 +469,7 @@ export class ScreenManager extends EventEmitter { // Strategy 3: Kill screen session by name try { - execSync(`screen -S ${screen.screenName} -X quit`, { + execSync(`screen -S "${screen.screenName}" -X quit`, { timeout: EXEC_TIMEOUT_MS }); } catch { @@ -652,11 +658,11 @@ export class ScreenManager extends EventEmitter { for (const line of pgrepOutput.split('\n')) { const [pidStr, childrenStr] = line.split(':'); const screenPid = parseInt(pidStr, 10); - if (!isNaN(screenPid)) { + if (!Number.isNaN(screenPid)) { const children = (childrenStr || '') .split(',') .map(s => parseInt(s.trim(), 10)) - .filter(n => !isNaN(n) && n > 0); + .filter(n => !Number.isNaN(n) && n > 0); descendantMap.set(screenPid, children); } } @@ -685,7 +691,7 @@ export class ScreenManager extends EventEmitter { const pid = parseInt(parts[0], 10); const rss = parseFloat(parts[1]) || 0; const cpu = parseFloat(parts[2]) || 0; - if (!isNaN(pid)) { + if (!Number.isNaN(pid)) { processStats.set(pid, { rss, cpu }); } } @@ -848,21 +854,24 @@ export class ScreenManager extends EventEmitter { // Send text first (if any) if (escapedText) { - const textCmd = `screen -S ${screen.screenName} -p 0 -X stuff "${escapedText}"`; + const textCmd = `screen -S "${screen.screenName}" -p 0 -X stuff "${escapedText}"`; execSync(textCmd, { encoding: 'utf-8', timeout: EXEC_TIMEOUT_MS }); } // Send carriage return separately (Enter key for Ink) - // Use a synchronous sleep to ensure screen processes the text first + // Use synchronous delay to ensure screen processes the text first if (hasCarriageReturn) { // Delay to let screen process the text before sending Enter // This prevents race conditions where Enter arrives before the text is processed // 100ms is needed for reliability - screen's internal buffering can be slow if (escapedText) { - execSync('sleep 0.1', { timeout: 1000 }); + // Use Atomics.wait for synchronous delay instead of shell command + const sharedBuffer = new SharedArrayBuffer(4); + const sharedArray = new Int32Array(sharedBuffer); + Atomics.wait(sharedArray, 0, 0, 100); } - const crCmd = `screen -S ${screen.screenName} -p 0 -X stuff "$(printf '\\015')"`; + const crCmd = `screen -S "${screen.screenName}" -p 0 -X stuff "$(printf '\\015')"`; // Try up to CR_MAX_ATTEMPTS times with increasing delays let success = false; @@ -873,7 +882,11 @@ export class ScreenManager extends EventEmitter { } catch (crErr) { console.warn(`[ScreenManager] Carriage return attempt ${attempt}/${CR_MAX_ATTEMPTS} failed`); if (attempt < CR_MAX_ATTEMPTS) { - execSync(`sleep 0.${attempt}`, { timeout: 1000 }); // 0.1s, 0.2s delays + // Use synchronous sleep without shell command (security: avoid shell injection vectors) + const sleepMs = attempt * 100; // 100ms, 200ms, 300ms delays + const sharedBuffer = new SharedArrayBuffer(4); + const sharedArray = new Int32Array(sharedBuffer); + Atomics.wait(sharedArray, 0, 0, sleepMs); } } } diff --git a/src/session.ts b/src/session.ts index 92b5ff99..41d6f7e7 100644 --- a/src/session.ts +++ b/src/session.ts @@ -993,6 +993,11 @@ export class Session extends EventEmitter { .replace(CTRL_L_PATTERN, ''); // Remove Ctrl+L if (!data) return; // Skip if only filtered sequences + // Strip ANSI once for all downstream pattern-matching operations + // This avoids redundant regex operations in parseTokensFromStatusLine, + // parseClaudeCodeInfo, parseTaskDescriptionsFromTerminalData, and working detection + const cleanData = data.replace(ANSI_ESCAPE_PATTERN_FULL, ''); + // BufferAccumulator handles auto-trimming when max size exceeded this._terminalBuffer.append(data); this._lastActivityAt = Date.now(); @@ -1000,21 +1005,21 @@ export class Session extends EventEmitter { this.emit('terminal', data); this.emit('output', data); - // Forward to Ralph tracker to detect Ralph loops and todos + // Forward to Ralph tracker to detect Ralph loops and todos (handles own stripping) this._ralphTracker.processTerminalData(data); - // Forward to Bash tool parser to detect file-viewing commands + // Forward to Bash tool parser to detect file-viewing commands (handles own stripping) this._bashToolParser.processTerminalData(data); // Parse token count from status line (e.g., "123.4k tokens" or "5234 tokens") - this.parseTokensFromStatusLine(data); + this.parseTokensFromStatusLine(cleanData); // Parse Claude Code CLI info (version, model, account type) from startup - this.parseClaudeCodeInfo(data); + this.parseClaudeCodeInfo(cleanData); // Parse task descriptions from terminal output (e.g., "Explore(Check files)") // This enables correlating subagent windows with their short descriptions - this.parseTaskDescriptionsFromTerminalData(data); + this.parseTaskDescriptionsFromTerminalData(cleanData); // Detect if Claude is working or at prompt // The prompt line contains "❯" when waiting for input @@ -1042,14 +1047,13 @@ export class Session extends EventEmitter { } // Detect when Claude starts working (thinking, writing, etc) - // Strip ANSI/OSC sequences to avoid false positives from window titles like "3 File Reading Task" - const cleanDataForWorkingCheck = data.replace(ANSI_ESCAPE_PATTERN_FULL, ''); - if (cleanDataForWorkingCheck.includes('Thinking') || cleanDataForWorkingCheck.includes('Writing') || - cleanDataForWorkingCheck.includes('Reading') || cleanDataForWorkingCheck.includes('Running') || - cleanDataForWorkingCheck.includes('⠋') || cleanDataForWorkingCheck.includes('⠙') || - cleanDataForWorkingCheck.includes('⠹') || cleanDataForWorkingCheck.includes('⠸') || - cleanDataForWorkingCheck.includes('⠼') || cleanDataForWorkingCheck.includes('⠴') || - cleanDataForWorkingCheck.includes('⠦') || cleanDataForWorkingCheck.includes('⠧')) { + // Using pre-cleaned data avoids false positives from window titles like "3 File Reading Task" + if (cleanData.includes('Thinking') || cleanData.includes('Writing') || + cleanData.includes('Reading') || cleanData.includes('Running') || + cleanData.includes('⠋') || cleanData.includes('⠙') || + cleanData.includes('⠹') || cleanData.includes('⠸') || + cleanData.includes('⠼') || cleanData.includes('⠴') || + cleanData.includes('⠦') || cleanData.includes('⠧')) { if (!this._isWorking) { this._isWorking = true; this._status = 'busy'; @@ -1504,31 +1508,43 @@ export class Session extends EventEmitter { } /** - * Parse task descriptions from raw terminal data (may contain multiple lines). - * Called from interactive mode's onData handler. + * Parse task descriptions from terminal data (may contain multiple lines). + * Called from interactive mode's onData handler with ANSI-stripped data. + * @param cleanData - Terminal data with ANSI codes already stripped */ - private parseTaskDescriptionsFromTerminalData(data: string): void { + private parseTaskDescriptionsFromTerminalData(cleanData: string): void { // Quick pre-check: skip if no parentheses present - if (!data.includes('(') || !data.includes(')')) return; + if (!cleanData.includes('(') || !cleanData.includes(')')) return; - // Split by newlines and process each line - const lines = data.split(NEWLINE_SPLIT_PATTERN); + // Split by newlines and process each line (data already ANSI-stripped) + const lines = cleanData.split(NEWLINE_SPLIT_PATTERN); for (const line of lines) { - this.parseTaskDescriptionsFromLine(line); + this.parseTaskDescriptionsDirect(line); } } /** - * Parse task descriptions from terminal output. + * Parse task descriptions from terminal output line. * Claude Code outputs Task tool calls as "ToolName(Description)" in the terminal. * We capture these descriptions to use as window titles for subagents. + * Called from processOutput() with potentially non-cleaned data. */ private parseTaskDescriptionsFromLine(line: string): void { // Quick pre-check: skip expensive regex if no common tool patterns present if (!line.includes('(') || !line.includes(')')) return; - // Strip ANSI codes before matching - terminal output has embedded codes like [1mExplore[0m + // Strip ANSI codes - may still be present from processOutput() path const cleanLine = line.replace(ANSI_ESCAPE_PATTERN_FULL, ''); + this.parseTaskDescriptionsDirect(cleanLine); + } + + /** + * Parse task descriptions from a pre-cleaned line (no ANSI codes). + * Internal method used by both parseTaskDescriptionsFromTerminalData and parseTaskDescriptionsFromLine. + */ + private parseTaskDescriptionsDirect(cleanLine: string): void { + // Quick pre-check: skip expensive regex if no common tool patterns present + if (!cleanLine.includes('(') || !cleanLine.includes(')')) return; // Reset regex lastIndex for global pattern TASK_TOOL_PATTERN.lastIndex = 0; @@ -1611,15 +1627,13 @@ export class Session extends EventEmitter { // - Max tokens per session: 500k (Claude's context is ~200k) // - Max delta per update: 100k (prevents sudden jumps from parsing errors) // - Rejects "M" suffix values > 0.5 (500k) to prevent false matches - private parseTokensFromStatusLine(data: string): void { + private parseTokensFromStatusLine(cleanData: string): void { // Quick pre-check: skip expensive regex if "token" not present (performance optimization) - if (!data.includes('token')) return; - - // Remove ANSI escape codes for cleaner parsing (use pre-compiled pattern) - const cleanData = data.replace(ANSI_ESCAPE_PATTERN_FULL, ''); + if (!cleanData.includes('token')) return; // Match patterns: "123.4k tokens", "5234 tokens", "1.2M tokens" // The status line typically shows total tokens like "1.2k tokens" near the prompt + // Note: ANSI codes are already stripped by caller for performance const tokenMatch = cleanData.match(TOKEN_PATTERN); if (tokenMatch) { @@ -1672,16 +1686,15 @@ export class Session extends EventEmitter { // Parse Claude Code CLI info from terminal startup output // Extracts version, model, and account type for display in Claudeman UI - private parseClaudeCodeInfo(data: string): void { + // Note: Expects cleanData with ANSI codes already stripped by caller + private parseClaudeCodeInfo(cleanData: string): void { // Only parse once per session (during startup) if (this._cliInfoParsed) return; // Quick pre-checks - if (!data.includes('Claude') && !data.includes('current:') && !data.includes('Opus') && !data.includes('Sonnet')) { + if (!cleanData.includes('Claude') && !cleanData.includes('current:') && !cleanData.includes('Opus') && !cleanData.includes('Sonnet')) { return; } - - const cleanData = data.replace(ANSI_ESCAPE_PATTERN_FULL, ''); let changed = false; // Match "Claude Code v2.1.27" or "Claude Code vX.Y.Z" @@ -1921,7 +1934,8 @@ export class Session extends EventEmitter { this._status = 'busy'; this._lastActivityAt = Date.now(); this.runPrompt(input).catch(err => { - this.emit('error', err.message); + const errorMsg = err instanceof Error ? err.message : String(err); + this.emit('error', errorMsg); }); } diff --git a/src/state-store.ts b/src/state-store.ts index c97631ae..a3e8e0c0 100644 --- a/src/state-store.ts +++ b/src/state-store.ts @@ -74,7 +74,9 @@ export class StateStore { private ensureDir(): void { const dir = dirname(this.filePath); if (!existsSync(dir)) { - mkdirSync(dir, { recursive: true }); + // Use restrictive permissions (0o700) - owner only can read/write/traverse + // State files may contain sensitive session data + mkdirSync(dir, { recursive: true, mode: 0o700 }); } } @@ -584,7 +586,7 @@ export class StateStore { if (!this.ralphStateDirty) { return; } - this.ralphStateDirty = false; + // Clear dirty flag only on success to enable retry on failure this.ensureDir(); const data = Object.fromEntries(this.ralphStates); // Atomic write: write to temp file, then rename (atomic on POSIX) @@ -594,13 +596,17 @@ export class StateStore { json = JSON.stringify(data, null, 2); } catch (err) { console.error('[StateStore] Failed to serialize Ralph state (circular reference or invalid data):', err); - throw err; + // Keep dirty flag true for retry - don't throw, let caller continue + return; } try { writeFileSync(tempPath, json, 'utf-8'); renameSync(tempPath, this.ralphStatePath); + // Success - clear dirty flag + this.ralphStateDirty = false; } catch (err) { console.error('[StateStore] Failed to write Ralph state file:', err); + // Keep dirty flag true for retry on next save // Try to clean up temp file on error try { if (existsSync(tempPath)) { @@ -609,7 +615,7 @@ export class StateStore { } catch (cleanupErr) { console.warn('[StateStore] Failed to cleanup temp file during Ralph state save error:', cleanupErr); } - throw err; + // Don't throw - let caller continue, retry on next save } } @@ -655,8 +661,28 @@ export class StateStore { /** Flushes all pending saves (main and inner state). Call before shutdown. */ flushAll(): void { - this.saveNow(); - this.saveRalphStatesNow(); + // Save both states, catching errors to ensure both are attempted + let mainError: unknown = null; + let ralphError: unknown = null; + + try { + this.saveNow(); + } catch (err) { + mainError = err; + console.error('[StateStore] Error flushing main state:', err); + } + + try { + this.saveRalphStatesNow(); + } catch (err) { + ralphError = err; + console.error('[StateStore] Error flushing Ralph state:', err); + } + + // Log summary if any errors occurred + if (mainError || ralphError) { + console.warn('[StateStore] flushAll completed with errors - some state may not be persisted'); + } } } diff --git a/src/subagent-watcher.ts b/src/subagent-watcher.ts index 62aad4ee..7a4870e8 100644 --- a/src/subagent-watcher.ts +++ b/src/subagent-watcher.ts @@ -12,7 +12,7 @@ import { createInterface } from 'node:readline'; import { homedir } from 'node:os'; import { join, basename } from 'node:path'; import { execSync } from 'node:child_process'; -import { PENDING_TOOL_CALL_TTL_MS } from './config/map-limits.js'; +import { PENDING_TOOL_CALL_TTL_MS, MAX_PENDING_TOOL_CALLS } from './config/map-limits.js'; // ========== Types ========== @@ -165,6 +165,11 @@ export class SubagentWatcher extends EventEmitter { // Map of agentId -> Map of toolUseId -> { toolName, timestamp } (for linking tool_result to tool_call) // Includes timestamp for TTL-based cleanup of orphaned entries private pendingToolCalls = new Map>(); + // Guard to prevent concurrent liveness checks (prevents duplicate completed events) + private _isCheckingLiveness = false; + // Store error handlers for FSWatchers to enable proper cleanup (prevent memory leaks) + private dirWatcherErrorHandlers = new Map void>(); + private fileWatcherErrorHandlers = new Map void>(); constructor() { super(); @@ -222,19 +227,29 @@ export class SubagentWatcher extends EventEmitter { if (this.livenessInterval) return; this.livenessInterval = setInterval(async () => { - for (const [agentId, info] of this.agentInfo) { - if (info.status === 'active' || info.status === 'idle') { - const alive = await this.checkSubagentAlive(agentId); - if (!alive) { - info.status = 'completed'; - // Clean up pendingToolCalls for this agent to prevent memory leak - this.pendingToolCalls.delete(agentId); - this.emit('subagent:completed', info); + // Guard: prevent concurrent liveness checks (avoids duplicate completed events) + if (this._isCheckingLiveness) return; + this._isCheckingLiveness = true; + + try { + for (const [agentId, info] of this.agentInfo) { + // Re-check status in case another check completed this agent + if (info.status === 'active' || info.status === 'idle') { + const alive = await this.checkSubagentAlive(agentId); + if (!alive && (info.status === 'active' || info.status === 'idle')) { + // Double-check status after async call to prevent race + info.status = 'completed'; + // Clean up pendingToolCalls for this agent to prevent memory leak + this.pendingToolCalls.delete(agentId); + this.emit('subagent:completed', info); + } } } + // Periodically clean up stale completed agents (older than 24 hours) + this.cleanupStaleAgents(); + } finally { + this._isCheckingLiveness = false; } - // Periodically clean up stale completed agents (older than 24 hours) - this.cleanupStaleAgents(); }, LIVENESS_CHECK_MS); } @@ -288,11 +303,25 @@ export class SubagentWatcher extends EventEmitter { this.livenessInterval = null; } + // Remove error handlers before closing watchers to prevent memory leak + for (const [filePath, handler] of this.fileWatcherErrorHandlers) { + const watcher = this.fileWatchers.get(filePath); + if (watcher) watcher.off('error', handler); + } + this.fileWatcherErrorHandlers.clear(); + for (const watcher of this.fileWatchers.values()) { watcher.close(); } this.fileWatchers.clear(); + // Remove error handlers before closing watchers to prevent memory leak + for (const [dir, handler] of this.dirWatcherErrorHandlers) { + const watcher = this.dirWatchers.get(dir); + if (watcher) watcher.off('error', handler); + } + this.dirWatcherErrorHandlers.clear(); + for (const watcher of this.dirWatchers.values()) { watcher.close(); } @@ -560,7 +589,7 @@ export class SubagentWatcher extends EventEmitter { for (const pidStr of pids) { const pid = parseInt(pidStr, 10); - if (isNaN(pid)) continue; + if (Number.isNaN(pid)) continue; try { // Check /proc/{pid}/environ for session ID @@ -899,11 +928,15 @@ export class SubagentWatcher extends EventEmitter { }); // Handle watcher errors to prevent unhandled exceptions - watcher.on('error', (error) => { + // Store handler reference for proper cleanup + const errorHandler = (error: Error) => { this.emit('subagent:error', error instanceof Error ? error : new Error(String(error))); + this.dirWatcherErrorHandlers.delete(dir); this.dirWatchers.delete(dir); this.knownSubagentDirs.delete(dir); - }); + }; + watcher.on('error', errorHandler); + this.dirWatcherErrorHandlers.set(dir, errorHandler); this.dirWatchers.set(dir, watcher); } catch { @@ -974,6 +1007,9 @@ export class SubagentWatcher extends EventEmitter { // Read existing content this.tailFile(filePath, agentId, sessionId, 0).then((position) => { this.filePositions.set(filePath, position); + }).catch((err) => { + // Log but don't throw - non-critical background operation + console.warn(`[SubagentWatcher] Failed to read initial content for ${agentId}:`, err); }); // Watch for changes @@ -1026,10 +1062,14 @@ export class SubagentWatcher extends EventEmitter { }); // Handle watcher errors to prevent unhandled exceptions - watcher.on('error', (error) => { + // Store handler reference for proper cleanup + const errorHandler = (error: Error) => { this.emit('subagent:error', error instanceof Error ? error : new Error(String(error)), agentId); + this.fileWatcherErrorHandlers.delete(filePath); this.fileWatchers.delete(filePath); - }); + }; + watcher.on('error', errorHandler); + this.fileWatcherErrorHandlers.set(filePath, errorHandler); this.fileWatchers.set(filePath, watcher); this.resetIdleTimer(agentId); @@ -1175,7 +1215,21 @@ export class SubagentWatcher extends EventEmitter { if (!this.pendingToolCalls.has(agentId)) { this.pendingToolCalls.set(agentId, new Map()); } - this.pendingToolCalls.get(agentId)!.set(content.id, { + const agentCalls = this.pendingToolCalls.get(agentId)!; + // Enforce size limit to prevent memory leak from rapid tool calls + if (agentCalls.size >= MAX_PENDING_TOOL_CALLS) { + // Evict oldest entry by timestamp + let oldestId: string | null = null; + let oldestTime = Infinity; + for (const [id, call] of agentCalls) { + if (call.timestamp < oldestTime) { + oldestTime = call.timestamp; + oldestId = id; + } + } + if (oldestId) agentCalls.delete(oldestId); + } + agentCalls.set(content.id, { toolName: content.name, timestamp: Date.now(), }); diff --git a/src/task-tracker.ts b/src/task-tracker.ts index 3f851519..33e1b48c 100644 --- a/src/task-tracker.ts +++ b/src/task-tracker.ts @@ -21,6 +21,7 @@ */ import { EventEmitter } from 'node:events'; +import { assertNever } from './utils/index.js'; // ========== Configuration Constants ========== @@ -68,6 +69,54 @@ const COMPLETE_PATTERNS = [ // ========== Type Definitions ========== +/** + * Content block for tool_use messages from Claude. + * Emitted when Claude invokes a tool. + */ +interface ClaudeToolUseBlock { + type: 'tool_use'; + /** Unique identifier for this tool invocation */ + id: string; + /** Name of the tool being invoked */ + name: string; + /** Parameters passed to the tool */ + input?: { + description?: string; + prompt?: string; + subagent_type?: string; + [key: string]: unknown; + }; +} + +/** + * Content block for tool_result messages from Claude. + * Emitted when a tool completes execution. + */ +interface ClaudeToolResultBlock { + type: 'tool_result'; + /** ID of the tool_use this result corresponds to */ + tool_use_id: string; + /** Whether the tool execution resulted in an error */ + is_error?: boolean; + /** Result content (string or structured data) */ + content?: string | unknown; +} + +/** + * Union type for content blocks we care about. + */ +type ClaudeContentBlock = ClaudeToolUseBlock | ClaudeToolResultBlock | { type: string }; + +/** + * Claude JSON message structure. + * This is the format Claude Code outputs for streaming events. + */ +interface ClaudeMessage { + message?: { + content?: ClaudeContentBlock[]; + }; +} + /** * Represents a background task spawned by Claude Code. * @@ -189,14 +238,14 @@ export class TaskTracker extends EventEmitter { * @fires taskCompleted - When a task finishes successfully * @fires taskFailed - When a task finishes with error */ - processMessage(msg: any): void { + processMessage(msg: ClaudeMessage | null | undefined): void { if (!msg || !msg.message?.content) return; for (const block of msg.message.content) { - if (block.type === 'tool_use' && block.name === 'Task') { - this.handleTaskToolUse(block); + if (block.type === 'tool_use' && (block as ClaudeToolUseBlock).name === 'Task') { + this.handleTaskToolUse(block as ClaudeToolUseBlock); } else if (block.type === 'tool_result') { - this.handleToolResult(block); + this.handleToolResult(block as ClaudeToolResultBlock); } } } @@ -256,7 +305,7 @@ export class TaskTracker extends EventEmitter { * @param block - The tool_use content block * @fires taskCreated */ - private handleTaskToolUse(block: any): void { + private handleTaskToolUse(block: ClaudeToolUseBlock): void { const toolUseId = block.id; const params = block.input || {}; @@ -305,7 +354,7 @@ export class TaskTracker extends EventEmitter { * @fires taskCompleted - If result is success * @fires taskFailed - If result is error */ - private handleToolResult(block: any): void { + private handleToolResult(block: ClaudeToolResultBlock): void { const toolUseId = block.tool_use_id; const task = this.tasks.get(toolUseId); @@ -416,7 +465,7 @@ export class TaskTracker extends EventEmitter { * @fires taskCreated */ private createTaskFromTerminal(agentType: string, _context: string): void { - const taskId = `terminal-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; + const taskId = `terminal-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`; const parentId = this.taskStack.length > 0 ? this.taskStack[this.taskStack.length - 1] : null; const task: BackgroundTask = { @@ -547,6 +596,8 @@ export class TaskTracker extends EventEmitter { case 'running': running++; break; case 'completed': completed++; break; case 'failed': failed++; break; + default: + assertNever(task.status, `Unhandled BackgroundTask status: ${task.status}`); } } diff --git a/src/transcript-watcher.ts b/src/transcript-watcher.ts index 43c0b6e1..bc94fd9a 100644 --- a/src/transcript-watcher.ts +++ b/src/transcript-watcher.ts @@ -230,6 +230,17 @@ export class TranscriptWatcher extends EventEmitter { } }); + // Add error handler to prevent unhandled errors and fall back to polling + this.fileWatcher.on('error', (err) => { + this.emit('transcript:error', err as Error); + this.fileWatcher?.close(); + this.fileWatcher = null; + // Fall back to polling on error + if (this._isRunning) { + this.startPolling(); + } + }); + // Initial read this.processNewContent(); } catch (err) { diff --git a/src/types.ts b/src/types.ts index 0618161a..f9e115c5 100644 --- a/src/types.ts +++ b/src/types.ts @@ -763,19 +763,12 @@ export interface HookEventRequest { // ========== API Response Types ========== /** - * Standard API response wrapper + * Standard API response wrapper (discriminated union for type safety) * @template T Type of the data payload */ -export interface ApiResponse { - /** Whether the request succeeded */ - success: boolean; - /** Error message if failed */ - error?: string; - /** Error code for programmatic handling */ - errorCode?: ApiErrorCode; - /** Response data payload */ - data?: T; -} +export type ApiResponse = + | { success: true; data?: T } + | { success: false; error: string; errorCode: ApiErrorCode }; /** * Creates a standardized error response @@ -783,7 +776,7 @@ export interface ApiResponse { * @param details Optional detailed error message * @returns Formatted error response */ -export function createErrorResponse(code: ApiErrorCode, details?: string): ApiResponse { +export function createErrorResponse(code: ApiErrorCode, details?: string): ApiResponse { return { success: false, error: details || ErrorMessages[code], diff --git a/src/utils/index.ts b/src/utils/index.ts index c4a31700..4ac32420 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -33,3 +33,4 @@ export { fuzzyPhraseMatch, todoContentHash, } from './string-similarity.js'; +export { assertNever } from './type-safety.js'; diff --git a/src/utils/lru-map.ts b/src/utils/lru-map.ts index 3c061d69..e7b44b69 100644 --- a/src/utils/lru-map.ts +++ b/src/utils/lru-map.ts @@ -199,10 +199,15 @@ export class LRUMap extends Map { const now = Date.now(); const cutoff = now - maxAge; let evicted = 0; + let deletedNewest = false; // Iterate from oldest to newest for (const [key, value] of super.entries()) { if (getTimestamp(value) < cutoff) { + // Track if we're deleting the newest key + if (key === this._newestKey) { + deletedNewest = true; + } super.delete(key); this.onEvict?.(key, value); evicted++; @@ -213,6 +218,14 @@ export class LRUMap extends Map { } } + // Update _newestKey if we deleted it (find new newest from remaining entries) + if (deletedNewest) { + this._newestKey = undefined; + for (const k of super.keys()) { + this._newestKey = k; // Last one becomes newest + } + } + return evicted; } diff --git a/src/utils/type-safety.ts b/src/utils/type-safety.ts new file mode 100644 index 00000000..eedeb1d8 --- /dev/null +++ b/src/utils/type-safety.ts @@ -0,0 +1,35 @@ +/** + * @fileoverview Type safety utilities for exhaustive checking. + * + * @module utils/type-safety + */ + +/** + * Assert that a value should never occur at runtime. + * Used in switch/case default blocks to ensure exhaustive handling. + * + * If TypeScript sees a code path where `value` is not `never`, + * it will report a compile-time error, catching missing cases. + * + * @param value - The value that should be of type `never` + * @param message - Optional custom error message + * @throws Error if called at runtime (indicates unhandled case) + * + * @example + * type Status = 'active' | 'inactive' | 'pending'; + * function handleStatus(status: Status): void { + * switch (status) { + * case 'active': // handle active + * break; + * case 'inactive': // handle inactive + * break; + * case 'pending': // handle pending + * break; + * default: + * assertNever(status); // Compile error if case is missing + * } + * } + */ +export function assertNever(value: never, message?: string): never { + throw new Error(message ?? `Unexpected value: ${JSON.stringify(value)}`); +} diff --git a/src/web/public/app.js b/src/web/public/app.js index bf3be20d..8a4c053a 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -1444,8 +1444,12 @@ class ClaudemanApp { }; window.addEventListener('resize', throttledResize); - const resizeObserver = new ResizeObserver(throttledResize); - resizeObserver.observe(container); + // Store resize observer for cleanup (prevents memory leak on terminal re-init) + if (this.terminalResizeObserver) { + this.terminalResizeObserver.disconnect(); + } + this.terminalResizeObserver = new ResizeObserver(throttledResize); + this.terminalResizeObserver.observe(container); // Handle keyboard input with batching for rapid keystrokes this._pendingInput = ''; @@ -1860,10 +1864,11 @@ class ClaudemanApp { } }, true); // Use capture phase to handle before terminal - // Token stats click handler + // Token stats click handler (with guard to prevent duplicate handlers on reconnect) const tokenEl = this.$('headerTokens'); - if (tokenEl) { + if (tokenEl && !tokenEl._statsHandlerAttached) { tokenEl.classList.add('clickable'); + tokenEl._statsHandlerAttached = true; tokenEl.addEventListener('click', () => this.openTokenStats()); } @@ -2913,6 +2918,18 @@ class ClaudemanApp { clearInterval(this.notificationManager.titleFlashInterval); this.notificationManager.titleFlashInterval = null; } + // Clear notification manager grouping timeouts (prevents orphaned timers) + if (this.notificationManager?.groupingMap) { + for (const { timeout } of this.notificationManager.groupingMap.values()) { + clearTimeout(timeout); + } + this.notificationManager.groupingMap.clear(); + } + // Disconnect terminal resize observer (prevents memory leak on reconnect) + if (this.terminalResizeObserver) { + this.terminalResizeObserver.disconnect(); + this.terminalResizeObserver = null; + } // Clear any other orphaned timers if (this.planLoadingTimer) { clearInterval(this.planLoadingTimer); diff --git a/src/web/schemas.ts b/src/web/schemas.ts new file mode 100644 index 00000000..05259d6b --- /dev/null +++ b/src/web/schemas.ts @@ -0,0 +1,141 @@ +/** + * @fileoverview Zod validation schemas for API routes + * + * This module contains Zod schemas for validating API request bodies. + * Schemas are used in src/web/server.ts route handlers. + * + * @module web/schemas + */ + +import { z } from 'zod'; + +// ========== Session Routes ========== + +/** + * Schema for POST /api/sessions + * Creates a new session with optional working directory, mode, and name. + */ +export const CreateSessionSchema = z.object({ + workingDir: z.string().optional(), + mode: z.enum(['claude', 'shell']).optional(), + name: z.string().max(100).optional(), +}); + +/** + * Schema for POST /api/sessions/:id/run + * Runs a prompt in a session. + */ +export const RunPromptSchema = z.object({ + prompt: z.string().min(1).max(100000), +}); + +/** + * Schema for POST /api/sessions/:id/input + * Sends input to an interactive session. + */ +export const SessionInputSchema = z.object({ + input: z.string(), + useScreen: z.boolean().optional(), +}); + +/** + * Schema for POST /api/sessions/:id/resize + * Resizes a session's terminal. + */ +export const ResizeSchema = z.object({ + cols: z.number().int().min(1).max(500), + rows: z.number().int().min(1).max(200), +}); + +// ========== Case Routes ========== + +/** + * Schema for POST /api/cases + * Creates a new case folder. + */ +export const CreateCaseSchema = z.object({ + name: z.string().regex(/^[a-zA-Z0-9_-]+$/, 'Invalid case name format. Use only letters, numbers, hyphens, underscores.'), + description: z.string().max(1000).optional(), +}); + +// ========== Quick Start ========== + +/** + * Schema for POST /api/quick-start + * Creates case (if needed) and starts interactive session. + */ +export const QuickStartSchema = z.object({ + caseName: z.string().regex(/^[a-zA-Z0-9_-]+$/, 'Invalid case name format. Use only letters, numbers, hyphens, underscores.').optional(), + mode: z.enum(['claude', 'shell']).optional(), +}); + +// ========== Hook Events ========== + +/** + * Schema for POST /api/hook-event + * Receives Claude Code hook events. + */ +export const HookEventSchema = z.object({ + event: z.enum(['permission_prompt', 'elicitation_dialog', 'idle_prompt', 'stop']), + sessionId: z.string().min(1), + data: z.unknown().optional(), +}); + +// ========== Configuration ========== + +/** + * Schema for respawn configuration (partial updates allowed) + * Used in PUT /api/config and respawn endpoints. + */ +export const RespawnConfigSchema = z.object({ + idleTimeoutMs: z.number().int().min(1000).max(600000).optional(), + updatePrompt: z.string().max(10000).optional(), + interStepDelayMs: z.number().int().min(100).max(60000).optional(), + enabled: z.boolean().optional(), + sendClear: z.boolean().optional(), + sendUpdate: z.boolean().optional(), + sendInit: z.boolean().optional(), + sendKickstart: z.boolean().optional(), + kickstartPrompt: z.string().max(10000).optional(), + aiIdleCheckEnabled: z.boolean().optional(), + aiIdleCheckTimeoutMs: z.number().int().min(10000).max(300000).optional(), + aiIdleCheckModel: z.string().max(100).optional(), + completionConfirmMs: z.number().int().min(1000).max(60000).optional(), + noOutputTimeoutMs: z.number().int().min(5000).max(600000).optional(), + maxIterations: z.number().int().min(0).max(10000).optional(), + stuckStateWarningMs: z.number().int().min(60000).max(3600000).optional(), + autoAcceptEnabled: z.boolean().optional(), + autoAcceptDelayMs: z.number().int().min(1000).max(60000).optional(), + planModeEnabled: z.boolean().optional(), + planCheckTimeoutMs: z.number().int().min(10000).max(300000).optional(), + planCheckModel: z.string().max(100).optional(), +}).strict(); + +/** + * Schema for PUT /api/config + * Updates application configuration with whitelist of allowed fields. + */ +export const ConfigUpdateSchema = z.object({ + pollIntervalMs: z.number().int().min(100).max(60000).optional(), + defaultTimeoutMs: z.number().int().min(1000).max(3600000).optional(), + maxConcurrentSessions: z.number().int().min(1).max(50).optional(), + respawn: RespawnConfigSchema.optional(), +}).strict(); + +/** + * Schema for PUT /api/settings + * User settings with allowed fields only. + */ +export const SettingsUpdateSchema = z.object({ + defaultClaudeMdPath: z.string().max(500).optional(), + lastUsedCase: z.string().max(200).optional(), + // Add other known settings fields as needed +}).passthrough(); // Allow additional fields but validate known ones + +/** + * Schema for POST /api/sessions/:id/input with length limit + */ +export const SessionInputWithLimitSchema = z.object({ + input: z.string().max(100000), // 100KB max input + useScreen: z.boolean().optional(), +}); diff --git a/src/web/server.ts b/src/web/server.ts index 67e7c7b7..cee651ea 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -12,7 +12,7 @@ import Fastify, { FastifyInstance, FastifyReply } from 'fastify'; import fastifyStatic from '@fastify/static'; -import path, { join, dirname, resolve } from 'node:path'; +import path, { join, dirname, resolve, relative, isAbsolute } from 'node:path'; import { fileURLToPath } from 'node:url'; import { existsSync, mkdirSync, writeFileSync, readdirSync, readFileSync, rmSync } from 'node:fs'; import fs from 'node:fs/promises'; @@ -42,15 +42,8 @@ import { getErrorMessage, ApiErrorCode, createErrorResponse, - type CreateSessionRequest, - type RunPromptRequest, - type SessionInputRequest, - type ResizeRequest, - type CreateCaseRequest, - type QuickStartRequest, type CreateScheduledRunRequest, type QuickRunRequest, - type HookEventRequest, type ApiResponse, type SessionResponse, type QuickStartResponse, @@ -60,6 +53,18 @@ import { type ImageDetectedEvent, DEFAULT_NICE_CONFIG, } from '../types.js'; +import { + CreateSessionSchema, + RunPromptSchema, + SessionInputSchema, + ResizeSchema, + CreateCaseSchema, + QuickStartSchema, + HookEventSchema, + ConfigUpdateSchema, + RespawnConfigSchema, +} from './schemas.js'; +import { StaleExpirationMap } from '../utils/index.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -317,15 +322,20 @@ export class WebServer extends EventEmitter { // Terminal batching for performance private terminalBatches: Map = new Map(); private terminalBatchTimer: NodeJS.Timeout | null = null; - // Adaptive batching: track rapid events to extend batch window - private lastTerminalEventTime: Map = new Map(); - private adaptiveBatchInterval: number = TERMINAL_BATCH_INTERVAL; + // Adaptive batching: track rapid events to extend batch window (per-session) + // StaleExpirationMap auto-cleans entries for sessions that stop generating output + private lastTerminalEventTime: StaleExpirationMap = new StaleExpirationMap({ + ttlMs: 5 * 60 * 1000, // 5 minutes - auto-expire stale session timing data + refreshOnGet: false, // Don't refresh on reads, only on explicit sets + }); + // Per-session adaptive batch intervals (sessions with rapid output get longer batches) + private adaptiveBatchIntervals: Map = new Map(); // Scheduled runs cleanup timer private scheduledCleanupTimer: NodeJS.Timeout | null = null; // SSE event batching private outputBatches: Map = new Map(); private outputBatchTimer: NodeJS.Timeout | null = null; - private taskUpdateBatches: Map = new Map(); + private taskUpdateBatches: Map = new Map(); private taskUpdateBatchTimer: NodeJS.Timeout | null = null; // State update batching (reduce expensive toDetailedState() serialization) private stateUpdatePending: Set = new Set(); @@ -551,11 +561,12 @@ export class WebServer extends EventEmitter { }); this.app.put('/api/config', async (req) => { - const body = req.body as Record | null; - if (!body || typeof body !== 'object') { - return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Request body must be a JSON object'); + // Validate request body against schema to prevent arbitrary config injection + const parseResult = ConfigUpdateSchema.safeParse(req.body); + if (!parseResult.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, `Invalid config: ${parseResult.error.message}`); } - this.store.setConfig(body as Partial>); + this.store.setConfig(parseResult.data as Partial>); return { success: true, config: this.store.getConfig() }; }); @@ -635,10 +646,14 @@ export class WebServer extends EventEmitter { this.app.post('/api/sessions', async (req): Promise => { // Prevent unbounded session creation if (this.sessions.size >= MAX_CONCURRENT_SESSIONS) { - return { success: false, error: `Maximum concurrent sessions (${MAX_CONCURRENT_SESSIONS}) reached. Delete some sessions first.` }; + return createErrorResponse(ApiErrorCode.OPERATION_FAILED, `Maximum concurrent sessions (${MAX_CONCURRENT_SESSIONS}) reached. Delete some sessions first.`); } - const body = req.body as CreateSessionRequest & { mode?: 'claude' | 'shell'; name?: string }; + const result = CreateSessionSchema.safeParse(req.body); + if (!result.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, result.error.issues[0]?.message ?? 'Validation failed'); + } + const body = result.data; const workingDir = body.workingDir || process.cwd(); const globalNice = this.getGlobalNiceConfig(); const session = new Session({ @@ -705,7 +720,7 @@ export class WebServer extends EventEmitter { const killScreen = query.killScreen !== 'false'; // Default to true if (!this.sessions.has(id)) { - return { success: false, error: 'Session not found' }; + return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found'); } await this.cleanupSession(id, killScreen); @@ -769,7 +784,7 @@ export class WebServer extends EventEmitter { const session = this.sessions.get(id); if (!session) { - return { success: false, error: 'Session not found' }; + return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found'); } return { @@ -949,9 +964,10 @@ export class WebServer extends EventEmitter { return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Missing path parameter'); } - // Validate path is within working directory (security) + // Validate path is within working directory (security: proper path traversal check) const fullPath = resolve(session.workingDir, filePath); - if (!fullPath.startsWith(session.workingDir)) { + const relativePath = relative(session.workingDir, fullPath); + if (relativePath.startsWith('..') || isAbsolute(relativePath)) { return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Path must be within working directory'); } @@ -978,8 +994,15 @@ export class WebServer extends EventEmitter { }; } - // Read text file with line limit - const maxLines = parseInt(lines || '500', 10); + // Validate file size before reading (DoS protection - prevent memory exhaustion) + const MAX_TEXT_FILE_SIZE = 10 * 1024 * 1024; // 10MB + if (stat.size > MAX_TEXT_FILE_SIZE) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, `File too large (${Math.round(stat.size / 1024 / 1024)}MB > ${MAX_TEXT_FILE_SIZE / 1024 / 1024}MB limit)`); + } + + // Read text file with line limit (bounded to prevent DoS) + const MAX_LINES_LIMIT = 10000; + const maxLines = Math.min(parseInt(lines || '500', 10) || 500, MAX_LINES_LIMIT); const content = await fs.readFile(fullPath, 'utf-8'); const allLines = content.split('\n'); const truncatedContent = allLines.length > maxLines; @@ -1008,23 +1031,32 @@ export class WebServer extends EventEmitter { const session = this.sessions.get(id); if (!session) { - reply.code(404).send({ success: false, error: 'Session not found' }); + reply.code(404).send(createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found')); return; } if (!filePath) { - reply.code(400).send({ success: false, error: 'Missing path parameter' }); + reply.code(400).send(createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Missing path parameter')); return; } - // Validate path is within working directory + // Validate path is within working directory (security: proper path traversal check) const fullPath = resolve(session.workingDir, filePath); - if (!fullPath.startsWith(session.workingDir)) { - reply.code(400).send({ success: false, error: 'Path must be within working directory' }); + const relativePath = relative(session.workingDir, fullPath); + if (relativePath.startsWith('..') || isAbsolute(relativePath)) { + reply.code(400).send(createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Path must be within working directory')); return; } try { + // Validate file size before reading (DoS protection - prevent memory exhaustion) + const MAX_RAW_FILE_SIZE = 50 * 1024 * 1024; // 50MB for raw files + const stat = await fs.stat(fullPath); + if (stat.size > MAX_RAW_FILE_SIZE) { + reply.code(400).send(createErrorResponse(ApiErrorCode.INVALID_INPUT, `File too large (${Math.round(stat.size / 1024 / 1024)}MB > ${MAX_RAW_FILE_SIZE / 1024 / 1024}MB limit)`)); + return; + } + const ext = filePath.split('.').pop()?.toLowerCase() || ''; const mimeTypes: Record = { 'png': 'image/png', 'jpg': 'image/jpeg', 'jpeg': 'image/jpeg', 'gif': 'image/gif', @@ -1038,7 +1070,7 @@ export class WebServer extends EventEmitter { reply.header('Content-Type', mimeTypes[ext] || 'application/octet-stream'); reply.send(content); } catch (err) { - reply.code(500).send({ success: false, error: `Failed to read file: ${getErrorMessage(err)}` }); + reply.code(500).send(createErrorResponse(ApiErrorCode.OPERATION_FAILED, `Failed to read file: ${getErrorMessage(err)}`)); } }); @@ -1049,12 +1081,12 @@ export class WebServer extends EventEmitter { const session = this.sessions.get(id); if (!session) { - reply.code(404).send({ success: false, error: 'Session not found' }); + reply.code(404).send(createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found')); return; } if (!filePath) { - reply.code(400).send({ success: false, error: 'Missing path parameter' }); + reply.code(400).send(createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Missing path parameter')); return; } @@ -1376,7 +1408,11 @@ export class WebServer extends EventEmitter { // Run prompt in session this.app.post('/api/sessions/:id/run', async (req): Promise => { const { id } = req.params as { id: string }; - const { prompt } = req.body as RunPromptRequest; + const result = RunPromptSchema.safeParse(req.body); + if (!result.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, result.error.issues[0]?.message ?? 'Validation failed'); + } + const { prompt } = result.data; const session = this.sessions.get(id); if (!session) { @@ -1455,17 +1491,17 @@ export class WebServer extends EventEmitter { // useScreen: true uses writeViaScreen which is more reliable for programmatic input this.app.post('/api/sessions/:id/input', async (req): Promise => { const { id } = req.params as { id: string }; - const { input, useScreen } = req.body as SessionInputRequest & { useScreen?: boolean }; + const result = SessionInputSchema.safeParse(req.body); + if (!result.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, result.error.issues[0]?.message ?? 'Validation failed'); + } + const { input, useScreen } = result.data; const session = this.sessions.get(id); if (!session) { return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found'); } - if (input === undefined || input === null) { - return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Input is required'); - } - const inputStr = String(input); if (inputStr.length > MAX_INPUT_LENGTH) { return createErrorResponse(ApiErrorCode.INVALID_INPUT, `Input exceeds maximum length (${MAX_INPUT_LENGTH} bytes)`); @@ -1491,16 +1527,18 @@ export class WebServer extends EventEmitter { // Resize session terminal this.app.post('/api/sessions/:id/resize', async (req): Promise => { const { id } = req.params as { id: string }; - const { cols, rows } = req.body as ResizeRequest; + const result = ResizeSchema.safeParse(req.body); + if (!result.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, result.error.issues[0]?.message ?? 'Validation failed'); + } + const { cols, rows } = result.data; const session = this.sessions.get(id); if (!session) { return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found'); } - if (!Number.isInteger(cols) || !Number.isInteger(rows) || cols < 1 || rows < 1) { - return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'cols and rows must be positive integers'); - } + // Note: Zod already validates that cols and rows are positive integers within bounds if (cols > MAX_TERMINAL_COLS || rows > MAX_TERMINAL_ROWS) { return createErrorResponse(ApiErrorCode.INVALID_INPUT, `Terminal dimensions exceed maximum (${MAX_TERMINAL_COLS}x${MAX_TERMINAL_ROWS})`); } @@ -1665,7 +1703,12 @@ export class WebServer extends EventEmitter { // Update respawn configuration (works with or without running controller) this.app.put('/api/sessions/:id/respawn/config', async (req) => { const { id } = req.params as { id: string }; - const config = req.body as Partial; + // Validate respawn config to prevent arbitrary field injection + const parseResult = RespawnConfigSchema.safeParse(req.body); + if (!parseResult.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, `Invalid respawn config: ${parseResult.error.message}`); + } + const config = parseResult.data as Partial; const session = this.sessions.get(id); if (!session) { @@ -1993,7 +2036,7 @@ export class WebServer extends EventEmitter { const run = this.scheduledRuns.get(id); if (!run) { - return { success: false, error: 'Scheduled run not found' }; + return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Scheduled run not found'); } await this.stopScheduledRun(id); @@ -2047,31 +2090,33 @@ export class WebServer extends EventEmitter { } } } - } catch { - // Ignore errors reading linked cases + } catch (err) { + // Log but don't fail - linked cases are optional + console.warn('[Server] Failed to read linked cases:', err); } return cases; }); - this.app.post('/api/cases', async (req): Promise<{ success: boolean; case?: { name: string; path: string }; error?: string }> => { - const { name, description } = req.body as CreateCaseRequest; - - if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) { - return { success: false, error: 'Invalid case name. Use only letters, numbers, hyphens, underscores.' }; + this.app.post('/api/cases', async (req): Promise> => { + const result = CreateCaseSchema.safeParse(req.body); + if (!result.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, result.error.issues[0]?.message ?? 'Validation failed'); } + const { name, description } = result.data; const casePath = join(casesDir, name); - // Security: Path traversal protection - ensure resolved path is within casesDir + // Security: Path traversal protection - use relative path check const resolvedPath = resolve(casePath); const resolvedBase = resolve(casesDir); - if (!resolvedPath.startsWith(resolvedBase + '/') && resolvedPath !== resolvedBase) { - return { success: false, error: 'Invalid case path' }; + const relPath = relative(resolvedBase, resolvedPath); + if (relPath.startsWith('..') || isAbsolute(relPath)) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case path'); } if (existsSync(casePath)) { - return { success: false, error: 'Case already exists' }; + return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'Case already exists'); } try { @@ -2088,22 +2133,22 @@ export class WebServer extends EventEmitter { this.broadcast('case:created', { name, path: casePath }); - return { success: true, case: { name, path: casePath } }; + return { success: true, data: { case: { name, path: casePath } } }; } catch (err) { - return { success: false, error: getErrorMessage(err) }; + return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err)); } }); // Link an existing folder as a case - this.app.post('/api/cases/link', async (req): Promise<{ success: boolean; case?: { name: string; path: string }; error?: string }> => { + this.app.post('/api/cases/link', async (req): Promise> => { const { name, path: folderPath } = req.body as { name: string; path: string }; if (!name || !/^[a-zA-Z0-9_-]+$/.test(name)) { - return { success: false, error: 'Invalid case name. Use only letters, numbers, hyphens, underscores.' }; + return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case name. Use only letters, numbers, hyphens, underscores.'); } if (!folderPath) { - return { success: false, error: 'Folder path is required.' }; + return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Folder path is required.'); } // Expand ~ to home directory @@ -2113,13 +2158,13 @@ export class WebServer extends EventEmitter { // Validate the folder exists if (!existsSync(expandedPath)) { - return { success: false, error: `Folder not found: ${expandedPath}` }; + return createErrorResponse(ApiErrorCode.NOT_FOUND, `Folder not found: ${expandedPath}`); } // Check if case name already exists in casesDir const casePath = join(casesDir, name); if (existsSync(casePath)) { - return { success: false, error: 'A case with this name already exists in claudeman-cases.' }; + return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, 'A case with this name already exists in claudeman-cases.'); } // Load existing linked cases @@ -2135,7 +2180,7 @@ export class WebServer extends EventEmitter { // Check if name is already linked if (linkedCases[name]) { - return { success: false, error: `Case "${name}" is already linked to ${linkedCases[name]}` }; + return createErrorResponse(ApiErrorCode.ALREADY_EXISTS, `Case "${name}" is already linked to ${linkedCases[name]}`); } // Save the linked case @@ -2147,9 +2192,9 @@ export class WebServer extends EventEmitter { } writeFileSync(linkedCasesFile, JSON.stringify(linkedCases, null, 2)); this.broadcast('case:linked', { name, path: expandedPath }); - return { success: true, case: { name, path: expandedPath } }; + return { success: true, data: { case: { name, path: expandedPath } } }; } catch (err) { - return { success: false, error: getErrorMessage(err) }; + return createErrorResponse(ApiErrorCode.OPERATION_FAILED, getErrorMessage(err)); } }); @@ -2276,12 +2321,14 @@ export class WebServer extends EventEmitter { } } - const stats = { - total: todos.length, - pending: todos.filter(t => t.status === 'pending').length, - inProgress: todos.filter(t => t.status === 'in_progress').length, - completed: todos.filter(t => t.status === 'completed').length, - }; + // Calculate stats in a single pass for better performance + let pending = 0, inProgress = 0, completed = 0; + for (const t of todos) { + if (t.status === 'pending') pending++; + else if (t.status === 'in_progress') inProgress++; + else if (t.status === 'completed') completed++; + } + const stats = { total: todos.length, pending, inProgress, completed }; return { success: true, @@ -2302,19 +2349,19 @@ export class WebServer extends EventEmitter { return { success: false, error: `Maximum concurrent sessions (${MAX_CONCURRENT_SESSIONS}) reached.` }; } - const { caseName = 'testcase', mode = 'claude' } = req.body as QuickStartRequest; - - // Validate case name - if (!/^[a-zA-Z0-9_-]+$/.test(caseName)) { - return { success: false, error: 'Invalid case name. Use only letters, numbers, hyphens, underscores.' }; + const result = QuickStartSchema.safeParse(req.body); + if (!result.success) { + return { success: false, error: result.error.issues[0]?.message ?? 'Validation failed' }; } + const { caseName = 'testcase', mode = 'claude' } = result.data; const casePath = join(casesDir, caseName); - // Security: Path traversal protection - ensure resolved path is within casesDir + // Security: Path traversal protection - use relative path check const resolvedPath = resolve(casePath); const resolvedBase = resolve(casesDir); - if (!resolvedPath.startsWith(resolvedBase + '/') && resolvedPath !== resolvedBase) { + const relPath = relative(resolvedBase, resolvedPath); + if (relPath.startsWith('..') || isAbsolute(relPath)) { return { success: false, error: 'Invalid case path' }; } @@ -2389,11 +2436,13 @@ export class WebServer extends EventEmitter { mkdirSync(dir, { recursive: true }); } // Use async write to avoid blocking event loop - fs.writeFile(settingsFilePath, JSON.stringify(settings, null, 2)).catch(() => { - // Non-critical, ignore settings save errors + fs.writeFile(settingsFilePath, JSON.stringify(settings, null, 2)).catch((err) => { + // Non-critical but log for debugging + console.warn('[Server] Failed to save settings (lastUsedCase):', err); }); - } catch { - // Non-critical, ignore settings save errors + } catch (err) { + // Non-critical but log for debugging + console.warn('[Server] Failed to prepare settings update:', err); } return { @@ -2626,10 +2675,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; if (caseName) { const casesDir = join(homedir(), 'claudeman-cases'); const casePath = join(casesDir, caseName); - // Security: Path traversal protection + // Security: Path traversal protection - use relative path check const resolvedCase = resolve(casePath); const resolvedBase = resolve(casesDir); - if (resolvedCase.startsWith(resolvedBase) && existsSync(casePath)) { + const relPath = relative(resolvedBase, resolvedCase); + if (!relPath.startsWith('..') && !isAbsolute(relPath) && existsSync(casePath)) { outputDir = join(casePath, 'ralph-wizard'); // Clear old ralph-wizard directory to ensure fresh prompts for each generation @@ -2748,10 +2798,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; const casesDir = join(homedir(), 'claudeman-cases'); const casePath = join(casesDir, caseName); - // Security: Path traversal protection + // Security: Path traversal protection - use relative path check const resolvedCase = resolve(casePath); const resolvedBase = resolve(casesDir); - if (!resolvedCase.startsWith(resolvedBase)) { + const relPath = relative(resolvedBase, resolvedCase); + if (relPath.startsWith('..') || isAbsolute(relPath)) { return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case name'); } @@ -2800,10 +2851,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; reply.header('Pragma', 'no-cache'); reply.header('Expires', '0'); - // Security: Path traversal protection for case name + // Security: Path traversal protection for case name - use relative path check const resolvedCase = resolve(casePath); const resolvedBase = resolve(casesDir); - if (!resolvedCase.startsWith(resolvedBase)) { + const relPath = relative(resolvedBase, resolvedCase); + if (relPath.startsWith('..') || isAbsolute(relPath)) { return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid case name'); } @@ -2827,13 +2879,23 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; const content = readFileSync(fullPath, 'utf-8'); const isJson = filePath.endsWith('.json'); + // Parse JSON content safely (may contain invalid JSON) + let parsed: unknown = null; + if (isJson) { + try { + parsed = JSON.parse(content); + } catch { + // Invalid JSON - return null for parsed, content still available as raw string + } + } + return { success: true, data: { content, filePath: decodedPath, isJson, - parsed: isJson ? JSON.parse(content) : null, + parsed, }, }; }); @@ -3236,12 +3298,12 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; // ========== Hook Events ========== this.app.post('/api/hook-event', async (req) => { - const { event, sessionId, data } = req.body as HookEventRequest; - const validEvents = ['idle_prompt', 'permission_prompt', 'elicitation_dialog', 'stop'] as const; - if (!event || !validEvents.includes(event as typeof validEvents[number])) { - return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Invalid event type'); + const result = HookEventSchema.safeParse(req.body); + if (!result.success) { + return createErrorResponse(ApiErrorCode.INVALID_INPUT, result.error.issues[0]?.message ?? 'Validation failed'); } - if (!sessionId || !this.sessions.has(sessionId)) { + const { event, sessionId, data } = result.data; + if (!this.sessions.has(sessionId)) { return createErrorResponse(ApiErrorCode.NOT_FOUND, 'Session not found'); } @@ -3269,7 +3331,7 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; } // Sanitize forwarded data: only include known safe fields, limit size - const safeData = sanitizeHookData(data); + const safeData = sanitizeHookData(data as Record | undefined); this.broadcast(`hook:${event}`, { sessionId, timestamp: Date.now(), ...safeData }); // Track in run summary @@ -3507,6 +3569,8 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; this.outputBatches.delete(sessionId); this.taskUpdateBatches.delete(sessionId); this.stateUpdatePending.delete(sessionId); + this.lastTerminalEventTime.delete(sessionId); + this.adaptiveBatchIntervals.delete(sessionId); // Reset Ralph tracker on the session before cleanup if (session) { @@ -4032,6 +4096,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; console.log(`[Server] Restored respawn controller for session ${session.id} from ${source} (will start in ${Math.ceil(delayMs / 1000)}s)`); const timer = setTimeout(() => { this.pendingRespawnStarts.delete(session.id); + // Verify session still exists (may have been deleted during grace period) + if (!this.sessions.has(session.id)) { + console.log(`[Server] Skipping restored respawn start - session ${session.id} no longer exists`); + return; + } // Double-check controller still exists and is stopped const ctrl = this.respawnControllers.get(session.id); if (ctrl && ctrl.state === 'stopped') { @@ -4112,8 +4181,16 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; this.scheduledRuns.set(id, run); this.broadcast('scheduled:created', run); - // Start the run loop - this.runScheduledLoop(id); + // Start the run loop (fire-and-forget with error handling) + this.runScheduledLoop(id).catch((err) => { + console.error(`[WebServer] Scheduled run ${id} failed:`, err); + const failedRun = this.scheduledRuns.get(id); + if (failedRun && failedRun.status === 'running') { + failedRun.status = 'stopped'; + failedRun.logs.push(`[${new Date().toISOString()}] Error: ${err instanceof Error ? err.message : String(err)}`); + this.broadcast('scheduled:stopped', { id, reason: 'error' }); + } + }); return run; } @@ -4378,21 +4455,23 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; const newBatch = existing + data; this.terminalBatches.set(sessionId, newBatch); - // Adaptive batching: detect rapid events and extend batch window + // Adaptive batching: detect rapid events and extend batch window (per-session) const now = Date.now(); - const lastEvent = this.lastTerminalEventTime.get(sessionId) || 0; + const lastEvent = this.lastTerminalEventTime.get(sessionId) ?? 0; const eventGap = now - lastEvent; this.lastTerminalEventTime.set(sessionId, now); - // Adjust batch interval based on event frequency + // Adjust batch interval based on event frequency (per-session) // Rapid events (<10ms gap) = 50ms batch, moderate (<20ms) = 32ms, else 16ms + let sessionInterval: number; if (eventGap > 0 && eventGap < 10) { - this.adaptiveBatchInterval = 50; + sessionInterval = 50; } else if (eventGap > 0 && eventGap < 20) { - this.adaptiveBatchInterval = 32; + sessionInterval = 32; } else { - this.adaptiveBatchInterval = TERMINAL_BATCH_INTERVAL; + sessionInterval = TERMINAL_BATCH_INTERVAL; } + this.adaptiveBatchIntervals.set(sessionId, sessionInterval); // Flush immediately if batch is large for responsiveness if (newBatch.length > BATCH_FLUSH_THRESHOLD) { @@ -4405,17 +4484,27 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; } // Start batch timer if not already running (uses adaptive interval) + // Pick the minimum interval across all pending sessions for responsiveness if (!this.terminalBatchTimer) { + const batchInterval = Math.min( + ...Array.from(this.adaptiveBatchIntervals.values()), + TERMINAL_BATCH_INTERVAL + ); this.terminalBatchTimer = setTimeout(() => { this.flushTerminalBatches(); this.terminalBatchTimer = null; - // Reset adaptive interval after flush - this.adaptiveBatchInterval = TERMINAL_BATCH_INTERVAL; - }, this.adaptiveBatchInterval); + // Clear per-session intervals after flush (they'll be recalculated on next event) + this.adaptiveBatchIntervals.clear(); + }, batchInterval); } } private flushTerminalBatches(): void { + // Skip if server is stopping (timer may have been queued before stop() was called) + if (this._isStopping) { + this.terminalBatches.clear(); + return; + } for (const [sessionId, data] of this.terminalBatches) { if (data.length > 0) { // Wrap with DEC mode 2026 synchronized output markers @@ -4446,6 +4535,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; } private flushOutputBatches(): void { + // Skip if server is stopping (timer may have been queued before stop() was called) + if (this._isStopping) { + this.outputBatches.clear(); + return; + } for (const [sessionId, data] of this.outputBatches) { if (data.length > 0) { this.broadcast('session:output', { id: sessionId, data }); @@ -4454,12 +4548,15 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; this.outputBatches.clear(); } - // Batch task:updated events at 100ms - only send latest update per session + // Batch task:updated events at 100ms - only send latest update per task + // Key is sessionId:taskId to avoid collisions when multiple tasks update concurrently private batchTaskUpdate(sessionId: string, task: BackgroundTask): void { // Skip if server is stopping if (this._isStopping) return; - this.taskUpdateBatches.set(sessionId, task); + // Use composite key to avoid losing updates when multiple tasks update in same batch window + const key = `${sessionId}:${task.id}`; + this.taskUpdateBatches.set(key, { sessionId, task }); if (!this.taskUpdateBatchTimer) { this.taskUpdateBatchTimer = setTimeout(() => { @@ -4470,7 +4567,12 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; } private flushTaskUpdateBatches(): void { - for (const [sessionId, task] of this.taskUpdateBatches) { + // Skip if server is stopping (timer may have been queued before stop() was called) + if (this._isStopping) { + this.taskUpdateBatches.clear(); + return; + } + for (const [, { sessionId, task }] of this.taskUpdateBatches) { this.broadcast('task:updated', { sessionId, task }); } this.taskUpdateBatches.clear(); @@ -4496,6 +4598,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; } private flushStateUpdates(): void { + // Skip if server is stopping (timer may have been queued before stop() was called) + if (this._isStopping) { + this.stateUpdatePending.clear(); + return; + } for (const sessionId of this.stateUpdatePending) { const session = this.sessions.get(sessionId); if (session) { @@ -4905,6 +5012,21 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; // Stop image watcher imageWatcher.stop(); + // Destroy file stream manager (clears cleanup timer and kills remaining tail processes) + fileStreamManager.destroy(); + + // Clear remaining Maps that accumulate session references + this.respawnTimers.clear(); + this.runSummaryTrackers.clear(); + this.transcriptWatchers.clear(); + this.sessionListenerRefs.clear(); + this.scheduledRuns.clear(); + // Dispose StaleExpirationMap (stops internal cleanup timer) + this.lastTerminalEventTime.dispose(); + this.adaptiveBatchIntervals.clear(); + this.activePlanOrchestrators.clear(); + this.cleaningUp.clear(); + await this.app.close(); } } diff --git a/test/e2e/e2e.config.ts b/test/e2e/e2e.config.ts index 026a056c..f82d7270 100644 --- a/test/e2e/e2e.config.ts +++ b/test/e2e/e2e.config.ts @@ -17,6 +17,7 @@ export const E2E_PORTS = { MOBILE_SAFARI: 3191, MOBILE_COMPREHENSIVE: 3192, MOBILE_EDGE_CASES: 3193, + PLAN_GENERATION: 3194, } as const; // Mobile device viewports for responsive testing diff --git a/test/e2e/screenshots/baselines/agent-windows-visible.png b/test/e2e/screenshots/baselines/agent-windows-visible.png new file mode 100644 index 0000000000000000000000000000000000000000..903710ec94b5b855967dfbee77a2417e5302a42a GIT binary patch literal 92565 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 z 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 literal 0 HcmV?d00001 diff --git a/test/e2e/workflows/mobile-comprehensive.e2e.ts b/test/e2e/workflows/mobile-comprehensive.e2e.ts index 6454f2b2..f304626e 100644 --- a/test/e2e/workflows/mobile-comprehensive.e2e.ts +++ b/test/e2e/workflows/mobile-comprehensive.e2e.ts @@ -19,9 +19,9 @@ import { type ServerFixture, type MobileBrowserFixture, } from '../fixtures/index.js'; -import { E2E_TIMEOUTS, MOBILE_VIEWPORTS, generateCaseName } from '../e2e.config.js'; +import { E2E_TIMEOUTS, MOBILE_VIEWPORTS, generateCaseName, E2E_PORTS } from '../e2e.config.js'; -const PORT = 3192; +const PORT = E2E_PORTS.MOBILE_COMPREHENSIVE; let serverFixture: ServerFixture | null = null; let cleanup: CleanupTracker; diff --git a/test/e2e/workflows/plan-generation.e2e.ts b/test/e2e/workflows/plan-generation.e2e.ts index 7ec3db43..01bdd5c1 100644 --- a/test/e2e/workflows/plan-generation.e2e.ts +++ b/test/e2e/workflows/plan-generation.e2e.ts @@ -22,9 +22,9 @@ import { type ServerFixture, type BrowserFixture, } from '../fixtures/index.js'; -import { E2E_TIMEOUTS } from '../e2e.config.js'; +import { E2E_TIMEOUTS, E2E_PORTS } from '../e2e.config.js'; -const PORT = 3191; // Next available port per CLAUDE.md +const PORT = E2E_PORTS.PLAN_GENERATION; let serverFixture: ServerFixture | null = null; let cleanup: CleanupTracker;