mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
fix: exclude full terminal buffers from SSE init to prevent browser freezes
- Add getLightState() and getLightSessionsState() methods - SSE init payload reduced from ~1.9MB to ~5KB (~400x smaller) - Terminal buffers are fetched on-demand when switching tabs - Fixes UI becoming unresponsive on SSE reconnect chore: bump version to 0.1388
This commit is contained in:
@@ -15,13 +15,17 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
## ⚡ COM Shorthand (Deployment)
|
## ⚡ COM Shorthand (Deployment)
|
||||||
|
|
||||||
When user says "COM": 1) Increment version in BOTH `package.json` AND `CLAUDE.md` (keep them in sync), 2) `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`. Always bump version on every COM, even for small changes.
|
When user says "COM":
|
||||||
|
1. Increment version in BOTH `package.json` AND `CLAUDE.md` (keep them in sync)
|
||||||
|
2. Run: `git add -A && git commit -m "chore: bump version to X.XXXX" && git push && npm run build && systemctl --user restart claudeman-web`
|
||||||
|
|
||||||
|
Always bump version on every COM, even for small changes.
|
||||||
|
|
||||||
## Project Overview
|
## Project Overview
|
||||||
|
|
||||||
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
|
Claudeman is a Claude Code session manager with a web interface and autonomous Ralph Loop. It spawns Claude CLI processes via PTY, streams output in real-time via SSE, and supports scheduled/timed runs.
|
||||||
|
|
||||||
**Version**: 0.1387 (must match `package.json`)
|
**Version**: 0.1388 (must match `package.json`)
|
||||||
|
|
||||||
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
**Tech Stack**: TypeScript (ES2022/NodeNext, strict mode), Node.js, Fastify, Server-Sent Events, node-pty
|
||||||
|
|
||||||
@@ -126,7 +130,7 @@ Unit tests (no port needed): respawn-controller, ralph-tracker, pty-interactive,
|
|||||||
|
|
||||||
**Test Cleanup Patterns**: Integration tests track resources in `createdSessions` and `createdCases` arrays, cleaned up by `afterAll`/`afterEach` hooks. However, some tests perform cleanup in the test body itself (e.g., `edge-cases.test.ts:273-302` creates 5 sessions and cleans them in a loop). If assertions fail before cleanup code runs, resources leak.
|
**Test Cleanup Patterns**: Integration tests track resources in `createdSessions` and `createdCases` arrays, cleaned up by `afterAll`/`afterEach` hooks. However, some tests perform cleanup in the test body itself (e.g., `edge-cases.test.ts:273-302` creates 5 sessions and cleans them in a loop). If assertions fail before cleanup code runs, resources leak.
|
||||||
|
|
||||||
**Known Cleanup Issues**:
|
**Known Cleanup Issues** (technical debt):
|
||||||
- `pty-interactive.test.ts`: Uses `await session.stop()` at end of each test, not in `afterEach`. Test failures leave sessions running.
|
- `pty-interactive.test.ts`: Uses `await session.stop()` at end of each test, not in `afterEach`. Test failures leave sessions running.
|
||||||
- `edge-cases.test.ts`: Multiple sessions created in test body with cleanup at end; failures leak sessions.
|
- `edge-cases.test.ts`: Multiple sessions created in test body with cleanup at end; failures leak sessions.
|
||||||
- Test cases (`~/claudeman-cases/`): Cases named `flow-test-*`, `ralph-track-loop-*`, `session-detail-*` may persist after test failures.
|
- Test cases (`~/claudeman-cases/`): Cases named `flow-test-*`, `ralph-track-loop-*`, `session-detail-*` may persist after test failures.
|
||||||
@@ -604,11 +608,16 @@ Placeholders replaced:
|
|||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
|
**Reference docs** (read these for deep dives):
|
||||||
- `docs/respawn-state-machine.md` - Respawn controller states, idle detection, auto-accept
|
- `docs/respawn-state-machine.md` - Respawn controller states, idle detection, auto-accept
|
||||||
- `docs/spawn-protocol.md` - Spawn1337 agent protocol, MCP tools, resource governance
|
- `docs/spawn-protocol.md` - Spawn1337 agent protocol, MCP tools, resource governance
|
||||||
- `docs/ralph-wiggum-guide.md` - Ralph Wiggum loop guide (plugin reference, prompt templates)
|
- `docs/ralph-wiggum-guide.md` - Ralph Wiggum loop guide (plugin reference, prompt templates)
|
||||||
- `docs/claude-code-hooks-reference.md` - Claude Code hooks documentation
|
- `docs/claude-code-hooks-reference.md` - Claude Code hooks documentation
|
||||||
|
|
||||||
|
**Internal planning** (historical context, may be outdated):
|
||||||
|
- `docs/respawn-improvement-plan.md` - Planned respawn improvements
|
||||||
|
- `docs/run-summary-plan.md` - Run summary feature design
|
||||||
|
|
||||||
### Ralph Wiggum Loops
|
### Ralph Wiggum Loops
|
||||||
|
|
||||||
**Core Pattern**: `<promise>PHRASE</promise>` - The completion signal that tells the loop to stop.
|
**Core Pattern**: `<promise>PHRASE</promise>` - The completion signal that tells the loop to stop.
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1387",
|
"version": "0.1388",
|
||||||
"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",
|
||||||
|
|||||||
+54
-1
@@ -384,7 +384,9 @@ export class WebServer extends EventEmitter {
|
|||||||
this.sseClients.add(reply);
|
this.sseClients.add(reply);
|
||||||
|
|
||||||
// Send initial state
|
// Send initial state
|
||||||
this.sendSSE(reply, 'init', this.getFullState());
|
// Use light state for SSE init to avoid sending 2MB+ terminal buffers
|
||||||
|
// Buffers are fetched on-demand when switching tabs
|
||||||
|
this.sendSSE(reply, 'init', this.getLightState());
|
||||||
|
|
||||||
req.raw.on('close', () => {
|
req.raw.on('close', () => {
|
||||||
this.sseClients.delete(reply);
|
this.sseClients.delete(reply);
|
||||||
@@ -2797,6 +2799,27 @@ export class WebServer extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lightweight session state for SSE init - excludes full terminal buffers
|
||||||
|
* to prevent browser freezes on SSE reconnect. Full buffers are fetched
|
||||||
|
* on-demand when switching tabs via /api/sessions/:id/buffer
|
||||||
|
*/
|
||||||
|
private getLightSessionsState() {
|
||||||
|
return Array.from(this.sessions.values()).map(s => {
|
||||||
|
const controller = this.respawnControllers.get(s.id);
|
||||||
|
const detailed = s.toDetailedState();
|
||||||
|
return {
|
||||||
|
...detailed,
|
||||||
|
// Exclude full buffers - they're fetched on-demand
|
||||||
|
terminalBuffer: '',
|
||||||
|
textOutput: '',
|
||||||
|
respawnEnabled: controller?.getConfig()?.enabled ?? false,
|
||||||
|
respawnConfig: controller?.getConfig() ?? null,
|
||||||
|
respawn: controller?.getStatus() ?? null,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Clean up old completed scheduled runs
|
// Clean up old completed scheduled runs
|
||||||
private cleanupScheduledRuns(): void {
|
private cleanupScheduledRuns(): void {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
@@ -2850,6 +2873,36 @@ export class WebServer extends EventEmitter {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get lightweight state for SSE init - excludes full terminal buffers
|
||||||
|
* to prevent browser freezes. Terminal buffers are fetched on-demand.
|
||||||
|
*/
|
||||||
|
private getLightState() {
|
||||||
|
const respawnStatus: Record<string, ReturnType<RespawnController['getStatus']>> = {};
|
||||||
|
for (const [sessionId, controller] of this.respawnControllers) {
|
||||||
|
respawnStatus[sessionId] = controller.getStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeSessionTokens: Record<string, { inputTokens?: number; outputTokens?: number; totalCost?: number }> = {};
|
||||||
|
for (const [sessionId, session] of this.sessions) {
|
||||||
|
activeSessionTokens[sessionId] = {
|
||||||
|
inputTokens: session.inputTokens,
|
||||||
|
outputTokens: session.outputTokens,
|
||||||
|
totalCost: session.totalCost,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: APP_VERSION,
|
||||||
|
sessions: this.getLightSessionsState(),
|
||||||
|
scheduledRuns: Array.from(this.scheduledRuns.values()),
|
||||||
|
respawnStatus,
|
||||||
|
globalStats: this.store.getAggregateStats(activeSessionTokens),
|
||||||
|
subagents: subagentWatcher.getRecentSubagents(60),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private sendSSE(reply: FastifyReply, event: string, data: unknown): void {
|
private sendSSE(reply: FastifyReply, event: string, data: unknown): void {
|
||||||
try {
|
try {
|
||||||
reply.raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
reply.raw.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
||||||
|
|||||||
Reference in New Issue
Block a user