mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-09-30 12:39:42 +02:00
fix: respawn controller stuck after AI check and AI check PATH discovery
Two bugs fixed: 1. Respawn controller got stuck after AI check returned WORKING/ERROR. The pre-filter and no-output timers fired once but were never restarted, leaving the controller in a dead 'watching' state with no retry mechanism. Now restarts both timers after any non-IDLE AI check result and on cooldown expiry. Also seeds the controller's terminal buffer from the session's existing output so the first AI check has context. 2. AI idle checker couldn't find the claude binary in restricted environments (systemd service). The spawned screen ran `claude -p` without PATH augmentation. Now exports getAugmentedPath() from session.ts and prepends it in the AI checker's bash command. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ import { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|||||||
import { tmpdir } from 'node:os';
|
import { tmpdir } from 'node:os';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { EventEmitter } from 'node:events';
|
import { EventEmitter } from 'node:events';
|
||||||
|
import { getAugmentedPath } from './session.js';
|
||||||
|
|
||||||
// ========== Types ==========
|
// ========== Types ==========
|
||||||
|
|
||||||
@@ -323,8 +324,9 @@ export class AiIdleChecker extends EventEmitter {
|
|||||||
// Build the command - escape the prompt for shell
|
// Build the command - escape the prompt for shell
|
||||||
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
const escapedPrompt = prompt.replace(/'/g, "'\\''");
|
||||||
const modelArg = `--model ${this.config.model}`;
|
const modelArg = `--model ${this.config.model}`;
|
||||||
|
const augmentedPath = getAugmentedPath();
|
||||||
const claudeCmd = `claude -p ${modelArg} --output-format text '${escapedPrompt}'`;
|
const claudeCmd = `claude -p ${modelArg} --output-format text '${escapedPrompt}'`;
|
||||||
const fullCmd = `${claudeCmd} > "${this.checkTempFile}" 2>&1; echo "${DONE_MARKER}" >> "${this.checkTempFile}"`;
|
const fullCmd = `export PATH="${augmentedPath}"; ${claudeCmd} > "${this.checkTempFile}" 2>&1; echo "${DONE_MARKER}" >> "${this.checkTempFile}"`;
|
||||||
|
|
||||||
// Spawn screen
|
// Spawn screen
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -579,6 +579,10 @@ export class RespawnController extends EventEmitter {
|
|||||||
|
|
||||||
this.aiChecker.on('cooldownEnded', () => {
|
this.aiChecker.on('cooldownEnded', () => {
|
||||||
this.emit('aiCheckCooldown', false, null);
|
this.emit('aiCheckCooldown', false, null);
|
||||||
|
// Restart pre-filter timer when cooldown expires so a new check can be triggered
|
||||||
|
if (this._state === 'watching') {
|
||||||
|
this.startPreFilterTimer();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
this.aiChecker.on('disabled', (reason: string) => {
|
this.aiChecker.on('disabled', (reason: string) => {
|
||||||
@@ -772,6 +776,14 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.completionMessageTime = null;
|
this.completionMessageTime = null;
|
||||||
this.hasReceivedOutput = false;
|
this.hasReceivedOutput = false;
|
||||||
|
|
||||||
|
// Seed the terminal buffer from the session's existing output.
|
||||||
|
// This gives the AI checker context even if no new output arrives.
|
||||||
|
const existingBuffer = this.session.terminalBuffer;
|
||||||
|
if (existingBuffer) {
|
||||||
|
this.terminalBuffer.clear();
|
||||||
|
this.terminalBuffer.append(existingBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
this.aiChecker.reset();
|
this.aiChecker.reset();
|
||||||
this.setState('watching');
|
this.setState('watching');
|
||||||
this.setupTerminalListener();
|
this.setupTerminalListener();
|
||||||
@@ -1329,10 +1341,16 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.emit('aiCheckCompleted', result);
|
this.emit('aiCheckCompleted', result);
|
||||||
this.setState('watching');
|
this.setState('watching');
|
||||||
this.log(`AI check says WORKING, returning to watching with ${this.config.aiIdleCheckCooldownMs}ms cooldown`);
|
this.log(`AI check says WORKING, returning to watching with ${this.config.aiIdleCheckCooldownMs}ms cooldown`);
|
||||||
|
// Restart timers so the controller retries after cooldown expires
|
||||||
|
this.startNoOutputTimer();
|
||||||
|
this.startPreFilterTimer();
|
||||||
} else {
|
} else {
|
||||||
// ERROR verdict
|
// ERROR verdict
|
||||||
this.emit('aiCheckFailed', result.reasoning);
|
this.emit('aiCheckFailed', result.reasoning);
|
||||||
this.setState('watching');
|
this.setState('watching');
|
||||||
|
// Restart timers to allow retry
|
||||||
|
this.startNoOutputTimer();
|
||||||
|
this.startPreFilterTimer();
|
||||||
}
|
}
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
if (this._state === 'ai_checking') {
|
if (this._state === 'ai_checking') {
|
||||||
@@ -1340,6 +1358,9 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.emit('aiCheckFailed', errorMsg);
|
this.emit('aiCheckFailed', errorMsg);
|
||||||
this.setState('watching');
|
this.setState('watching');
|
||||||
this.log(`AI check error: ${errorMsg}`);
|
this.log(`AI check error: ${errorMsg}`);
|
||||||
|
// Restart timers to allow retry
|
||||||
|
this.startNoOutputTimer();
|
||||||
|
this.startPreFilterTimer();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -87,7 +87,7 @@ let _augmentedPath: string | null = null;
|
|||||||
* prepends its directory to the current PATH if not already present.
|
* prepends its directory to the current PATH if not already present.
|
||||||
* Result is cached for subsequent calls.
|
* Result is cached for subsequent calls.
|
||||||
*/
|
*/
|
||||||
function getAugmentedPath(): string {
|
export function getAugmentedPath(): string {
|
||||||
if (_augmentedPath) return _augmentedPath;
|
if (_augmentedPath) return _augmentedPath;
|
||||||
|
|
||||||
const currentPath = process.env.PATH || '';
|
const currentPath = process.env.PATH || '';
|
||||||
|
|||||||
Reference in New Issue
Block a user