chore: bump version to 0.1496

This commit is contained in:
arkon
2026-02-13 07:42:50 +01:00
parent e6da0a22ff
commit 3a77fe48e3
9 changed files with 42 additions and 24 deletions
+1 -1
View File
@@ -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`) 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` 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.1495 (must match `package.json` for npm publish) **Version**: 0.1496 (must match `package.json` for npm publish)
## Project Overview ## Project Overview
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "claudeman", "name": "claudeman",
"version": "0.1495", "version": "0.1496",
"description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence", "description": "The missing control plane for Claude Code - run 20 autonomous agents with real-time monitoring and session persistence",
"type": "module", "type": "module",
"main": "dist/index.js", "main": "dist/index.js",
+6
View File
@@ -981,6 +981,8 @@ export class RalphTracker extends EventEmitter {
this._todos.clear(); this._todos.clear();
this._completionPhraseCount.clear(); this._completionPhraseCount.clear();
this._taskNumberToContent.clear(); this._taskNumberToContent.clear();
this._todoStartTimes.clear();
this._alternateCompletionPhrases.clear();
this._lineBuffer = ''; this._lineBuffer = '';
// Reset all RALPH_STATUS block and circuit breaker state // Reset all RALPH_STATUS block and circuit breaker state
this._statusBlockBuffer = []; this._statusBlockBuffer = [];
@@ -2888,6 +2890,8 @@ export class RalphTracker extends EventEmitter {
this._loopState = createInitialRalphTrackerState(); // This sets enabled: false this._loopState = createInitialRalphTrackerState(); // This sets enabled: false
this._todos.clear(); this._todos.clear();
this._taskNumberToContent.clear(); this._taskNumberToContent.clear();
this._todoStartTimes.clear();
this._alternateCompletionPhrases.clear();
this._lineBuffer = ''; this._lineBuffer = '';
this._partialPromiseBuffer = ''; this._partialPromiseBuffer = '';
this._completionPhraseCount.clear(); this._completionPhraseCount.clear();
@@ -3838,6 +3842,8 @@ export class RalphTracker extends EventEmitter {
this.stopIterationStallDetection(); this.stopIterationStallDetection();
this._todos.clear(); this._todos.clear();
this._taskNumberToContent.clear(); this._taskNumberToContent.clear();
this._todoStartTimes.clear();
this._alternateCompletionPhrases.clear();
this._completionPhraseCount.clear(); this._completionPhraseCount.clear();
this._planTasks.clear(); this._planTasks.clear();
this.removeAllListeners(); this.removeAllListeners();
+4
View File
@@ -1765,6 +1765,10 @@ export class RespawnController extends EventEmitter {
clearInterval(this.stuckStateTimer); clearInterval(this.stuckStateTimer);
this.stuckStateTimer = null; this.stuckStateTimer = null;
} }
if (this.detectionUpdateTimer) {
clearInterval(this.detectionUpdateTimer);
this.detectionUpdateTimer = null;
}
// Clear all tracked timers // Clear all tracked timers
this.activeTimers.clear(); this.activeTimers.clear();
} }
+1 -1
View File
@@ -657,7 +657,7 @@ export class StateStore {
const tempPath = this.ralphStatePath + '.tmp'; const tempPath = this.ralphStatePath + '.tmp';
let json: string; let json: string;
try { try {
json = JSON.stringify(data, null, 2); json = JSON.stringify(data);
} catch (err) { } catch (err) {
console.error('[StateStore] Failed to serialize Ralph state (circular reference or invalid data):', err); console.error('[StateStore] Failed to serialize Ralph state (circular reference or invalid data):', err);
// Keep dirty flag true for retry - don't throw, let caller continue // Keep dirty flag true for retry - don't throw, let caller continue
+3 -10
View File
@@ -1232,16 +1232,9 @@ export class SubagentWatcher extends EventEmitter {
const agentCalls = this.pendingToolCalls.get(agentId)!; const agentCalls = this.pendingToolCalls.get(agentId)!;
// Enforce size limit to prevent memory leak from rapid tool calls // Enforce size limit to prevent memory leak from rapid tool calls
if (agentCalls.size >= MAX_PENDING_TOOL_CALLS) { if (agentCalls.size >= MAX_PENDING_TOOL_CALLS) {
// Evict oldest entry by timestamp // FIFO eviction: delete first (oldest) entry using Map insertion order
let oldestId: string | null = null; const firstKey = agentCalls.keys().next().value;
let oldestTime = Infinity; if (firstKey !== undefined) agentCalls.delete(firstKey);
for (const [id, call] of agentCalls) {
if (call.timestamp < oldestTime) {
oldestTime = call.timestamp;
oldestId = id;
}
}
if (oldestId) agentCalls.delete(oldestId);
} }
agentCalls.set(content.id, { agentCalls.set(content.id, {
toolName: content.name, toolName: content.name,
+1 -1
View File
@@ -18,7 +18,7 @@ import { LRUMap } from './utils/lru-map.js';
// ========== Constants ========== // ========== Constants ==========
const POLL_INTERVAL_MS = 3000; const POLL_INTERVAL_MS = 5000;
const MAX_CACHED_TEAMS = 50; const MAX_CACHED_TEAMS = 50;
const MAX_CACHED_TASKS = 200; const MAX_CACHED_TASKS = 200;
+4 -6
View File
@@ -40,6 +40,8 @@ const WINDOW_DEFAULT_WIDTH_PX = 300;
// xterm.js doesn't support DEC 2026 natively, so we implement buffering ourselves. // xterm.js doesn't support DEC 2026 natively, so we implement buffering ourselves.
const DEC_SYNC_START = '\x1b[?2026h'; const DEC_SYNC_START = '\x1b[?2026h';
const DEC_SYNC_END = '\x1b[?2026l'; const DEC_SYNC_END = '\x1b[?2026l';
// Pre-compiled regex for stripping DEC 2026 markers (single pass instead of two replaceAll calls)
const DEC_SYNC_STRIP_RE = /\x1b\[\?2026[hl]/g;
// Built-in respawn configuration presets // Built-in respawn configuration presets
const BUILTIN_RESPAWN_PRESETS = [ const BUILTIN_RESPAWN_PRESETS = [
@@ -1867,9 +1869,7 @@ class ClaudemanApp {
// Strip any DEC 2026 markers that might be in the buffer // Strip any DEC 2026 markers that might be in the buffer
// (from historical SSE data that was stored with markers) // (from historical SSE data that was stored with markers)
const cleanBuffer = buffer const cleanBuffer = buffer.replace(DEC_SYNC_STRIP_RE, '');
.replaceAll(DEC_SYNC_START, '')
.replaceAll(DEC_SYNC_END, '');
// For small buffers, write directly // For small buffers, write directly
if (cleanBuffer.length <= chunkSize) { if (cleanBuffer.length <= chunkSize) {
@@ -2157,9 +2157,7 @@ class ClaudemanApp {
if (termData.terminalBuffer) { if (termData.terminalBuffer) {
// Strip any DEC 2026 markers and write raw content // Strip any DEC 2026 markers and write raw content
// (markers don't help here - this is a static buffer reload, not live Ink redraws) // (markers don't help here - this is a static buffer reload, not live Ink redraws)
const cleanBuffer = termData.terminalBuffer const cleanBuffer = termData.terminalBuffer.replace(DEC_SYNC_STRIP_RE, '');
.replaceAll(DEC_SYNC_START, '')
.replaceAll(DEC_SYNC_END, '');
// Use chunked write to avoid UI freeze with large buffers (can be 1-2MB) // Use chunked write to avoid UI freeze with large buffers (can be 1-2MB)
await this.chunkedTerminalWrite(cleanBuffer); await this.chunkedTerminalWrite(cleanBuffer);
} }
+21 -4
View File
@@ -352,6 +352,10 @@ export class WebServer extends EventEmitter {
private sseHealthCheckTimer: NodeJS.Timeout | null = null; private sseHealthCheckTimer: NodeJS.Timeout | null = null;
// Flag to prevent new timers during shutdown // Flag to prevent new timers during shutdown
private _isStopping: boolean = false; private _isStopping: boolean = false;
// Cached light state for SSE init (avoids rebuilding on every reconnect)
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private cachedLightState: { data: Record<string, unknown>; timestamp: number } | null = null;
private static readonly LIGHT_STATE_CACHE_TTL_MS = 1000;
// Token recording for daily stats (track what's been recorded to avoid double-counting) // Token recording for daily stats (track what's been recorded to avoid double-counting)
private lastRecordedTokens: Map<string, { input: number; output: number }> = new Map(); private lastRecordedTokens: Map<string, { input: number; output: number }> = new Map();
private tokenRecordingTimer: NodeJS.Timeout | null = null; private tokenRecordingTimer: NodeJS.Timeout | null = null;
@@ -684,8 +688,9 @@ export class WebServer extends EventEmitter {
this.persistSessionState(session); this.persistSessionState(session);
await this.setupSessionListeners(session); await this.setupSessionListeners(session);
this.broadcast('session:created', session.toDetailedState()); const detailedState = session.toDetailedState();
return { success: true, session: session.toDetailedState() }; this.broadcast('session:created', detailedState);
return { success: true, session: detailedState };
}); });
// Rename a session // Rename a session
@@ -4371,6 +4376,11 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
* to prevent browser freezes. Terminal buffers are fetched on-demand. * to prevent browser freezes. Terminal buffers are fetched on-demand.
*/ */
private getLightState() { private getLightState() {
const now = Date.now();
if (this.cachedLightState && now - this.cachedLightState.timestamp < WebServer.LIGHT_STATE_CACHE_TTL_MS) {
return this.cachedLightState.data;
}
const respawnStatus: Record<string, ReturnType<RespawnController['getStatus']>> = {}; const respawnStatus: Record<string, ReturnType<RespawnController['getStatus']>> = {};
for (const [sessionId, controller] of this.respawnControllers) { for (const [sessionId, controller] of this.respawnControllers) {
respawnStatus[sessionId] = controller.getStatus(); respawnStatus[sessionId] = controller.getStatus();
@@ -4385,15 +4395,18 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
}; };
} }
return { const result = {
version: APP_VERSION, version: APP_VERSION,
sessions: this.getLightSessionsState(), sessions: this.getLightSessionsState(),
scheduledRuns: Array.from(this.scheduledRuns.values()), scheduledRuns: Array.from(this.scheduledRuns.values()),
respawnStatus, respawnStatus,
globalStats: this.store.getAggregateStats(activeSessionTokens), globalStats: this.store.getAggregateStats(activeSessionTokens),
subagents: subagentWatcher.getRecentSubagents(15), // 15 min to avoid stale agents subagents: subagentWatcher.getRecentSubagents(15), // 15 min to avoid stale agents
timestamp: Date.now(), timestamp: now,
}; };
this.cachedLightState = { data: result, timestamp: now };
return result;
} }
private sendSSE(reply: FastifyReply, event: string, data: unknown): void { private sendSSE(reply: FastifyReply, event: string, data: unknown): void {
@@ -4426,6 +4439,10 @@ NOW: Generate the implementation plan for the task above. Think step by step.`;
} }
private broadcast(event: string, data: unknown): void { private broadcast(event: string, data: unknown): void {
// Invalidate light state cache on any state-changing broadcast
if (event.startsWith('session:') || event === 'respawn:') {
this.cachedLightState = null;
}
// Performance optimization: serialize JSON once for all clients // Performance optimization: serialize JSON once for all clients
let message: string; let message: string;
try { try {