mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 00:19:42 +02:00
refactor: complete Phase 2 — migrate ralph-tracker and respawn-controller to managed timers
ralph-tracker.ts: Replace 4 manual timer/flag fields (_todoUpdateTimer, _loopUpdateTimer, _todoUpdatePending, _loopUpdatePending) with 2 Debouncer instances. respawn-controller.ts: Replace 10 manual timer fields (stepTimer, completionConfirmTimer, noOutputTimer, detectionUpdateTimer, autoAcceptTimer, preFilterTimer, hookConfirmTimer, clearFallbackTimer, stepConfirmTimer, stuckStateTimer) with CleanupManager + timerIds Map. startTrackedTimer/cancelTrackedTimer preserved as wrappers for UI countdown display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -877,12 +877,12 @@ execSync(`tmux kill-session -t "${this.checkMuxName}"`, { timeout: 3000 });
|
|||||||
4. ✅ Add `z.infer` to Zod schemas (~2 hours) — `src/web/schemas.ts` now has 36 `z.infer` type exports (lines 512-547) covering all schemas
|
4. ✅ Add `z.infer` to Zod schemas (~2 hours) — `src/web/schemas.ts` now has 36 `z.infer` type exports (lines 512-547) covering all schemas
|
||||||
5. ✅ Fix 10 weak "not.toThrow()" tests (~1 hour) — All `not.toThrow()` calls now have behavior assertions: `task-tracker.test.ts` (6 instances all followed by state checks), `image-watcher.test.ts` (1 instance followed by length check), `session-manager.test.ts` (1 instance followed by count check)
|
5. ✅ Fix 10 weak "not.toThrow()" tests (~1 hour) — All `not.toThrow()` calls now have behavior assertions: `task-tracker.test.ts` (6 instances all followed by state checks), `image-watcher.test.ts` (1 instance followed by length check), `session-manager.test.ts` (1 instance followed by count check)
|
||||||
|
|
||||||
**Phase 2 - CleanupManager & Debounce (2-3 days)** ⚠️ PARTIALLY COMPLETE
|
**Phase 2 - CleanupManager & Debounce (2-3 days)** ✅ COMPLETE
|
||||||
1. ✅ Create `Debouncer` utility class (~1 hour) — Created `src/utils/debouncer.ts` with `Debouncer` and `KeyedDebouncer` classes; exported from `src/utils/index.ts`
|
1. ✅ Create `Debouncer` utility class (~1 hour) — Created `src/utils/debouncer.ts` with `Debouncer` and `KeyedDebouncer` classes; exported from `src/utils/index.ts`
|
||||||
2. ⚠️ Migrate 6 of 8 files from manual debounce to Debouncer — `state-store.ts` (2 Debouncers), `push-store.ts` (1 Debouncer), `bash-tool-parser.ts` (1 Debouncer), `image-watcher.ts` (1 KeyedDebouncer), `subagent-watcher.ts` (2 KeyedDebouncers), `server.ts` (1 KeyedDebouncer for persist timers). **Not migrated**: `ralph-tracker.ts` (still has 2 manual timer fields: `_todoUpdateTimer`, `_loopUpdateTimer`)
|
2. ✅ Migrate all 8 files from manual debounce to Debouncer — `state-store.ts` (2 Debouncers), `push-store.ts` (1 Debouncer), `bash-tool-parser.ts` (1 Debouncer), `image-watcher.ts` (1 KeyedDebouncer), `subagent-watcher.ts` (2 KeyedDebouncers), `server.ts` (1 KeyedDebouncer for persist timers), `ralph-tracker.ts` (2 Debouncers replacing 4 manual fields: `_todoUpdateTimer`, `_loopUpdateTimer`, `_todoUpdatePending`, `_loopUpdatePending`)
|
||||||
3. ❌ Migrate respawn-controller to CleanupManager — **NOT DONE**. Still has 10+ manual timer fields (`stepTimer`, `completionConfirmTimer`, `noOutputTimer`, `detectionUpdateTimer`, `autoAcceptTimer`, `preFilterTimer`, `hookConfirmTimer`, `clearFallbackTimer`, `stepConfirmTimer`, `stuckStateTimer`). Uses custom `startTrackedTimer()`/`cancelTrackedTimer()` helpers instead.
|
3. ✅ Migrate respawn-controller to CleanupManager — 10 manual timer fields replaced with single `CleanupManager` instance + `timerIds` Map. `startTrackedTimer()`/`cancelTrackedTimer()` preserved as wrappers for UI countdown display and timer events. `clearTimers()` uses dispose-and-recreate pattern for state transitions.
|
||||||
4. ✅ Migrate server.ts timer cleanup to CleanupManager (~2 hours) — `private cleanup = new CleanupManager()` present; terminal batch timers and pending respawn starts left as manual Maps (complex lifecycle)
|
4. ✅ Migrate server.ts timer cleanup to CleanupManager (~2 hours) — `private cleanup = new CleanupManager()` present; terminal batch timers and pending respawn starts left as manual Maps (complex lifecycle)
|
||||||
5. ⚠️ Migrate remaining files — `bash-tool-parser.ts` (CleanupManager ✅), `subagent-watcher.ts` (CleanupManager ✅). **Not migrated**: `ralph-tracker.ts` (no CleanupManager, still uses manual timer clearing)
|
5. ✅ Migrate remaining files — `bash-tool-parser.ts` (CleanupManager ✅), `subagent-watcher.ts` (CleanupManager ✅), `ralph-tracker.ts` (Debouncer ✅)
|
||||||
|
|
||||||
**Phase 3 - server.ts Route Extraction (3-4 days)** ✅ COMPLETE
|
**Phase 3 - server.ts Route Extraction (3-4 days)** ✅ COMPLETE
|
||||||
1. ✅ Created `src/web/routes/` with 12 domain route modules + index barrel (4,090 LOC total): session (909), system (768), ralph (533), plan (459), respawn (315), case, file, hook-event, mux, push, scheduled, team
|
1. ✅ Created `src/web/routes/` with 12 domain route modules + index barrel (4,090 LOC total): session (909), system (768), ralph (533), plan (459), respawn (315), case, file, hook-event, mux, push, scheduled, team
|
||||||
|
|||||||
+15
-60
@@ -33,7 +33,7 @@ import {
|
|||||||
PlanTaskStatus,
|
PlanTaskStatus,
|
||||||
TddPhase,
|
TddPhase,
|
||||||
} from './types.js';
|
} from './types.js';
|
||||||
import { ANSI_ESCAPE_PATTERN_SIMPLE, fuzzyPhraseMatch, todoContentHash, stringSimilarity } from './utils/index.js';
|
import { ANSI_ESCAPE_PATTERN_SIMPLE, fuzzyPhraseMatch, todoContentHash, stringSimilarity, Debouncer } from './utils/index.js';
|
||||||
import { MAX_LINE_BUFFER_SIZE } from './config/buffer-limits.js';
|
import { MAX_LINE_BUFFER_SIZE } from './config/buffer-limits.js';
|
||||||
import { MAX_TODOS_PER_SESSION } from './config/map-limits.js';
|
import { MAX_TODOS_PER_SESSION } from './config/map-limits.js';
|
||||||
import { RalphPlanTracker } from './ralph-plan-tracker.js';
|
import { RalphPlanTracker } from './ralph-plan-tracker.js';
|
||||||
@@ -454,17 +454,11 @@ export class RalphTracker extends EventEmitter {
|
|||||||
/** Timestamp of last cleanup check for throttling */
|
/** Timestamp of last cleanup check for throttling */
|
||||||
private _lastCleanupTime: number = 0;
|
private _lastCleanupTime: number = 0;
|
||||||
|
|
||||||
/** Debounce timer for todoUpdate events */
|
/** Debouncer for todoUpdate events */
|
||||||
private _todoUpdateTimer: NodeJS.Timeout | null = null;
|
private _todoDeb = new Debouncer(EVENT_DEBOUNCE_MS);
|
||||||
|
|
||||||
/** Debounce timer for loopUpdate events */
|
/** Debouncer for loopUpdate events */
|
||||||
private _loopUpdateTimer: NodeJS.Timeout | null = null;
|
private _loopDeb = new Debouncer(EVENT_DEBOUNCE_MS);
|
||||||
|
|
||||||
/** Flag indicating pending todoUpdate emission */
|
|
||||||
private _todoUpdatePending: boolean = false;
|
|
||||||
|
|
||||||
/** Flag indicating pending loopUpdate emission */
|
|
||||||
private _loopUpdatePending: boolean = false;
|
|
||||||
|
|
||||||
/** When true, prevents auto-enable on pattern detection */
|
/** When true, prevents auto-enable on pattern detection */
|
||||||
private _autoEnableDisabled: boolean = true;
|
private _autoEnableDisabled: boolean = true;
|
||||||
@@ -996,74 +990,34 @@ export class RalphTracker extends EventEmitter {
|
|||||||
* Clear all debounce timers.
|
* Clear all debounce timers.
|
||||||
*/
|
*/
|
||||||
private clearDebounceTimers(): void {
|
private clearDebounceTimers(): void {
|
||||||
if (this._todoUpdateTimer) {
|
this._todoDeb.cancel();
|
||||||
clearTimeout(this._todoUpdateTimer);
|
this._loopDeb.cancel();
|
||||||
this._todoUpdateTimer = null;
|
|
||||||
}
|
|
||||||
if (this._loopUpdateTimer) {
|
|
||||||
clearTimeout(this._loopUpdateTimer);
|
|
||||||
this._loopUpdateTimer = null;
|
|
||||||
}
|
|
||||||
this._todoUpdatePending = false;
|
|
||||||
this._loopUpdatePending = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit todoUpdate event with debouncing.
|
* Emit todoUpdate event with debouncing.
|
||||||
*/
|
*/
|
||||||
private emitTodoUpdateDebounced(): void {
|
private emitTodoUpdateDebounced(): void {
|
||||||
this._todoUpdatePending = true;
|
this._todoDeb.schedule(() => this.emit('todoUpdate', this.todos));
|
||||||
|
|
||||||
if (this._todoUpdateTimer) {
|
|
||||||
clearTimeout(this._todoUpdateTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._todoUpdateTimer = setTimeout(() => {
|
|
||||||
if (this._todoUpdatePending) {
|
|
||||||
this._todoUpdatePending = false;
|
|
||||||
this._todoUpdateTimer = null;
|
|
||||||
this.emit('todoUpdate', this.todos);
|
|
||||||
}
|
|
||||||
}, EVENT_DEBOUNCE_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Emit loopUpdate event with debouncing.
|
* Emit loopUpdate event with debouncing.
|
||||||
*/
|
*/
|
||||||
private emitLoopUpdateDebounced(): void {
|
private emitLoopUpdateDebounced(): void {
|
||||||
this._loopUpdatePending = true;
|
this._loopDeb.schedule(() => this.emit('loopUpdate', this.loopState));
|
||||||
|
|
||||||
if (this._loopUpdateTimer) {
|
|
||||||
clearTimeout(this._loopUpdateTimer);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._loopUpdateTimer = setTimeout(() => {
|
|
||||||
if (this._loopUpdatePending) {
|
|
||||||
this._loopUpdatePending = false;
|
|
||||||
this._loopUpdateTimer = null;
|
|
||||||
this.emit('loopUpdate', this.loopState);
|
|
||||||
}
|
|
||||||
}, EVENT_DEBOUNCE_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush all pending debounced events immediately.
|
* Flush all pending debounced events immediately.
|
||||||
*/
|
*/
|
||||||
flushPendingEvents(): void {
|
flushPendingEvents(): void {
|
||||||
if (this._todoUpdatePending) {
|
if (this._todoDeb.isPending) {
|
||||||
this._todoUpdatePending = false;
|
this._todoDeb.cancel();
|
||||||
if (this._todoUpdateTimer) {
|
|
||||||
clearTimeout(this._todoUpdateTimer);
|
|
||||||
this._todoUpdateTimer = null;
|
|
||||||
}
|
|
||||||
this.emit('todoUpdate', this.todos);
|
this.emit('todoUpdate', this.todos);
|
||||||
}
|
}
|
||||||
if (this._loopUpdatePending) {
|
if (this._loopDeb.isPending) {
|
||||||
this._loopUpdatePending = false;
|
this._loopDeb.cancel();
|
||||||
if (this._loopUpdateTimer) {
|
|
||||||
clearTimeout(this._loopUpdateTimer);
|
|
||||||
this._loopUpdateTimer = null;
|
|
||||||
}
|
|
||||||
this.emit('loopUpdate', this.loopState);
|
this.emit('loopUpdate', this.loopState);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2373,7 +2327,8 @@ export class RalphTracker extends EventEmitter {
|
|||||||
* Clean up all resources and release memory.
|
* Clean up all resources and release memory.
|
||||||
*/
|
*/
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.clearDebounceTimers();
|
this._todoDeb.dispose();
|
||||||
|
this._loopDeb.dispose();
|
||||||
this.fixPlanWatcher.destroy();
|
this.fixPlanWatcher.destroy();
|
||||||
this.stallDetector.destroy();
|
this.stallDetector.destroy();
|
||||||
this.statusParser.destroy();
|
this.statusParser.destroy();
|
||||||
|
|||||||
+94
-157
@@ -41,7 +41,7 @@ import { AiIdleChecker, type AiCheckResult, type AiCheckState } from './ai-idle-
|
|||||||
import { AiPlanChecker, type AiPlanCheckResult } from './ai-plan-checker.js';
|
import { AiPlanChecker, type AiPlanCheckResult } from './ai-plan-checker.js';
|
||||||
import type { TeamWatcher } from './team-watcher.js';
|
import type { TeamWatcher } from './team-watcher.js';
|
||||||
import { BufferAccumulator } from './utils/buffer-accumulator.js';
|
import { BufferAccumulator } from './utils/buffer-accumulator.js';
|
||||||
import { ANSI_ESCAPE_PATTERN_SIMPLE, assertNever } from './utils/index.js';
|
import { ANSI_ESCAPE_PATTERN_SIMPLE, assertNever, CleanupManager } from './utils/index.js';
|
||||||
import { MAX_RESPAWN_BUFFER_SIZE, TRIM_RESPAWN_BUFFER_TO as RESPAWN_BUFFER_TRIM_SIZE } from './config/buffer-limits.js';
|
import { MAX_RESPAWN_BUFFER_SIZE, TRIM_RESPAWN_BUFFER_TO as RESPAWN_BUFFER_TRIM_SIZE } from './config/buffer-limits.js';
|
||||||
import {
|
import {
|
||||||
isCompletionMessage,
|
isCompletionMessage,
|
||||||
@@ -643,27 +643,15 @@ export class RespawnController extends EventEmitter {
|
|||||||
/** Current state machine state */
|
/** Current state machine state */
|
||||||
private _state: RespawnState = 'stopped';
|
private _state: RespawnState = 'stopped';
|
||||||
|
|
||||||
/** Timer for step delays */
|
/** Centralized timer lifecycle manager — disposed and recreated on clearTimers() */
|
||||||
private stepTimer: NodeJS.Timeout | null = null;
|
private cleanup = new CleanupManager();
|
||||||
|
|
||||||
/** Timer for completion confirmation (Layer 2) */
|
/** Maps timer names to CleanupManager registration IDs (for individual cancel) */
|
||||||
private completionConfirmTimer: NodeJS.Timeout | null = null;
|
private timerIds = new Map<string, string>();
|
||||||
|
|
||||||
/** Timer for no-output fallback (Layer 5) */
|
|
||||||
private noOutputTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Timer for periodic detection status updates */
|
|
||||||
private detectionUpdateTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Cached key fields from last emitted detection status (for dedup) */
|
/** Cached key fields from last emitted detection status (for dedup) */
|
||||||
private lastEmittedDetectionKey: string = '';
|
private lastEmittedDetectionKey: string = '';
|
||||||
|
|
||||||
/** Timer for auto-accepting plan mode prompts */
|
|
||||||
private autoAcceptTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Timer for pre-filter silence detection (triggers AI check) */
|
|
||||||
private preFilterTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Whether any terminal output has been received since start/last-auto-accept */
|
/** Whether any terminal output has been received since start/last-auto-accept */
|
||||||
private hasReceivedOutput: boolean = false;
|
private hasReceivedOutput: boolean = false;
|
||||||
|
|
||||||
@@ -684,9 +672,6 @@ export class RespawnController extends EventEmitter {
|
|||||||
/** Timestamp when idle_prompt was received */
|
/** Timestamp when idle_prompt was received */
|
||||||
private idlePromptTime: number | null = null;
|
private idlePromptTime: number | null = null;
|
||||||
|
|
||||||
/** Timer for short confirmation after hook signal (handles race conditions) */
|
|
||||||
private hookConfirmTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Confirmation delay after hook signal before confirming idle (ms) */
|
/** Confirmation delay after hook signal before confirming idle (ms) */
|
||||||
private static readonly HOOK_CONFIRM_DELAY_MS = 3000;
|
private static readonly HOOK_CONFIRM_DELAY_MS = 3000;
|
||||||
|
|
||||||
@@ -720,12 +705,6 @@ export class RespawnController extends EventEmitter {
|
|||||||
/** Unique ID for current AI check request (to detect stale results) */
|
/** Unique ID for current AI check request (to detect stale results) */
|
||||||
private _currentAiCheckId: string | null = null;
|
private _currentAiCheckId: string | null = null;
|
||||||
|
|
||||||
/** Timer for /clear step fallback (sends /init if no prompt detected) */
|
|
||||||
private clearFallbackTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Timer for step completion confirmation (waits for silence after completion) */
|
|
||||||
private stepConfirmTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Fallback timeout for /clear step (ms) - sends /init without waiting for prompt */
|
/** Fallback timeout for /clear step (ms) - sends /init without waiting for prompt */
|
||||||
private static readonly CLEAR_FALLBACK_TIMEOUT_MS = 10000;
|
private static readonly CLEAR_FALLBACK_TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
@@ -743,9 +722,6 @@ export class RespawnController extends EventEmitter {
|
|||||||
/** Timestamp when the current state was entered */
|
/** Timestamp when the current state was entered */
|
||||||
private stateEnteredAt: number = 0;
|
private stateEnteredAt: number = 0;
|
||||||
|
|
||||||
/** Timer for stuck-state detection */
|
|
||||||
private stuckStateTimer: NodeJS.Timeout | null = null;
|
|
||||||
|
|
||||||
/** Whether a stuck-state warning has been emitted for current state */
|
/** Whether a stuck-state warning has been emitted for current state */
|
||||||
private stuckStateWarned: boolean = false;
|
private stuckStateWarned: boolean = false;
|
||||||
|
|
||||||
@@ -1116,7 +1092,7 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.stopDetectionUpdates();
|
this.stopDetectionUpdates();
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
this.lastEmittedDetectionKey = '';
|
this.lastEmittedDetectionKey = '';
|
||||||
this.detectionUpdateTimer = setInterval(() => {
|
const id = this.cleanup.setInterval(() => {
|
||||||
try {
|
try {
|
||||||
if (this._state !== 'stopped') {
|
if (this._state !== 'stopped') {
|
||||||
const status = this.getDetectionStatus();
|
const status = this.getDetectionStatus();
|
||||||
@@ -1131,17 +1107,15 @@ export class RespawnController extends EventEmitter {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[RespawnController] Error in detectionUpdateTimer:`, err);
|
console.error(`[RespawnController] Error in detectionUpdateTimer:`, err);
|
||||||
}
|
}
|
||||||
}, 2000);
|
}, 2000, { description: 'detection-update' });
|
||||||
|
this.timerIds.set('detection-update', id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop periodic detection status updates.
|
* Stop periodic detection status updates.
|
||||||
*/
|
*/
|
||||||
private stopDetectionUpdates(): void {
|
private stopDetectionUpdates(): void {
|
||||||
if (this.detectionUpdateTimer) {
|
this.cancelTrackedTimer('detection-update');
|
||||||
clearInterval(this.detectionUpdateTimer);
|
|
||||||
this.detectionUpdateTimer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1266,7 +1240,6 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.aiChecker.removeAllListeners();
|
this.aiChecker.removeAllListeners();
|
||||||
this.planChecker.removeAllListeners();
|
this.planChecker.removeAllListeners();
|
||||||
this.clearTimers();
|
this.clearTimers();
|
||||||
this.stopDetectionUpdates();
|
|
||||||
this.recentActions.length = 0;
|
this.recentActions.length = 0;
|
||||||
this.setState('stopped');
|
this.setState('stopped');
|
||||||
if (this.terminalHandler) {
|
if (this.terminalHandler) {
|
||||||
@@ -1431,8 +1404,8 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.lastWorkingPatternTime = now;
|
this.lastWorkingPatternTime = now;
|
||||||
|
|
||||||
// Cancel hook confirmation timer if running
|
// Cancel hook confirmation timer if running
|
||||||
this.cancelTrackedTimer('hook-confirm', this.hookConfirmTimer, 'working patterns detected');
|
this.cancelTrackedTimer('hook-confirm','working patterns detected');
|
||||||
this.hookConfirmTimer = null;
|
|
||||||
|
|
||||||
// Cancel any pending completion confirmation
|
// Cancel any pending completion confirmation
|
||||||
this.cancelCompletionConfirm();
|
this.cancelCompletionConfirm();
|
||||||
@@ -1564,8 +1537,8 @@ export class RespawnController extends EventEmitter {
|
|||||||
*/
|
*/
|
||||||
private checkClearComplete(): void {
|
private checkClearComplete(): void {
|
||||||
// Clear the fallback timer since we got prompt detection
|
// Clear the fallback timer since we got prompt detection
|
||||||
this.cancelTrackedTimer('clear-fallback', this.clearFallbackTimer, 'prompt detected');
|
this.cancelTrackedTimer('clear-fallback','prompt detected');
|
||||||
this.clearFallbackTimer = null;
|
|
||||||
this.logAction('step', '/clear completed');
|
this.logAction('step', '/clear completed');
|
||||||
this.emit('stepCompleted', 'clear');
|
this.emit('stepCompleted', 'clear');
|
||||||
|
|
||||||
@@ -1613,11 +1586,11 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.logAction('step', 'Monitoring if /init triggered work...');
|
this.logAction('step', 'Monitoring if /init triggered work...');
|
||||||
|
|
||||||
// Give Claude a moment to start working before checking for idle
|
// Give Claude a moment to start working before checking for idle
|
||||||
this.stepTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'init-monitor',
|
'init-monitor',
|
||||||
3000,
|
3000,
|
||||||
() => {
|
() => {
|
||||||
this.stepTimer = null;
|
|
||||||
// If still in monitoring state and no work detected, consider it idle
|
// If still in monitoring state and no work detected, consider it idle
|
||||||
if (this._state === 'monitoring_init' && !this.workingDetected) {
|
if (this._state === 'monitoring_init' && !this.workingDetected) {
|
||||||
this.checkMonitoringInitIdle();
|
this.checkMonitoringInitIdle();
|
||||||
@@ -1633,10 +1606,7 @@ export class RespawnController extends EventEmitter {
|
|||||||
* @fires stepCompleted - With step 'init'
|
* @fires stepCompleted - With step 'init'
|
||||||
*/
|
*/
|
||||||
private checkMonitoringInitIdle(): void {
|
private checkMonitoringInitIdle(): void {
|
||||||
if (this.stepTimer) {
|
this.cancelTrackedTimer('init-monitor');
|
||||||
clearTimeout(this.stepTimer);
|
|
||||||
this.stepTimer = null;
|
|
||||||
}
|
|
||||||
this.log('/init did not trigger work, sending kickstart prompt');
|
this.log('/init did not trigger work, sending kickstart prompt');
|
||||||
this.emit('stepCompleted', 'init');
|
this.emit('stepCompleted', 'init');
|
||||||
this.sendKickstart();
|
this.sendKickstart();
|
||||||
@@ -1651,11 +1621,11 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.terminalBuffer.clear();
|
this.terminalBuffer.clear();
|
||||||
this.clearWorkingPatternWindow();
|
this.clearWorkingPatternWindow();
|
||||||
|
|
||||||
this.stepTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'step-delay',
|
'step-delay',
|
||||||
this.config.interStepDelayMs,
|
this.config.interStepDelayMs,
|
||||||
async () => {
|
async () => {
|
||||||
this.stepTimer = null;
|
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
const prompt = this.config.kickstartPrompt!;
|
const prompt = this.config.kickstartPrompt!;
|
||||||
this.logAction('command', `Sending kickstart: "${prompt.substring(0, 40)}..."`);
|
this.logAction('command', `Sending kickstart: "${prompt.substring(0, 40)}..."`);
|
||||||
@@ -1685,48 +1655,10 @@ export class RespawnController extends EventEmitter {
|
|||||||
|
|
||||||
/** Clear all timers (step, completion confirm, no-output, pre-filter, step confirm, auto-accept, hook confirm, and clear fallback) */
|
/** Clear all timers (step, completion confirm, no-output, pre-filter, step confirm, auto-accept, hook confirm, and clear fallback) */
|
||||||
private clearTimers(): void {
|
private clearTimers(): void {
|
||||||
// Clear tracked timers map first to avoid stale entries during individual cleanup
|
|
||||||
this.activeTimers.clear();
|
this.activeTimers.clear();
|
||||||
if (this.stepTimer) {
|
this.timerIds.clear();
|
||||||
clearTimeout(this.stepTimer);
|
this.cleanup.dispose();
|
||||||
this.stepTimer = null;
|
this.cleanup = new CleanupManager();
|
||||||
}
|
|
||||||
if (this.clearFallbackTimer) {
|
|
||||||
clearTimeout(this.clearFallbackTimer);
|
|
||||||
this.clearFallbackTimer = null;
|
|
||||||
}
|
|
||||||
if (this.completionConfirmTimer) {
|
|
||||||
clearTimeout(this.completionConfirmTimer);
|
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
}
|
|
||||||
if (this.stepConfirmTimer) {
|
|
||||||
clearTimeout(this.stepConfirmTimer);
|
|
||||||
this.stepConfirmTimer = null;
|
|
||||||
}
|
|
||||||
if (this.autoAcceptTimer) {
|
|
||||||
clearTimeout(this.autoAcceptTimer);
|
|
||||||
this.autoAcceptTimer = null;
|
|
||||||
}
|
|
||||||
if (this.preFilterTimer) {
|
|
||||||
clearTimeout(this.preFilterTimer);
|
|
||||||
this.preFilterTimer = null;
|
|
||||||
}
|
|
||||||
if (this.noOutputTimer) {
|
|
||||||
clearTimeout(this.noOutputTimer);
|
|
||||||
this.noOutputTimer = null;
|
|
||||||
}
|
|
||||||
if (this.hookConfirmTimer) {
|
|
||||||
clearTimeout(this.hookConfirmTimer);
|
|
||||||
this.hookConfirmTimer = null;
|
|
||||||
}
|
|
||||||
if (this.stuckStateTimer) {
|
|
||||||
clearInterval(this.stuckStateTimer);
|
|
||||||
this.stuckStateTimer = null;
|
|
||||||
}
|
|
||||||
if (this.detectionUpdateTimer) {
|
|
||||||
clearInterval(this.detectionUpdateTimer);
|
|
||||||
this.detectionUpdateTimer = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== Stuck-State Detection Methods ==========
|
// ========== Stuck-State Detection Methods ==========
|
||||||
@@ -1740,21 +1672,19 @@ export class RespawnController extends EventEmitter {
|
|||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
|
|
||||||
// Clear existing timer
|
// Clear existing timer
|
||||||
if (this.stuckStateTimer) {
|
this.cancelTrackedTimer('stuck-state');
|
||||||
clearInterval(this.stuckStateTimer);
|
|
||||||
this.stuckStateTimer = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check interval for stuck state
|
// Check interval for stuck state
|
||||||
const checkIntervalMs = Math.min(this.config.stuckStateWarningMs, 60000); // Check every minute max
|
const checkIntervalMs = Math.min(this.config.stuckStateWarningMs, 60000); // Check every minute max
|
||||||
|
|
||||||
this.stuckStateTimer = setInterval(() => {
|
const id = this.cleanup.setInterval(() => {
|
||||||
try {
|
try {
|
||||||
this.checkStuckState();
|
this.checkStuckState();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`[RespawnController] Error in stuckStateTimer:`, err);
|
console.error(`[RespawnController] Error in stuckStateTimer:`, err);
|
||||||
}
|
}
|
||||||
}, checkIntervalMs);
|
}, checkIntervalMs, { description: 'stuck-state' });
|
||||||
|
this.timerIds.set('stuck-state', id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1895,7 +1825,10 @@ export class RespawnController extends EventEmitter {
|
|||||||
* Start a tracked timer with UI countdown support.
|
* Start a tracked timer with UI countdown support.
|
||||||
* Emits timerStarted event and tracks the timer for UI display.
|
* Emits timerStarted event and tracks the timer for UI display.
|
||||||
*/
|
*/
|
||||||
private startTrackedTimer(name: string, durationMs: number, callback: () => void, reason?: string): NodeJS.Timeout {
|
private startTrackedTimer(name: string, durationMs: number, callback: () => void, reason?: string): void {
|
||||||
|
// Cancel any existing timer with this name
|
||||||
|
this.cancelTrackedTimer(name);
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const endsAt = now + durationMs;
|
const endsAt = now + durationMs;
|
||||||
|
|
||||||
@@ -1903,19 +1836,23 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.emit('timerStarted', { name, durationMs, endsAt, reason });
|
this.emit('timerStarted', { name, durationMs, endsAt, reason });
|
||||||
this.logAction('timer', `Started ${name}: ${Math.round(durationMs / 1000)}s${reason ? ` (${reason})` : ''}`);
|
this.logAction('timer', `Started ${name}: ${Math.round(durationMs / 1000)}s${reason ? ` (${reason})` : ''}`);
|
||||||
|
|
||||||
return setTimeout(() => {
|
const id = this.cleanup.setTimeout(() => {
|
||||||
|
this.timerIds.delete(name);
|
||||||
this.activeTimers.delete(name);
|
this.activeTimers.delete(name);
|
||||||
this.emit('timerCompleted', name);
|
this.emit('timerCompleted', name);
|
||||||
callback();
|
callback();
|
||||||
}, durationMs);
|
}, durationMs, { description: name });
|
||||||
|
this.timerIds.set(name, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel a tracked timer and emit cancellation event.
|
* Cancel a tracked timer and emit cancellation event.
|
||||||
*/
|
*/
|
||||||
private cancelTrackedTimer(name: string, timerRef: NodeJS.Timeout | null, reason?: string): void {
|
private cancelTrackedTimer(name: string, reason?: string): void {
|
||||||
if (timerRef) {
|
const id = this.timerIds.get(name);
|
||||||
clearTimeout(timerRef);
|
if (id) {
|
||||||
|
this.cleanup.unregister(id);
|
||||||
|
this.timerIds.delete(name);
|
||||||
if (this.activeTimers.has(name)) {
|
if (this.activeTimers.has(name)) {
|
||||||
this.activeTimers.delete(name);
|
this.activeTimers.delete(name);
|
||||||
this.emit('timerCancelled', name, reason);
|
this.emit('timerCancelled', name, reason);
|
||||||
@@ -1988,14 +1925,14 @@ export class RespawnController extends EventEmitter {
|
|||||||
* (used when AI check is disabled or has too many errors).
|
* (used when AI check is disabled or has too many errors).
|
||||||
*/
|
*/
|
||||||
private startNoOutputTimer(): void {
|
private startNoOutputTimer(): void {
|
||||||
this.cancelTrackedTimer('no-output-fallback', this.noOutputTimer, 'restarting');
|
this.cancelTrackedTimer('no-output-fallback','restarting');
|
||||||
this.noOutputTimer = null;
|
|
||||||
|
|
||||||
this.noOutputTimer = this.startTrackedTimer(
|
|
||||||
|
this.startTrackedTimer(
|
||||||
'no-output-fallback',
|
'no-output-fallback',
|
||||||
this.config.noOutputTimeoutMs,
|
this.config.noOutputTimeoutMs,
|
||||||
() => {
|
() => {
|
||||||
this.noOutputTimer = null;
|
|
||||||
if (this._state === 'watching' || this._state === 'confirming_idle') {
|
if (this._state === 'watching' || this._state === 'confirming_idle') {
|
||||||
const msSinceOutput = Date.now() - this.lastOutputTime;
|
const msSinceOutput = Date.now() - this.lastOutputTime;
|
||||||
this.logAction('detection', `No-output fallback: ${Math.round(msSinceOutput / 1000)}s silence`);
|
this.logAction('detection', `No-output fallback: ${Math.round(msSinceOutput / 1000)}s silence`);
|
||||||
@@ -2028,17 +1965,17 @@ export class RespawnController extends EventEmitter {
|
|||||||
* This provides an additional path to AI check even without a completion message.
|
* This provides an additional path to AI check even without a completion message.
|
||||||
*/
|
*/
|
||||||
private startPreFilterTimer(): void {
|
private startPreFilterTimer(): void {
|
||||||
this.cancelTrackedTimer('pre-filter', this.preFilterTimer, 'restarting');
|
this.cancelTrackedTimer('pre-filter','restarting');
|
||||||
this.preFilterTimer = null;
|
|
||||||
|
|
||||||
// Only set up pre-filter when AI check is enabled
|
// Only set up pre-filter when AI check is enabled
|
||||||
if (!this.config.aiIdleCheckEnabled) return;
|
if (!this.config.aiIdleCheckEnabled) return;
|
||||||
|
|
||||||
this.preFilterTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'pre-filter',
|
'pre-filter',
|
||||||
this.config.completionConfirmMs,
|
this.config.completionConfirmMs,
|
||||||
() => {
|
() => {
|
||||||
this.preFilterTimer = null;
|
|
||||||
if (this._state === 'watching') {
|
if (this._state === 'watching') {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const msSinceOutput = now - this.lastOutputTime;
|
const msSinceOutput = now - this.lastOutputTime;
|
||||||
@@ -2143,18 +2080,18 @@ export class RespawnController extends EventEmitter {
|
|||||||
|
|
||||||
if (result.verdict === 'IDLE') {
|
if (result.verdict === 'IDLE') {
|
||||||
// Cancel any pending confirmation timers - AI has spoken
|
// Cancel any pending confirmation timers - AI has spoken
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'AI verdict: IDLE');
|
this.cancelTrackedTimer('completion-confirm','AI verdict: IDLE');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
this.cancelTrackedTimer('pre-filter', this.preFilterTimer, 'AI verdict: IDLE');
|
this.cancelTrackedTimer('pre-filter','AI verdict: IDLE');
|
||||||
this.preFilterTimer = null;
|
|
||||||
|
|
||||||
this.logAction('ai-check', `Verdict: IDLE - ${result.reasoning}`);
|
this.logAction('ai-check', `Verdict: IDLE - ${result.reasoning}`);
|
||||||
this.emit('aiCheckCompleted', result);
|
this.emit('aiCheckCompleted', result);
|
||||||
this.onIdleConfirmed(`ai-check: idle (${result.reasoning})`);
|
this.onIdleConfirmed(`ai-check: idle (${result.reasoning})`);
|
||||||
} else if (result.verdict === 'WORKING') {
|
} else if (result.verdict === 'WORKING') {
|
||||||
// Cancel timers and go to cooldown
|
// Cancel timers and go to cooldown
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'AI verdict: WORKING');
|
this.cancelTrackedTimer('completion-confirm','AI verdict: WORKING');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
|
|
||||||
this.logAction('ai-check', `Verdict: WORKING - ${result.reasoning}`);
|
this.logAction('ai-check', `Verdict: WORKING - ${result.reasoning}`);
|
||||||
this.emit('aiCheckCompleted', result);
|
this.emit('aiCheckCompleted', result);
|
||||||
@@ -2210,14 +2147,14 @@ export class RespawnController extends EventEmitter {
|
|||||||
* and no elicitation dialog was detected. Only handles plan mode approvals.
|
* and no elicitation dialog was detected. Only handles plan mode approvals.
|
||||||
*/
|
*/
|
||||||
private startAutoAcceptTimer(): void {
|
private startAutoAcceptTimer(): void {
|
||||||
this.cancelTrackedTimer('auto-accept', this.autoAcceptTimer, 'restarting');
|
this.cancelTrackedTimer('auto-accept','restarting');
|
||||||
this.autoAcceptTimer = null;
|
|
||||||
|
|
||||||
this.autoAcceptTimer = this.startTrackedTimer(
|
|
||||||
|
this.startTrackedTimer(
|
||||||
'auto-accept',
|
'auto-accept',
|
||||||
this.config.autoAcceptDelayMs,
|
this.config.autoAcceptDelayMs,
|
||||||
() => {
|
() => {
|
||||||
this.autoAcceptTimer = null;
|
|
||||||
this.tryAutoAccept();
|
this.tryAutoAccept();
|
||||||
},
|
},
|
||||||
'plan mode detection'
|
'plan mode detection'
|
||||||
@@ -2229,8 +2166,8 @@ export class RespawnController extends EventEmitter {
|
|||||||
* Called when a completion message is detected (normal idle flow handles it).
|
* Called when a completion message is detected (normal idle flow handles it).
|
||||||
*/
|
*/
|
||||||
private cancelAutoAcceptTimer(): void {
|
private cancelAutoAcceptTimer(): void {
|
||||||
this.cancelTrackedTimer('auto-accept', this.autoAcceptTimer, 'cancelled');
|
this.cancelTrackedTimer('auto-accept','cancelled');
|
||||||
this.autoAcceptTimer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2391,8 +2328,8 @@ export class RespawnController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cancel completion confirmation - auto-accept takes precedence
|
// Cancel completion confirmation - auto-accept takes precedence
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'auto-accept');
|
this.cancelTrackedTimer('completion-confirm','auto-accept');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
this.completionMessageTime = null;
|
this.completionMessageTime = null;
|
||||||
|
|
||||||
// Ensure we're in watching state (not confirming_idle or ai_checking)
|
// Ensure we're in watching state (not confirming_idle or ai_checking)
|
||||||
@@ -2446,12 +2383,12 @@ export class RespawnController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cancel completion confirm timer - hook takes precedence
|
// Cancel completion confirm timer - hook takes precedence
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'Stop hook received');
|
this.cancelTrackedTimer('completion-confirm','Stop hook received');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
|
|
||||||
// Cancel pre-filter timer - hook takes precedence
|
// Cancel pre-filter timer - hook takes precedence
|
||||||
this.cancelTrackedTimer('pre-filter', this.preFilterTimer, 'Stop hook received');
|
this.cancelTrackedTimer('pre-filter','Stop hook received');
|
||||||
this.preFilterTimer = null;
|
|
||||||
|
|
||||||
// Start short confirmation timer to handle race conditions
|
// Start short confirmation timer to handle race conditions
|
||||||
// (e.g., Stop hook arrives but Claude immediately starts new work)
|
// (e.g., Stop hook arrives but Claude immediately starts new work)
|
||||||
@@ -2485,12 +2422,12 @@ export class RespawnController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Cancel all other detection timers - this is definitive
|
// Cancel all other detection timers - this is definitive
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'idle_prompt received');
|
this.cancelTrackedTimer('completion-confirm','idle_prompt received');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
this.cancelTrackedTimer('pre-filter', this.preFilterTimer, 'idle_prompt received');
|
this.cancelTrackedTimer('pre-filter','idle_prompt received');
|
||||||
this.preFilterTimer = null;
|
|
||||||
this.cancelTrackedTimer('no-output-fallback', this.noOutputTimer, 'idle_prompt received');
|
this.cancelTrackedTimer('no-output-fallback','idle_prompt received');
|
||||||
this.noOutputTimer = null;
|
|
||||||
|
|
||||||
// idle_prompt is an even stronger signal than Stop hook (60s+ idle)
|
// idle_prompt is an even stronger signal than Stop hook (60s+ idle)
|
||||||
// Skip confirmation and go directly to idle
|
// Skip confirmation and go directly to idle
|
||||||
@@ -2504,14 +2441,14 @@ export class RespawnController extends EventEmitter {
|
|||||||
* @param hookType - Which hook triggered this ('stop' or 'idle_prompt')
|
* @param hookType - Which hook triggered this ('stop' or 'idle_prompt')
|
||||||
*/
|
*/
|
||||||
private startHookConfirmTimer(hookType: 'stop' | 'idle_prompt'): void {
|
private startHookConfirmTimer(hookType: 'stop' | 'idle_prompt'): void {
|
||||||
this.cancelTrackedTimer('hook-confirm', this.hookConfirmTimer, 'restarting');
|
this.cancelTrackedTimer('hook-confirm','restarting');
|
||||||
this.hookConfirmTimer = null;
|
|
||||||
|
|
||||||
this.hookConfirmTimer = this.startTrackedTimer(
|
|
||||||
|
this.startTrackedTimer(
|
||||||
'hook-confirm',
|
'hook-confirm',
|
||||||
RespawnController.HOOK_CONFIRM_DELAY_MS,
|
RespawnController.HOOK_CONFIRM_DELAY_MS,
|
||||||
() => {
|
() => {
|
||||||
this.hookConfirmTimer = null;
|
|
||||||
|
|
||||||
// Verify we haven't received new output since the hook arrived
|
// Verify we haven't received new output since the hook arrived
|
||||||
const hookTime = hookType === 'stop' ? this.stopHookTime : this.idlePromptTime;
|
const hookTime = hookType === 'stop' ? this.stopHookTime : this.idlePromptTime;
|
||||||
@@ -2585,17 +2522,17 @@ export class RespawnController extends EventEmitter {
|
|||||||
* After completion message, waits for output silence then triggers AI check.
|
* After completion message, waits for output silence then triggers AI check.
|
||||||
*/
|
*/
|
||||||
private startCompletionConfirmTimer(): void {
|
private startCompletionConfirmTimer(): void {
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'restarting');
|
this.cancelTrackedTimer('completion-confirm','restarting');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
|
|
||||||
this.setState('confirming_idle');
|
this.setState('confirming_idle');
|
||||||
this.logAction('detection', 'Completion message found in output');
|
this.logAction('detection', 'Completion message found in output');
|
||||||
|
|
||||||
this.completionConfirmTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'completion-confirm',
|
'completion-confirm',
|
||||||
this.config.completionConfirmMs,
|
this.config.completionConfirmMs,
|
||||||
() => {
|
() => {
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
const msSinceOutput = Date.now() - this.lastOutputTime;
|
const msSinceOutput = Date.now() - this.lastOutputTime;
|
||||||
if (msSinceOutput >= this.config.completionConfirmMs) {
|
if (msSinceOutput >= this.config.completionConfirmMs) {
|
||||||
@@ -2616,8 +2553,8 @@ export class RespawnController extends EventEmitter {
|
|||||||
* Cancel completion confirmation if new activity detected.
|
* Cancel completion confirmation if new activity detected.
|
||||||
*/
|
*/
|
||||||
private cancelCompletionConfirm(): void {
|
private cancelCompletionConfirm(): void {
|
||||||
this.cancelTrackedTimer('completion-confirm', this.completionConfirmTimer, 'activity detected');
|
this.cancelTrackedTimer('completion-confirm','activity detected');
|
||||||
this.completionConfirmTimer = null;
|
|
||||||
if (this._state === 'confirming_idle') {
|
if (this._state === 'confirming_idle') {
|
||||||
this.setState('watching');
|
this.setState('watching');
|
||||||
this.completionMessageTime = null;
|
this.completionMessageTime = null;
|
||||||
@@ -2630,14 +2567,14 @@ export class RespawnController extends EventEmitter {
|
|||||||
* This ensures Claude has finished processing before we send the next command.
|
* This ensures Claude has finished processing before we send the next command.
|
||||||
*/
|
*/
|
||||||
private startStepConfirmTimer(step: 'update' | 'init' | 'kickstart'): void {
|
private startStepConfirmTimer(step: 'update' | 'init' | 'kickstart'): void {
|
||||||
this.cancelTrackedTimer('step-confirm', this.stepConfirmTimer, 'restarting');
|
this.cancelTrackedTimer('step-confirm','restarting');
|
||||||
this.stepConfirmTimer = null;
|
|
||||||
|
|
||||||
this.stepConfirmTimer = this.startTrackedTimer(
|
|
||||||
|
this.startTrackedTimer(
|
||||||
'step-confirm',
|
'step-confirm',
|
||||||
this.config.completionConfirmMs,
|
this.config.completionConfirmMs,
|
||||||
() => {
|
() => {
|
||||||
this.stepConfirmTimer = null;
|
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
const msSinceOutput = Date.now() - this.lastOutputTime;
|
const msSinceOutput = Date.now() - this.lastOutputTime;
|
||||||
|
|
||||||
@@ -2670,8 +2607,8 @@ export class RespawnController extends EventEmitter {
|
|||||||
* Cancel step confirmation if working patterns detected.
|
* Cancel step confirmation if working patterns detected.
|
||||||
*/
|
*/
|
||||||
private cancelStepConfirm(): void {
|
private cancelStepConfirm(): void {
|
||||||
this.cancelTrackedTimer('step-confirm', this.stepConfirmTimer, 'working detected');
|
this.cancelTrackedTimer('step-confirm','working detected');
|
||||||
this.stepConfirmTimer = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -2833,11 +2770,11 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.terminalBuffer.clear(); // Clear buffer for fresh detection
|
this.terminalBuffer.clear(); // Clear buffer for fresh detection
|
||||||
this.clearWorkingPatternWindow(); // Clear rolling window
|
this.clearWorkingPatternWindow(); // Clear rolling window
|
||||||
|
|
||||||
this.stepTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'step-delay',
|
'step-delay',
|
||||||
this.config.interStepDelayMs,
|
this.config.interStepDelayMs,
|
||||||
async () => {
|
async () => {
|
||||||
this.stepTimer = null;
|
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
|
|
||||||
// Use RALPH_STATUS RECOMMENDATION if available, otherwise fall back to config
|
// Use RALPH_STATUS RECOMMENDATION if available, otherwise fall back to config
|
||||||
@@ -2874,11 +2811,11 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.terminalBuffer.clear();
|
this.terminalBuffer.clear();
|
||||||
this.clearWorkingPatternWindow();
|
this.clearWorkingPatternWindow();
|
||||||
|
|
||||||
this.stepTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'step-delay',
|
'step-delay',
|
||||||
this.config.interStepDelayMs,
|
this.config.interStepDelayMs,
|
||||||
async () => {
|
async () => {
|
||||||
this.stepTimer = null;
|
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
this.logAction('command', 'Sending: /clear');
|
this.logAction('command', 'Sending: /clear');
|
||||||
await this.session.writeViaMux('/clear\r'); // \r triggers Enter in Ink/Claude CLI
|
await this.session.writeViaMux('/clear\r'); // \r triggers Enter in Ink/Claude CLI
|
||||||
@@ -2887,11 +2824,11 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.promptDetected = false;
|
this.promptDetected = false;
|
||||||
|
|
||||||
// Start fallback timer - if no prompt detected after 10s, proceed to /init anyway
|
// Start fallback timer - if no prompt detected after 10s, proceed to /init anyway
|
||||||
this.clearFallbackTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'clear-fallback',
|
'clear-fallback',
|
||||||
RespawnController.CLEAR_FALLBACK_TIMEOUT_MS,
|
RespawnController.CLEAR_FALLBACK_TIMEOUT_MS,
|
||||||
() => {
|
() => {
|
||||||
this.clearFallbackTimer = null;
|
|
||||||
if (this._state === 'waiting_clear') {
|
if (this._state === 'waiting_clear') {
|
||||||
this.logAction('step', '/clear fallback: proceeding to /init');
|
this.logAction('step', '/clear fallback: proceeding to /init');
|
||||||
this.emit('stepCompleted', 'clear');
|
this.emit('stepCompleted', 'clear');
|
||||||
@@ -2918,11 +2855,11 @@ export class RespawnController extends EventEmitter {
|
|||||||
this.terminalBuffer.clear();
|
this.terminalBuffer.clear();
|
||||||
this.clearWorkingPatternWindow();
|
this.clearWorkingPatternWindow();
|
||||||
|
|
||||||
this.stepTimer = this.startTrackedTimer(
|
this.startTrackedTimer(
|
||||||
'step-delay',
|
'step-delay',
|
||||||
this.config.interStepDelayMs,
|
this.config.interStepDelayMs,
|
||||||
async () => {
|
async () => {
|
||||||
this.stepTimer = null;
|
|
||||||
if (this._state === 'stopped') return;
|
if (this._state === 'stopped') return;
|
||||||
this.logAction('command', 'Sending: /init');
|
this.logAction('command', 'Sending: /init');
|
||||||
await this.session.writeViaMux('/init\r'); // \r triggers Enter in Ink/Claude CLI
|
await this.session.writeViaMux('/init\r'); // \r triggers Enter in Ink/Claude CLI
|
||||||
|
|||||||
Reference in New Issue
Block a user