feat: disable Ralph/Todo tracker auto-enable by default

Ralph tracker no longer auto-enables on pattern detection. It must be
explicitly enabled per-session (via API) or globally via the new
`ralphEnabled` AppConfig setting. Adds GET/PUT /api/config endpoints
for runtime configuration. Spawn agent ralph enable is unchanged.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-23 22:14:00 +01:00
co-authored by Claude Opus 4.5
parent a39ae1d151
commit a41240049e
4 changed files with 80 additions and 33 deletions
+1 -1
View File
@@ -310,7 +310,7 @@ export class RalphTracker extends EventEmitter {
private _loopUpdatePending: boolean = false;
/** When true, prevents auto-enable on pattern detection */
private _autoEnableDisabled: boolean = false;
private _autoEnableDisabled: boolean = true;
/** Maps task numbers from "✔ Task #N" format to their content for status updates */
private _taskNumberToContent: Map<number, string> = new Map();
+3
View File
@@ -229,6 +229,8 @@ export interface AppConfig {
respawn: RespawnConfig;
/** Last used case name (for default selection) */
lastUsedCase: string | null;
/** Whether Ralph/Todo tracker is globally enabled for all new sessions */
ralphEnabled: boolean;
}
// ========== Output Types ==========
@@ -564,6 +566,7 @@ export const DEFAULT_CONFIG: AppConfig = {
sendInit: true, // send /init after /clear
},
lastUsedCase: null,
ralphEnabled: false,
};
// ========== Inner Loop Tracking Types ==========
+32 -6
View File
@@ -213,6 +213,19 @@ export class WebServer extends EventEmitter {
// API Routes
this.app.get('/api/status', async () => this.getFullState());
this.app.get('/api/config', async () => {
return { success: true, config: this.store.getConfig() };
});
this.app.put('/api/config', async (req) => {
const body = req.body as Record<string, unknown> | null;
if (!body || typeof body !== 'object') {
return createErrorResponse(ApiErrorCode.INVALID_INPUT, 'Request body must be a JSON object');
}
this.store.setConfig(body as Partial<ReturnType<typeof this.store.getConfig>>);
return { success: true, config: this.store.getConfig() };
});
// Session management
this.app.get('/api/sessions', async () => this.getSessionsState());
@@ -449,8 +462,13 @@ export class WebServer extends EventEmitter {
}
try {
// Auto-detect completion phrase from CLAUDE.md BEFORE starting
autoConfigureRalph(session, session.workingDir, () => {});
// Auto-detect completion phrase from CLAUDE.md BEFORE starting (only if globally enabled)
if (this.store.getConfig().ralphEnabled) {
autoConfigureRalph(session, session.workingDir, () => {});
if (!session.ralphTracker.enabled) {
session.ralphTracker.enable();
}
}
await session.startInteractive();
this.broadcast('session:interactive', { id });
@@ -723,8 +741,13 @@ export class WebServer extends EventEmitter {
}
try {
// Auto-detect completion phrase from CLAUDE.md BEFORE starting
autoConfigureRalph(session, session.workingDir, () => {});
// Auto-detect completion phrase from CLAUDE.md BEFORE starting (only if globally enabled)
if (this.store.getConfig().ralphEnabled) {
autoConfigureRalph(session, session.workingDir, () => {});
if (!session.ralphTracker.enabled) {
session.ralphTracker.enable();
}
}
// Start interactive session
await session.startInteractive();
@@ -1165,9 +1188,12 @@ export class WebServer extends EventEmitter {
});
// Auto-detect completion phrase from CLAUDE.md BEFORE broadcasting
// so the initial state already has the phrase configured
if (mode === 'claude') {
// so the initial state already has the phrase configured (only if globally enabled)
if (mode === 'claude' && this.store.getConfig().ralphEnabled) {
autoConfigureRalph(session, casePath, () => {}); // no broadcast yet
if (!session.ralphTracker.enabled) {
session.ralphTracker.enable();
}
}
this.sessions.set(session.id, session);