From 99ad44e90703c860da5fe122851fba4f53e0b3cb Mon Sep 17 00:00:00 2001 From: arkon Date: Fri, 20 Feb 2026 04:30:02 +0100 Subject: [PATCH] =?UTF-8?q?perf:=20speed=20up=20session=20creation=20by=20?= =?UTF-8?q?~500ms=20=E2=80=94=20remove=20redundant=20sleep,=20reduce=20tmu?= =?UTF-8?q?x=20wait,=20fire-and-forget=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 4 ++-- package.json | 2 +- src/session.ts | 8 ++----- src/tmux-manager.ts | 8 ++++--- src/web/public/app.js | 17 +++++++++++++++ src/web/server.ts | 49 ++++++++++++++++++++++++------------------- 6 files changed, 54 insertions(+), 34 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 282dce48..3ded1cf7 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -35,7 +35,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.1567 (must match `package.json` for npm publish) +**Version**: 0.1568 (must match `package.json` for npm publish) ## Project Overview @@ -202,7 +202,7 @@ The frontend is a single 16K-line vanilla JS file with these key systems: **Z-index layers**: subagent windows (1000), plan agents (1100), log viewers (2000), image popups (3000), local echo overlay (7). -**Built-in respawn presets**: `solo-work` (3s idle, 60min), `subagent-workflow` (45s idle, 240min), `team-lead` (90s idle, 480min), `overnight-autonomous` (10s idle, 480min, full reset). +**Built-in respawn presets**: `solo-work` (3s idle, 60min), `subagent-workflow` (45s idle, 240min), `team-lead` (90s idle, 480min), `ralph-todo` (8s idle, 480min, works through @fix_plan.md tasks), `overnight-autonomous` (10s idle, 480min, full reset). **Keyboard shortcuts**: Escape (close panels), Ctrl+? (help), Ctrl+Enter (quick start), Ctrl+W (kill session), Ctrl+Tab (next session), Ctrl+K (kill all), Ctrl+L (clear), Ctrl+Shift+R (restore size), Ctrl/Cmd +/- (font size). diff --git a/package.json b/package.json index c58c5130..f20a63e1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "claudeman", - "version": "0.1567", + "version": "0.1568", "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/session.ts b/src/session.ts index 37a37410..39c77c6e 100644 --- a/src/session.ts +++ b/src/session.ts @@ -878,9 +878,7 @@ export class Session extends EventEmitter { // Create a new mux session this._muxSession = await this._mux.createSession(this.id, this.workingDir, 'claude', this._name, this._niceConfig, this._model); console.log('[Session] Created mux session:', this._muxSession.muxName); - - // Wait a moment for mux to fully start - await new Promise(resolve => setTimeout(resolve, MUX_STARTUP_DELAY_MS)); + // No extra sleep — createSession() already waits for tmux readiness } // Attach to the mux session via PTY @@ -1176,9 +1174,7 @@ export class Session extends EventEmitter { // Create a new mux session this._muxSession = await this._mux.createSession(this.id, this.workingDir, 'shell', this._name, this._niceConfig); console.log('[Session] Created mux session:', this._muxSession.muxName); - - // Wait a moment for mux to fully start - await new Promise(resolve => setTimeout(resolve, MUX_STARTUP_DELAY_MS)); + // No extra sleep — createSession() already waits for tmux readiness } // Attach to the mux session via PTY diff --git a/src/tmux-manager.ts b/src/tmux-manager.ts index 71480116..84c0af76 100644 --- a/src/tmux-manager.ts +++ b/src/tmux-manager.ts @@ -45,8 +45,8 @@ import { findClaudeDir } from './utils/claude-cli-resolver.js'; /** Timeout for exec commands (5 seconds) */ const EXEC_TIMEOUT_MS = 5000; -/** Delay after tmux session creation (300ms) */ -const TMUX_CREATION_WAIT_MS = 300; +/** Delay after tmux session creation — enough for detached tmux to be queryable */ +const TMUX_CREATION_WAIT_MS = 100; /** Delay after tmux kill command (200ms) */ const TMUX_KILL_WAIT_MS = 200; @@ -298,7 +298,9 @@ export class TmuxManager extends EventEmitter implements TerminalMultiplexer { ); } - await Promise.all(configPromises); + // Fire-and-forget — these are non-critical UX niceties that don't need + // to complete before the session is usable. Errors are already swallowed. + void Promise.all(configPromises); // Get the PID of the pane process const pid = this.getPanePid(muxName); diff --git a/src/web/public/app.js b/src/web/public/app.js index 9c6d3c12..4bfcf949 100644 --- a/src/web/public/app.js +++ b/src/web/public/app.js @@ -96,6 +96,23 @@ const BUILTIN_RESPAWN_PRESETS = [ builtIn: true, createdAt: 0, }, + { + id: 'ralph-todo', + name: 'Ralph/Todo', + description: 'Ralph Loop task list — works through todos with progress tracking', + config: { + idleTimeoutMs: 8000, + updatePrompt: 'update CLAUDE.md with discoveries and progress notes, mark completed tasks in @fix_plan.md, write a brief summary so the next cycle can continue seamlessly.', + interStepDelayMs: 3000, + sendClear: true, + sendInit: true, + kickstartPrompt: 'read @fix_plan.md for task status, continue on the next uncompleted task. When ALL tasks are complete, output COMPLETE.', + autoAcceptPrompts: true, + }, + durationMinutes: 480, + builtIn: true, + createdAt: 0, + }, { id: 'overnight-autonomous', name: 'Overnight', diff --git a/src/web/server.ts b/src/web/server.ts index a3dd2145..b470525b 100644 --- a/src/web/server.ts +++ b/src/web/server.ts @@ -4635,37 +4635,42 @@ NOW: Generate the implementation plan for the task above. Think step by step.`; return undefined; } - // Helper to get global Nice priority config from settings - private async getGlobalNiceConfig(): Promise { + // Read ~/.claudeman/settings.json once and return the parsed object. + // Cached for 2s to avoid redundant reads during session creation bursts. + private _settingsCache: { data: Record; ts: number } | null = null; + private async readSettings(): Promise> { + const now = Date.now(); + if (this._settingsCache && now - this._settingsCache.ts < 2000) { + return this._settingsCache.data; + } const settingsPath = join(homedir(), '.claudeman', 'settings.json'); - try { const content = await fs.readFile(settingsPath, 'utf-8'); - const settings = JSON.parse(content); - if (settings.nice && settings.nice.enabled) { - return { - enabled: settings.nice.enabled ?? false, - niceValue: settings.nice.niceValue ?? DEFAULT_NICE_CONFIG.niceValue, - }; - } - } catch (err) { - if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { - console.error('Failed to read Nice priority settings:', err); - } + const data = JSON.parse(content) as Record; + this._settingsCache = { data, ts: now }; + return data; + } catch { + return {}; + } + } + + // Helper to get global Nice priority config from settings + private async getGlobalNiceConfig(): Promise { + const settings = await this.readSettings(); + const nice = settings.nice as { enabled?: boolean; niceValue?: number } | undefined; + if (nice && nice.enabled) { + return { + enabled: nice.enabled ?? false, + niceValue: nice.niceValue ?? DEFAULT_NICE_CONFIG.niceValue, + }; } return undefined; } // Helper to get model configuration from settings private async getModelConfig(): Promise<{ defaultModel?: string; agentTypeOverrides?: Record } | null> { - const settingsPath = join(homedir(), '.claudeman', 'settings.json'); - - try { - const content = await fs.readFile(settingsPath, 'utf-8'); - return JSON.parse(content).modelConfig || null; - } catch { - return null; - } + const settings = await this.readSettings(); + return (settings.modelConfig as { defaultModel?: string; agentTypeOverrides?: Record }) || null; } private async startScheduledRun(prompt: string, workingDir: string, durationMinutes: number): Promise {