aicodeman 0.5.2 → 0.5.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai-checker-base.d.ts.map +1 -1
- package/dist/ai-checker-base.js +3 -2
- package/dist/ai-checker-base.js.map +1 -1
- package/dist/bash-tool-parser.d.ts +6 -0
- package/dist/bash-tool-parser.d.ts.map +1 -1
- package/dist/bash-tool-parser.js +87 -101
- package/dist/bash-tool-parser.js.map +1 -1
- package/dist/file-stream-manager.d.ts.map +1 -1
- package/dist/file-stream-manager.js +2 -1
- package/dist/file-stream-manager.js.map +1 -1
- package/dist/orchestrator-loop.d.ts +2 -0
- package/dist/orchestrator-loop.d.ts.map +1 -1
- package/dist/orchestrator-loop.js +27 -22
- package/dist/orchestrator-loop.js.map +1 -1
- package/dist/orchestrator-verifier.d.ts +1 -1
- package/dist/orchestrator-verifier.d.ts.map +1 -1
- package/dist/orchestrator-verifier.js +3 -2
- package/dist/orchestrator-verifier.js.map +1 -1
- package/dist/plan-orchestrator.d.ts +4 -1
- package/dist/plan-orchestrator.d.ts.map +1 -1
- package/dist/plan-orchestrator.js +66 -88
- package/dist/plan-orchestrator.js.map +1 -1
- package/dist/ralph-status-parser.d.ts +2 -0
- package/dist/ralph-status-parser.d.ts.map +1 -1
- package/dist/ralph-status-parser.js +98 -102
- package/dist/ralph-status-parser.js.map +1 -1
- package/dist/ralph-tracker.d.ts +9 -0
- package/dist/ralph-tracker.d.ts.map +1 -1
- package/dist/ralph-tracker.js +52 -60
- package/dist/ralph-tracker.js.map +1 -1
- package/dist/respawn-controller.d.ts +18 -1
- package/dist/respawn-controller.d.ts.map +1 -1
- package/dist/respawn-controller.js +215 -181
- package/dist/respawn-controller.js.map +1 -1
- package/dist/session-auto-ops.d.ts.map +1 -1
- package/dist/session-auto-ops.js +57 -55
- package/dist/session-auto-ops.js.map +1 -1
- package/dist/session.d.ts +5 -0
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +182 -218
- package/dist/session.js.map +1 -1
- package/dist/state-store.d.ts +6 -0
- package/dist/state-store.d.ts.map +1 -1
- package/dist/state-store.js +67 -79
- package/dist/state-store.js.map +1 -1
- package/dist/subagent-watcher.d.ts +24 -0
- package/dist/subagent-watcher.d.ts.map +1 -1
- package/dist/subagent-watcher.js +215 -220
- package/dist/subagent-watcher.js.map +1 -1
- package/dist/tmux-manager.d.ts +17 -0
- package/dist/tmux-manager.d.ts.map +1 -1
- package/dist/tmux-manager.js +57 -66
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tunnel-manager.d.ts.map +1 -1
- package/dist/tunnel-manager.js +2 -1
- package/dist/tunnel-manager.js.map +1 -1
- package/dist/web/public/api-client.3adebdc2.js.gz +0 -0
- package/dist/web/public/app.16290ae3.js +26 -0
- package/dist/web/public/app.16290ae3.js.br +0 -0
- package/dist/web/public/app.16290ae3.js.gz +0 -0
- package/dist/web/public/constants.64161167.js.gz +0 -0
- package/dist/web/public/index.html +7 -7
- package/dist/web/public/index.html.br +0 -0
- package/dist/web/public/index.html.gz +0 -0
- package/dist/web/public/input-cjk.88082175.js.gz +0 -0
- package/dist/web/public/keyboard-accessory.9fb81db6.js.gz +0 -0
- package/dist/web/public/mobile-handlers.1e2a8ef8.js.gz +0 -0
- package/dist/web/public/mobile.0b213796.css.gz +0 -0
- package/dist/web/public/notification-manager.2d5ea8ec.js.gz +0 -0
- package/dist/web/public/orchestrator-panel.js.gz +0 -0
- package/dist/web/public/{panels-ui.8204db1e.js → panels-ui.2d5b9703.js} +1 -1
- package/dist/web/public/panels-ui.2d5b9703.js.br +0 -0
- package/dist/web/public/panels-ui.2d5b9703.js.gz +0 -0
- package/dist/web/public/{ralph-panel.a2733fd5.js → ralph-panel.61076370.js} +1 -1
- package/dist/web/public/ralph-panel.61076370.js.br +0 -0
- package/dist/web/public/ralph-panel.61076370.js.gz +0 -0
- package/dist/web/public/ralph-wizard.f31ab90e.js.gz +0 -0
- package/dist/web/public/{respawn-ui.372c6ea7.js → respawn-ui.60be6ef5.js} +1 -1
- package/dist/web/public/respawn-ui.60be6ef5.js.br +0 -0
- package/dist/web/public/respawn-ui.60be6ef5.js.gz +0 -0
- package/dist/web/public/{session-ui.72f2f538.js → session-ui.554092ae.js} +1 -1
- package/dist/web/public/session-ui.554092ae.js.br +0 -0
- package/dist/web/public/session-ui.554092ae.js.gz +0 -0
- package/dist/web/public/{settings-ui.bd3eaadb.js → settings-ui.c58b0b9b.js} +7 -7
- package/dist/web/public/settings-ui.c58b0b9b.js.br +0 -0
- package/dist/web/public/settings-ui.c58b0b9b.js.gz +0 -0
- package/dist/web/public/styles.111ff326.css.gz +0 -0
- package/dist/web/public/subagent-windows.a366a4ad.js.gz +0 -0
- package/dist/web/public/sw.js.gz +0 -0
- package/dist/web/public/terminal-ui.474f79df.js.gz +0 -0
- package/dist/web/public/upload.html.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-fit.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-unicode11.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-addon-webgl.min.js.gz +0 -0
- package/dist/web/public/vendor/xterm-zerolag-input.137ad9f0.js.gz +0 -0
- package/dist/web/public/vendor/xterm.css.gz +0 -0
- package/dist/web/public/vendor/xterm.min.js.gz +0 -0
- package/dist/web/public/voice-input.085e9e73.js.gz +0 -0
- package/dist/web/respawn-event-wiring.d.ts +51 -0
- package/dist/web/respawn-event-wiring.d.ts.map +1 -0
- package/dist/web/respawn-event-wiring.js +280 -0
- package/dist/web/respawn-event-wiring.js.map +1 -0
- package/dist/web/route-helpers.d.ts +23 -0
- package/dist/web/route-helpers.d.ts.map +1 -1
- package/dist/web/route-helpers.js +53 -0
- package/dist/web/route-helpers.js.map +1 -1
- package/dist/web/routes/case-routes.d.ts.map +1 -1
- package/dist/web/routes/case-routes.js +2 -11
- package/dist/web/routes/case-routes.js.map +1 -1
- package/dist/web/routes/file-routes.d.ts.map +1 -1
- package/dist/web/routes/file-routes.js +8 -24
- package/dist/web/routes/file-routes.js.map +1 -1
- package/dist/web/routes/orchestrator-routes.d.ts.map +1 -1
- package/dist/web/routes/orchestrator-routes.js +23 -30
- package/dist/web/routes/orchestrator-routes.js.map +1 -1
- package/dist/web/routes/system-routes.d.ts.map +1 -1
- package/dist/web/routes/system-routes.js +17 -71
- package/dist/web/routes/system-routes.js.map +1 -1
- package/dist/web/server.d.ts +4 -51
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +98 -941
- package/dist/web/server.js.map +1 -1
- package/dist/web/session-listener-wiring.d.ts +89 -0
- package/dist/web/session-listener-wiring.d.ts.map +1 -0
- package/dist/web/session-listener-wiring.js +290 -0
- package/dist/web/session-listener-wiring.js.map +1 -0
- package/dist/web/sse-stream-manager.d.ts +91 -0
- package/dist/web/sse-stream-manager.d.ts.map +1 -0
- package/dist/web/sse-stream-manager.js +426 -0
- package/dist/web/sse-stream-manager.js.map +1 -0
- package/package.json +1 -1
- package/dist/web/public/app.e09fd4a6.js +0 -26
- package/dist/web/public/app.e09fd4a6.js.br +0 -0
- package/dist/web/public/app.e09fd4a6.js.gz +0 -0
- package/dist/web/public/panels-ui.8204db1e.js.br +0 -0
- package/dist/web/public/panels-ui.8204db1e.js.gz +0 -0
- package/dist/web/public/ralph-panel.a2733fd5.js.br +0 -0
- package/dist/web/public/ralph-panel.a2733fd5.js.gz +0 -0
- package/dist/web/public/respawn-ui.372c6ea7.js.br +0 -0
- package/dist/web/public/respawn-ui.372c6ea7.js.gz +0 -0
- package/dist/web/public/session-ui.72f2f538.js.br +0 -0
- package/dist/web/public/session-ui.72f2f538.js.gz +0 -0
- package/dist/web/public/settings-ui.bd3eaadb.js.br +0 -0
- package/dist/web/public/settings-ui.bd3eaadb.js.gz +0 -0
|
@@ -53,12 +53,20 @@ import { RespawnAdaptiveTiming } from './respawn-adaptive-timing.js';
|
|
|
53
53
|
import { RespawnCycleMetricsTracker } from './respawn-metrics.js';
|
|
54
54
|
import { calculateHealthScore, shouldSkipClear } from './respawn-health.js';
|
|
55
55
|
import { AI_CHECK_MODEL, AI_IDLE_CHECK_MAX_CONTEXT, AI_PLAN_CHECK_MAX_CONTEXT, AI_IDLE_CHECK_TIMEOUT_MS, AI_IDLE_CHECK_COOLDOWN_MS, AI_PLAN_CHECK_TIMEOUT_MS, AI_PLAN_CHECK_COOLDOWN_MS, } from './config/ai-defaults.js';
|
|
56
|
+
import { getErrorMessage, } from './types.js';
|
|
56
57
|
// ========== Constants ==========
|
|
57
58
|
// COMPLETION_TIME_PATTERN moved to ./respawn-patterns.ts
|
|
58
59
|
/** Pre-filter: numbered option pattern for plan mode detection */
|
|
59
60
|
const PLAN_MODE_OPTION_PATTERN = /\d+\.\s+(Yes|No|Type|Cancel|Skip|Proceed|Approve|Reject)/i;
|
|
60
61
|
/** Pre-filter: selection indicator arrow for plan mode detection */
|
|
61
62
|
const PLAN_MODE_SELECTOR_PATTERN = /[❯>]\s*\d+\./;
|
|
63
|
+
/**
|
|
64
|
+
* Convert milliseconds to a non-negative whole number of seconds for countdown display.
|
|
65
|
+
* Rounds up so that e.g. 1200 ms shows as 2 s (never under-reports remaining time).
|
|
66
|
+
*/
|
|
67
|
+
function formatRemainingSeconds(ms) {
|
|
68
|
+
return Math.max(0, Math.ceil(ms / 1000));
|
|
69
|
+
}
|
|
62
70
|
/** Default configuration values */
|
|
63
71
|
const DEFAULT_CONFIG = {
|
|
64
72
|
idleTimeoutMs: 10000, // 10 seconds of no activity after prompt (legacy, still used as fallback)
|
|
@@ -291,35 +299,39 @@ export class RespawnController extends EventEmitter {
|
|
|
291
299
|
*/
|
|
292
300
|
validateConfig() {
|
|
293
301
|
const c = this.config;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
302
|
+
/**
|
|
303
|
+
* Validate that a timeout value is positive (or non-negative when allowZero is true).
|
|
304
|
+
* Falls back to the DEFAULT_CONFIG value if invalid.
|
|
305
|
+
*/
|
|
306
|
+
const validatePositiveTimeout = (field, allowZero = false) => {
|
|
307
|
+
const value = c[field];
|
|
308
|
+
const invalid = allowZero ? value < 0 : value <= 0;
|
|
309
|
+
if (invalid) {
|
|
310
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
311
|
+
c[field] = DEFAULT_CONFIG[field];
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
const REQUIRED_TIMEOUT_FIELDS = [
|
|
315
|
+
'idleTimeoutMs',
|
|
316
|
+
'completionConfirmMs',
|
|
317
|
+
'noOutputTimeoutMs',
|
|
318
|
+
'interStepDelayMs',
|
|
319
|
+
'aiIdleCheckTimeoutMs',
|
|
320
|
+
'aiIdleCheckMaxContext',
|
|
321
|
+
'aiPlanCheckTimeoutMs',
|
|
322
|
+
'aiPlanCheckMaxContext',
|
|
323
|
+
];
|
|
324
|
+
for (const field of REQUIRED_TIMEOUT_FIELDS) {
|
|
325
|
+
validatePositiveTimeout(field);
|
|
326
|
+
}
|
|
327
|
+
const ALLOW_ZERO_FIELDS = ['autoAcceptDelayMs', 'aiIdleCheckCooldownMs', 'aiPlanCheckCooldownMs'];
|
|
328
|
+
for (const field of ALLOW_ZERO_FIELDS) {
|
|
329
|
+
validatePositiveTimeout(field, true);
|
|
330
|
+
}
|
|
305
331
|
// Ensure completion confirm doesn't exceed no-output timeout
|
|
306
332
|
if (c.completionConfirmMs > c.noOutputTimeoutMs) {
|
|
307
333
|
c.completionConfirmMs = c.noOutputTimeoutMs;
|
|
308
334
|
}
|
|
309
|
-
// Ensure AI check timeouts are positive
|
|
310
|
-
if (c.aiIdleCheckTimeoutMs <= 0)
|
|
311
|
-
c.aiIdleCheckTimeoutMs = DEFAULT_CONFIG.aiIdleCheckTimeoutMs;
|
|
312
|
-
if (c.aiIdleCheckCooldownMs < 0)
|
|
313
|
-
c.aiIdleCheckCooldownMs = DEFAULT_CONFIG.aiIdleCheckCooldownMs;
|
|
314
|
-
if (c.aiIdleCheckMaxContext <= 0)
|
|
315
|
-
c.aiIdleCheckMaxContext = DEFAULT_CONFIG.aiIdleCheckMaxContext;
|
|
316
|
-
// Ensure plan check timeouts are positive
|
|
317
|
-
if (c.aiPlanCheckTimeoutMs <= 0)
|
|
318
|
-
c.aiPlanCheckTimeoutMs = DEFAULT_CONFIG.aiPlanCheckTimeoutMs;
|
|
319
|
-
if (c.aiPlanCheckCooldownMs < 0)
|
|
320
|
-
c.aiPlanCheckCooldownMs = DEFAULT_CONFIG.aiPlanCheckCooldownMs;
|
|
321
|
-
if (c.aiPlanCheckMaxContext <= 0)
|
|
322
|
-
c.aiPlanCheckMaxContext = DEFAULT_CONFIG.aiPlanCheckMaxContext;
|
|
323
335
|
}
|
|
324
336
|
/** Wire up AI checker events to controller events (removes existing listeners first to prevent duplicates) */
|
|
325
337
|
setupAiCheckerListeners() {
|
|
@@ -437,12 +449,12 @@ export class RespawnController extends EventEmitter {
|
|
|
437
449
|
}
|
|
438
450
|
else if (this._state === 'confirming_idle') {
|
|
439
451
|
statusText = `Confirming idle (${confidence}% confidence)`;
|
|
440
|
-
waitingFor = `${
|
|
452
|
+
waitingFor = `${formatRemainingSeconds(this.config.completionConfirmMs - msSinceLastOutput)}s more silence`;
|
|
441
453
|
}
|
|
442
454
|
else if (this._state === 'watching') {
|
|
443
455
|
const aiState = this.aiChecker.getState();
|
|
444
456
|
if (aiState.status === 'cooldown') {
|
|
445
|
-
const remaining =
|
|
457
|
+
const remaining = formatRemainingSeconds(this.aiChecker.getCooldownRemainingMs());
|
|
446
458
|
statusText = `AI Check: WORKING (cooldown ${remaining}s)`;
|
|
447
459
|
waitingFor = 'Cooldown to expire';
|
|
448
460
|
}
|
|
@@ -788,88 +800,13 @@ export class RespawnController extends EventEmitter {
|
|
|
788
800
|
this.lastTokenCount = tokenCount;
|
|
789
801
|
this.lastTokenChangeTime = now;
|
|
790
802
|
}
|
|
791
|
-
//
|
|
792
|
-
|
|
793
|
-
// the work is done, even if working patterns are still in the rolling window
|
|
794
|
-
if (isCompletionMessage(data)) {
|
|
795
|
-
// Clear the rolling window - completion marks a transition point
|
|
796
|
-
this.clearWorkingPatternWindow();
|
|
797
|
-
this.workingDetected = false;
|
|
798
|
-
this.completionMessageTime = now;
|
|
799
|
-
this.cancelAutoAcceptTimer(); // Normal idle flow handles this
|
|
800
|
-
this.log(`Completion message detected: "${data.trim().substring(0, 50)}..."`);
|
|
801
|
-
// In watching state, start completion confirmation timer
|
|
802
|
-
if (this._state === 'watching') {
|
|
803
|
-
this.startCompletionConfirmTimer();
|
|
804
|
-
return;
|
|
805
|
-
}
|
|
806
|
-
// In waiting states, also use confirmation timer (same detection logic)
|
|
807
|
-
// This ensures we wait for Claude to finish before proceeding
|
|
808
|
-
// Note: 'watching' is already handled above and returns early
|
|
809
|
-
switch (this._state) {
|
|
810
|
-
case 'waiting_update':
|
|
811
|
-
this.startStepConfirmTimer('update');
|
|
812
|
-
break;
|
|
813
|
-
case 'waiting_clear':
|
|
814
|
-
this.checkClearComplete(); // /clear is quick, no need to wait
|
|
815
|
-
break;
|
|
816
|
-
case 'waiting_init':
|
|
817
|
-
this.startStepConfirmTimer('init');
|
|
818
|
-
break;
|
|
819
|
-
case 'waiting_kickstart':
|
|
820
|
-
this.startStepConfirmTimer('kickstart');
|
|
821
|
-
break;
|
|
822
|
-
// Non-waiting states: completion message is ignored
|
|
823
|
-
case 'confirming_idle':
|
|
824
|
-
case 'ai_checking':
|
|
825
|
-
case 'sending_update':
|
|
826
|
-
case 'sending_clear':
|
|
827
|
-
case 'sending_init':
|
|
828
|
-
case 'monitoring_init':
|
|
829
|
-
case 'sending_kickstart':
|
|
830
|
-
case 'stopped':
|
|
831
|
-
// Completion message during these states is ignored
|
|
832
|
-
break;
|
|
833
|
-
default:
|
|
834
|
-
assertNever(this._state, `Unhandled RespawnState in completion detection: ${this._state}`);
|
|
835
|
-
}
|
|
803
|
+
// Layer 1: Completion message (PRIMARY) — checked before working patterns
|
|
804
|
+
if (this._detectCompletionMessage(data, now))
|
|
836
805
|
return;
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
const isWorking = this.checkWorkingPattern(data);
|
|
840
|
-
if (isWorking) {
|
|
841
|
-
this.workingDetected = true;
|
|
842
|
-
this.promptDetected = false;
|
|
843
|
-
this.elicitationDetected = false; // Clear on new work cycle
|
|
844
|
-
this.resetHookState(); // Clear hook signals on new work
|
|
845
|
-
this.lastWorkingPatternTime = now;
|
|
846
|
-
// Cancel hook confirmation timer if running
|
|
847
|
-
this.cancelTrackedTimer('hook-confirm', 'working patterns detected');
|
|
848
|
-
// Cancel any pending completion confirmation
|
|
849
|
-
this.cancelCompletionConfirm();
|
|
850
|
-
// Cancel any pending step confirmation (Claude is still working)
|
|
851
|
-
this.cancelStepConfirm();
|
|
852
|
-
// If AI check is running, cancel it (Claude is working)
|
|
853
|
-
if (this._state === 'ai_checking') {
|
|
854
|
-
this.log('Working patterns detected during AI check, cancelling');
|
|
855
|
-
this.aiChecker.cancel();
|
|
856
|
-
this.setState('watching');
|
|
857
|
-
}
|
|
858
|
-
// Cancel plan check if running (Claude started working)
|
|
859
|
-
if (this.planChecker.status === 'checking') {
|
|
860
|
-
this.log('Working patterns detected during plan check, cancelling');
|
|
861
|
-
this.planChecker.cancel();
|
|
862
|
-
}
|
|
863
|
-
// If we're monitoring init and work started, go to watching (no kickstart needed)
|
|
864
|
-
if (this._state === 'monitoring_init') {
|
|
865
|
-
this.log('/init triggered work, skipping kickstart');
|
|
866
|
-
this.emit('stepCompleted', 'init');
|
|
867
|
-
this.completeCycle();
|
|
868
|
-
}
|
|
806
|
+
// Layer 4: Working patterns
|
|
807
|
+
if (this._detectWorkingPattern(data, now))
|
|
869
808
|
return;
|
|
870
|
-
|
|
871
|
-
// In confirming_idle or ai_checking state, substantial output cancels the flow.
|
|
872
|
-
// This prevents false triggers when Claude pauses briefly mid-work.
|
|
809
|
+
// Substantial output during confirming_idle/ai_checking cancels the flow
|
|
873
810
|
if (this._state === 'confirming_idle' || this._state === 'ai_checking') {
|
|
874
811
|
// Strip ANSI escape codes to check if there's real content
|
|
875
812
|
ANSI_ESCAPE_PATTERN_SIMPLE.lastIndex = 0;
|
|
@@ -888,42 +825,125 @@ export class RespawnController extends EventEmitter {
|
|
|
888
825
|
return;
|
|
889
826
|
}
|
|
890
827
|
}
|
|
891
|
-
// Legacy fallback:
|
|
828
|
+
// Legacy fallback: prompt detection
|
|
829
|
+
this._detectPrompt(data);
|
|
830
|
+
}
|
|
831
|
+
_detectCompletionMessage(data, now) {
|
|
832
|
+
if (!isCompletionMessage(data))
|
|
833
|
+
return false;
|
|
834
|
+
// Clear the rolling window - completion marks a transition point
|
|
835
|
+
this.clearWorkingPatternWindow();
|
|
836
|
+
this.workingDetected = false;
|
|
837
|
+
this.completionMessageTime = now;
|
|
838
|
+
this.cancelAutoAcceptTimer(); // Normal idle flow handles this
|
|
839
|
+
this.log(`Completion message detected: "${data.trim().substring(0, 50)}..."`);
|
|
840
|
+
// In watching state, start completion confirmation timer
|
|
841
|
+
if (this._state === 'watching') {
|
|
842
|
+
this.startCompletionConfirmTimer();
|
|
843
|
+
return true;
|
|
844
|
+
}
|
|
845
|
+
// In waiting states, also use confirmation timer (same detection logic)
|
|
846
|
+
// This ensures we wait for Claude to finish before proceeding
|
|
847
|
+
// Note: 'watching' is already handled above and returns early
|
|
848
|
+
switch (this._state) {
|
|
849
|
+
case 'waiting_update':
|
|
850
|
+
this.startStepConfirmTimer('update');
|
|
851
|
+
break;
|
|
852
|
+
case 'waiting_clear':
|
|
853
|
+
this.checkClearComplete(); // /clear is quick, no need to wait
|
|
854
|
+
break;
|
|
855
|
+
case 'waiting_init':
|
|
856
|
+
this.startStepConfirmTimer('init');
|
|
857
|
+
break;
|
|
858
|
+
case 'waiting_kickstart':
|
|
859
|
+
this.startStepConfirmTimer('kickstart');
|
|
860
|
+
break;
|
|
861
|
+
// Non-waiting states: completion message is ignored
|
|
862
|
+
case 'confirming_idle':
|
|
863
|
+
case 'ai_checking':
|
|
864
|
+
case 'sending_update':
|
|
865
|
+
case 'sending_clear':
|
|
866
|
+
case 'sending_init':
|
|
867
|
+
case 'monitoring_init':
|
|
868
|
+
case 'sending_kickstart':
|
|
869
|
+
case 'stopped':
|
|
870
|
+
// Completion message during these states is ignored
|
|
871
|
+
break;
|
|
872
|
+
default:
|
|
873
|
+
assertNever(this._state, `Unhandled RespawnState in completion detection: ${this._state}`);
|
|
874
|
+
}
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
_detectWorkingPattern(data, now) {
|
|
878
|
+
const isWorking = this.checkWorkingPattern(data);
|
|
879
|
+
if (!isWorking)
|
|
880
|
+
return false;
|
|
881
|
+
this.workingDetected = true;
|
|
882
|
+
this.promptDetected = false;
|
|
883
|
+
this.elicitationDetected = false; // Clear on new work cycle
|
|
884
|
+
this.resetHookState(); // Clear hook signals on new work
|
|
885
|
+
this.lastWorkingPatternTime = now;
|
|
886
|
+
// Cancel hook confirmation timer if running
|
|
887
|
+
this.cancelTrackedTimer('hook-confirm', 'working patterns detected');
|
|
888
|
+
// Cancel any pending completion confirmation
|
|
889
|
+
this.cancelCompletionConfirm();
|
|
890
|
+
// Cancel any pending step confirmation (Claude is still working)
|
|
891
|
+
this.cancelStepConfirm();
|
|
892
|
+
// If AI check is running, cancel it (Claude is working)
|
|
893
|
+
if (this._state === 'ai_checking') {
|
|
894
|
+
this.log('Working patterns detected during AI check, cancelling');
|
|
895
|
+
this.aiChecker.cancel();
|
|
896
|
+
this.setState('watching');
|
|
897
|
+
}
|
|
898
|
+
// Cancel plan check if running (Claude started working)
|
|
899
|
+
if (this.planChecker.status === 'checking') {
|
|
900
|
+
this.log('Working patterns detected during plan check, cancelling');
|
|
901
|
+
this.planChecker.cancel();
|
|
902
|
+
}
|
|
903
|
+
// If we're monitoring init and work started, go to watching (no kickstart needed)
|
|
904
|
+
if (this._state === 'monitoring_init') {
|
|
905
|
+
this.log('/init triggered work, skipping kickstart');
|
|
906
|
+
this.emit('stepCompleted', 'init');
|
|
907
|
+
this.completeCycle();
|
|
908
|
+
}
|
|
909
|
+
return true;
|
|
910
|
+
}
|
|
911
|
+
_detectPrompt(data) {
|
|
892
912
|
const hasPrompt = PROMPT_PATTERNS.some((pattern) => data.includes(pattern));
|
|
893
|
-
if (hasPrompt)
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
913
|
+
if (!hasPrompt)
|
|
914
|
+
return;
|
|
915
|
+
this.promptDetected = true;
|
|
916
|
+
this.workingDetected = false;
|
|
917
|
+
// Handle legacy detection in waiting states - also use confirmation timers
|
|
918
|
+
switch (this._state) {
|
|
919
|
+
case 'waiting_update':
|
|
920
|
+
this.startStepConfirmTimer('update');
|
|
921
|
+
break;
|
|
922
|
+
case 'waiting_clear':
|
|
923
|
+
this.checkClearComplete(); // /clear is quick, no need to wait
|
|
924
|
+
break;
|
|
925
|
+
case 'waiting_init':
|
|
926
|
+
this.startStepConfirmTimer('init');
|
|
927
|
+
break;
|
|
928
|
+
case 'monitoring_init':
|
|
929
|
+
this.checkMonitoringInitIdle();
|
|
930
|
+
break;
|
|
931
|
+
case 'waiting_kickstart':
|
|
932
|
+
this.startStepConfirmTimer('kickstart');
|
|
933
|
+
break;
|
|
934
|
+
// Non-waiting states: prompt detection is informational only
|
|
935
|
+
case 'watching':
|
|
936
|
+
case 'confirming_idle':
|
|
937
|
+
case 'ai_checking':
|
|
938
|
+
case 'sending_update':
|
|
939
|
+
case 'sending_clear':
|
|
940
|
+
case 'sending_init':
|
|
941
|
+
case 'sending_kickstart':
|
|
942
|
+
case 'stopped':
|
|
943
|
+
// Prompt detection during these states doesn't trigger action
|
|
944
|
+
break;
|
|
945
|
+
default:
|
|
946
|
+
assertNever(this._state, `Unhandled RespawnState in prompt detection: ${this._state}`);
|
|
927
947
|
}
|
|
928
948
|
}
|
|
929
949
|
/**
|
|
@@ -1167,23 +1187,26 @@ export class RespawnController extends EventEmitter {
|
|
|
1167
1187
|
case 'sending_init':
|
|
1168
1188
|
case 'sending_kickstart':
|
|
1169
1189
|
// For sending states, retry the send
|
|
1170
|
-
this.
|
|
1171
|
-
this.setState('watching');
|
|
1172
|
-
this.startNoOutputTimer();
|
|
1173
|
-
this.startPreFilterTimer();
|
|
1174
|
-
if (this.config.autoAcceptPrompts) {
|
|
1175
|
-
this.startAutoAcceptTimer();
|
|
1176
|
-
}
|
|
1190
|
+
this.recoveryResetToWatching('returning to watching state');
|
|
1177
1191
|
break;
|
|
1178
1192
|
default:
|
|
1179
1193
|
// Fallback: reset to watching
|
|
1180
|
-
this.
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1194
|
+
this.recoveryResetToWatching('fallback to watching state');
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
/**
|
|
1198
|
+
* Reset the controller to watching state during stuck-state recovery.
|
|
1199
|
+
* Sets state to watching and restarts all detection timers.
|
|
1200
|
+
*
|
|
1201
|
+
* @param reason - Human-readable reason for the reset (logged)
|
|
1202
|
+
*/
|
|
1203
|
+
recoveryResetToWatching(reason) {
|
|
1204
|
+
this.log(`Recovery: ${reason}`);
|
|
1205
|
+
this.setState('watching');
|
|
1206
|
+
this.startNoOutputTimer();
|
|
1207
|
+
this.startPreFilterTimer();
|
|
1208
|
+
if (this.config.autoAcceptPrompts) {
|
|
1209
|
+
this.startAutoAcceptTimer();
|
|
1187
1210
|
}
|
|
1188
1211
|
}
|
|
1189
1212
|
/**
|
|
@@ -1372,7 +1395,7 @@ export class RespawnController extends EventEmitter {
|
|
|
1372
1395
|
}
|
|
1373
1396
|
// If on cooldown, don't start check - wait for cooldown to expire
|
|
1374
1397
|
if (this.aiChecker.isOnCooldown()) {
|
|
1375
|
-
this.log(`AI check on cooldown (${
|
|
1398
|
+
this.log(`AI check on cooldown (${formatRemainingSeconds(this.aiChecker.getCooldownRemainingMs())}s remaining), waiting...`);
|
|
1376
1399
|
return;
|
|
1377
1400
|
}
|
|
1378
1401
|
// If already checking, don't start another
|
|
@@ -1448,7 +1471,7 @@ export class RespawnController extends EventEmitter {
|
|
|
1448
1471
|
if (this._state === 'stopped')
|
|
1449
1472
|
return; // Guard against stopped state
|
|
1450
1473
|
if (this._state === 'ai_checking') {
|
|
1451
|
-
const errorMsg =
|
|
1474
|
+
const errorMsg = getErrorMessage(err);
|
|
1452
1475
|
this.logAction('ai-check', `Failed: ${errorMsg.substring(0, 50)}`);
|
|
1453
1476
|
this.emit('aiCheckFailed', errorMsg);
|
|
1454
1477
|
this.setState('watching');
|
|
@@ -1500,33 +1523,13 @@ export class RespawnController extends EventEmitter {
|
|
|
1500
1523
|
* @fires planCheckStarted
|
|
1501
1524
|
*/
|
|
1502
1525
|
tryAutoAccept() {
|
|
1503
|
-
|
|
1504
|
-
if (this._state !== 'watching')
|
|
1526
|
+
if (!this.canAutoAccept())
|
|
1505
1527
|
return;
|
|
1506
|
-
// Don't auto-accept if a completion message was detected (normal idle handles it)
|
|
1507
|
-
if (this.completionMessageTime !== null)
|
|
1508
|
-
return;
|
|
1509
|
-
// Don't auto-accept if disabled
|
|
1510
|
-
if (!this.config.autoAcceptPrompts)
|
|
1511
|
-
return;
|
|
1512
|
-
// Don't auto-accept if we haven't received any output yet (prevents spurious Enter on fresh start)
|
|
1513
|
-
if (!this.hasReceivedOutput)
|
|
1514
|
-
return;
|
|
1515
|
-
// Don't auto-accept if an elicitation dialog (AskUserQuestion) was detected
|
|
1516
|
-
if (this.elicitationDetected) {
|
|
1517
|
-
this.log('Skipping auto-accept: elicitation dialog detected (AskUserQuestion)');
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
// Stage 1: Pre-filter — check if buffer looks like plan mode
|
|
1521
1528
|
const buffer = this.terminalBuffer.value;
|
|
1522
|
-
if (!this.isPlanModePreFilterMatch(buffer)) {
|
|
1523
|
-
this.log('Skipping auto-accept: pre-filter did not match plan mode patterns');
|
|
1524
|
-
return;
|
|
1525
|
-
}
|
|
1526
1529
|
// Stage 2: AI confirmation (if enabled and available)
|
|
1527
1530
|
if (this.config.aiPlanCheckEnabled && this.planChecker.status !== 'disabled') {
|
|
1528
1531
|
if (this.planChecker.isOnCooldown()) {
|
|
1529
|
-
this.log(`Skipping auto-accept: plan checker on cooldown (${
|
|
1532
|
+
this.log(`Skipping auto-accept: plan checker on cooldown (${formatRemainingSeconds(this.planChecker.getCooldownRemainingMs())}s remaining)`);
|
|
1530
1533
|
return;
|
|
1531
1534
|
}
|
|
1532
1535
|
if (this.planChecker.status === 'checking') {
|
|
@@ -1540,6 +1543,37 @@ export class RespawnController extends EventEmitter {
|
|
|
1540
1543
|
// AI plan check disabled — pre-filter passed, send Enter directly
|
|
1541
1544
|
this.sendAutoAcceptEnter();
|
|
1542
1545
|
}
|
|
1546
|
+
/**
|
|
1547
|
+
* Check whether all preconditions for auto-accept are met.
|
|
1548
|
+
* Validates state, config, and pre-filter conditions before attempting auto-accept.
|
|
1549
|
+
*
|
|
1550
|
+
* @returns True if auto-accept should proceed to the AI confirmation stage
|
|
1551
|
+
*/
|
|
1552
|
+
canAutoAccept() {
|
|
1553
|
+
// Only auto-accept in watching state (not during a respawn cycle)
|
|
1554
|
+
if (this._state !== 'watching')
|
|
1555
|
+
return false;
|
|
1556
|
+
// Don't auto-accept if a completion message was detected (normal idle handles it)
|
|
1557
|
+
if (this.completionMessageTime !== null)
|
|
1558
|
+
return false;
|
|
1559
|
+
// Don't auto-accept if disabled
|
|
1560
|
+
if (!this.config.autoAcceptPrompts)
|
|
1561
|
+
return false;
|
|
1562
|
+
// Don't auto-accept if we haven't received any output yet (prevents spurious Enter on fresh start)
|
|
1563
|
+
if (!this.hasReceivedOutput)
|
|
1564
|
+
return false;
|
|
1565
|
+
// Don't auto-accept if an elicitation dialog (AskUserQuestion) was detected
|
|
1566
|
+
if (this.elicitationDetected) {
|
|
1567
|
+
this.log('Skipping auto-accept: elicitation dialog detected (AskUserQuestion)');
|
|
1568
|
+
return false;
|
|
1569
|
+
}
|
|
1570
|
+
// Stage 1: Pre-filter — check if buffer looks like plan mode
|
|
1571
|
+
if (!this.isPlanModePreFilterMatch(this.terminalBuffer.value)) {
|
|
1572
|
+
this.log('Skipping auto-accept: pre-filter did not match plan mode patterns');
|
|
1573
|
+
return false;
|
|
1574
|
+
}
|
|
1575
|
+
return true;
|
|
1576
|
+
}
|
|
1543
1577
|
/**
|
|
1544
1578
|
* Check if the terminal buffer matches plan mode pre-filter patterns.
|
|
1545
1579
|
* Only checks the last 2000 chars (plan mode UI appears at the bottom).
|
|
@@ -1615,7 +1649,7 @@ export class RespawnController extends EventEmitter {
|
|
|
1615
1649
|
}
|
|
1616
1650
|
})
|
|
1617
1651
|
.catch((err) => {
|
|
1618
|
-
const errorMsg =
|
|
1652
|
+
const errorMsg = getErrorMessage(err);
|
|
1619
1653
|
this.emit('planCheckFailed', errorMsg);
|
|
1620
1654
|
this.logAction('plan-check', `Failed: ${errorMsg.substring(0, 50)}`);
|
|
1621
1655
|
});
|