mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
fix: persist respawn config across server restarts
- persistSessionState() now saves respawnEnabled based on config.enabled (user intent) instead of controller.state (running state) - cleanupSession() saves respawn config BEFORE removing controller - AiIdleChecker and AiPlanChecker constructors filter undefined values to prevent overwriting defaults (fixes "timed out after undefinedms") - updateConfig() methods also filter undefined values Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|||||||
|
|
||||||
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.1344
|
**Version**: 0.1345
|
||||||
|
|
||||||
**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
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claudeman",
|
"name": "claudeman",
|
||||||
"version": "0.1344",
|
"version": "0.1345",
|
||||||
"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",
|
||||||
|
|||||||
+10
-2
@@ -153,7 +153,11 @@ export class AiIdleChecker extends EventEmitter {
|
|||||||
constructor(sessionId: string, config: Partial<AiIdleCheckConfig> = {}) {
|
constructor(sessionId: string, config: Partial<AiIdleCheckConfig> = {}) {
|
||||||
super();
|
super();
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.config = { ...DEFAULT_AI_CHECK_CONFIG, ...config };
|
// Filter out undefined values to prevent overwriting defaults
|
||||||
|
const filteredConfig = Object.fromEntries(
|
||||||
|
Object.entries(config).filter(([, v]) => v !== undefined)
|
||||||
|
) as Partial<AiIdleCheckConfig>;
|
||||||
|
this.config = { ...DEFAULT_AI_CHECK_CONFIG, ...filteredConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the current status */
|
/** Get the current status */
|
||||||
@@ -287,7 +291,11 @@ export class AiIdleChecker extends EventEmitter {
|
|||||||
|
|
||||||
/** Update configuration at runtime */
|
/** Update configuration at runtime */
|
||||||
updateConfig(config: Partial<AiIdleCheckConfig>): void {
|
updateConfig(config: Partial<AiIdleCheckConfig>): void {
|
||||||
this.config = { ...this.config, ...config };
|
// Filter out undefined values to prevent overwriting existing config
|
||||||
|
const filteredConfig = Object.fromEntries(
|
||||||
|
Object.entries(config).filter(([, v]) => v !== undefined)
|
||||||
|
) as Partial<AiIdleCheckConfig>;
|
||||||
|
this.config = { ...this.config, ...filteredConfig };
|
||||||
if (config.enabled === false) {
|
if (config.enabled === false) {
|
||||||
this.disable('Disabled by config');
|
this.disable('Disabled by config');
|
||||||
} else if (config.enabled === true && this._status === 'disabled') {
|
} else if (config.enabled === true && this._status === 'disabled') {
|
||||||
|
|||||||
+10
-2
@@ -161,7 +161,11 @@ export class AiPlanChecker extends EventEmitter {
|
|||||||
constructor(sessionId: string, config: Partial<AiPlanCheckConfig> = {}) {
|
constructor(sessionId: string, config: Partial<AiPlanCheckConfig> = {}) {
|
||||||
super();
|
super();
|
||||||
this.sessionId = sessionId;
|
this.sessionId = sessionId;
|
||||||
this.config = { ...DEFAULT_PLAN_CHECK_CONFIG, ...config };
|
// Filter out undefined values to prevent overwriting defaults
|
||||||
|
const filteredConfig = Object.fromEntries(
|
||||||
|
Object.entries(config).filter(([, v]) => v !== undefined)
|
||||||
|
) as Partial<AiPlanCheckConfig>;
|
||||||
|
this.config = { ...DEFAULT_PLAN_CHECK_CONFIG, ...filteredConfig };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the current status */
|
/** Get the current status */
|
||||||
@@ -295,7 +299,11 @@ export class AiPlanChecker extends EventEmitter {
|
|||||||
|
|
||||||
/** Update configuration at runtime */
|
/** Update configuration at runtime */
|
||||||
updateConfig(config: Partial<AiPlanCheckConfig>): void {
|
updateConfig(config: Partial<AiPlanCheckConfig>): void {
|
||||||
this.config = { ...this.config, ...config };
|
// Filter out undefined values to prevent overwriting existing config
|
||||||
|
const filteredConfig = Object.fromEntries(
|
||||||
|
Object.entries(config).filter(([, v]) => v !== undefined)
|
||||||
|
) as Partial<AiPlanCheckConfig>;
|
||||||
|
this.config = { ...this.config, ...filteredConfig };
|
||||||
if (config.enabled === false) {
|
if (config.enabled === false) {
|
||||||
this.disable('Disabled by config');
|
this.disable('Disabled by config');
|
||||||
} else if (config.enabled === true && this._status === 'disabled') {
|
} else if (config.enabled === true && this._status === 'disabled') {
|
||||||
|
|||||||
+21
-3
@@ -1582,9 +1582,19 @@ export class WebServer extends EventEmitter {
|
|||||||
? Math.round((timerInfo.endAt - timerInfo.startedAt) / 60000)
|
? Math.round((timerInfo.endAt - timerInfo.startedAt) / 60000)
|
||||||
: undefined;
|
: undefined;
|
||||||
state.respawnConfig = { ...config, durationMinutes };
|
state.respawnConfig = { ...config, durationMinutes };
|
||||||
state.respawnEnabled = controller.state !== 'stopped';
|
// Use config.enabled instead of controller.state - this way the respawn
|
||||||
|
// will be restored on server restart even if it was temporarily stopped
|
||||||
|
// due to errors. Intentional stops via /respawn/stop call clearRespawnConfig().
|
||||||
|
state.respawnEnabled = config.enabled;
|
||||||
} else {
|
} else {
|
||||||
state.respawnEnabled = false;
|
// Don't overwrite respawnConfig if it exists in state - preserve it for restart
|
||||||
|
const existingState = this.store.getSession(session.id);
|
||||||
|
if (existingState?.respawnConfig) {
|
||||||
|
state.respawnConfig = existingState.respawnConfig;
|
||||||
|
state.respawnEnabled = existingState.respawnConfig.enabled ?? false;
|
||||||
|
} else {
|
||||||
|
state.respawnEnabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.store.setSession(session.id, state);
|
this.store.setSession(session.id, state);
|
||||||
}
|
}
|
||||||
@@ -1664,9 +1674,17 @@ export class WebServer extends EventEmitter {
|
|||||||
private async _doCleanupSession(sessionId: string, killScreen: boolean): Promise<void> {
|
private async _doCleanupSession(sessionId: string, killScreen: boolean): Promise<void> {
|
||||||
const session = this.sessions.get(sessionId);
|
const session = this.sessions.get(sessionId);
|
||||||
|
|
||||||
// Stop and remove respawn controller
|
// Stop and remove respawn controller - but save config first for restart recovery
|
||||||
const controller = this.respawnControllers.get(sessionId);
|
const controller = this.respawnControllers.get(sessionId);
|
||||||
if (controller) {
|
if (controller) {
|
||||||
|
// Save the config BEFORE removing controller, so it can be restored on restart
|
||||||
|
const config = controller.getConfig();
|
||||||
|
const timerInfo = this.respawnTimers.get(sessionId);
|
||||||
|
const durationMinutes = timerInfo
|
||||||
|
? Math.round((timerInfo.endAt - timerInfo.startedAt) / 60000)
|
||||||
|
: undefined;
|
||||||
|
this.saveRespawnConfig(sessionId, config, durationMinutes);
|
||||||
|
|
||||||
controller.stop();
|
controller.stop();
|
||||||
controller.removeAllListeners();
|
controller.removeAllListeners();
|
||||||
this.respawnControllers.delete(sessionId);
|
this.respawnControllers.delete(sessionId);
|
||||||
|
|||||||
Reference in New Issue
Block a user