fix: module-scope stallProbed + reset after approval for consecutive detection

This commit is contained in:
2026-03-10 11:16:23 +09:00
parent 857e10126d
commit c612c37105
3 changed files with 28 additions and 3 deletions

View File

@@ -430,6 +430,7 @@ const pendingResponses = new Map();
let clickTrigger = null; let clickTrigger = null;
let sessionStalled = false; // true when session is stalled waiting for approval let sessionStalled = false; // true when session is stalled waiting for approval
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step (module-level for tryApprovalStrategies) let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step (module-level for tryApprovalStrategies)
let stallProbed = false; // prevent repeated step probes during same stall (module-level for processResponseFile reset)
// Deep inspect trigger: curl sets this, renderer picks it up and POSTs results back // Deep inspect trigger: curl sets this, renderer picks it up and POSTs results back
let deepInspectRequested = false; let deepInspectRequested = false;
let deepInspectResult = null; let deepInspectResult = null;
@@ -1414,7 +1415,7 @@ function setupMonitor() {
let lastPendingTime = 0; // cooldown: minimum gap between pendings let lastPendingTime = 0; // cooldown: minimum gap between pendings
let sawRunningAfterPending = true; // gate: must see delta>0 before next pending let sawRunningAfterPending = true; // gate: must see delta>0 before next pending
let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval
let stallProbed = false; // prevent repeated step probes during same stall // stallProbed is module-level (used by processResponseFile to reset after approval)
let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay
let wasRunning = false; // track RUNNING→IDLE transition for response capture let wasRunning = false; // track RUNNING→IDLE transition for response capture
let lastUserInputStepIdx = -1; // track user input for response matching let lastUserInputStepIdx = -1; // track user input for response matching
@@ -2014,6 +2015,9 @@ async function processResponseFile(filePath) {
logToFile(`[RESPONSE] strategy result: ${strategyResult}`); logToFile(`[RESPONSE] strategy result: ${strategyResult}`);
} }
logToFile(`[RESPONSE] ${approved ? 'approve' : 'reject'} done (${isDomObserver ? 'dom' : 'step_probe'})`); logToFile(`[RESPONSE] ${approved ? 'approve' : 'reject'} done (${isDomObserver ? 'dom' : 'step_probe'})`);
// Reset stall probe gate so next WAITING step is detected immediately
stallProbed = false;
lastPendingStepIndex = -1;
// Cleanup response file // Cleanup response file
// CRITICAL: DOM observer responses must NOT be deleted here! // CRITICAL: DOM observer responses must NOT be deleted here!
// The renderer polls GET /response/:rid to discover the approval. // The renderer polls GET /response/:rid to discover the approval.
@@ -2184,6 +2188,14 @@ function writePendingApproval(data) {
return; return;
} }
} }
// Dedup: skip if step_probe already created pending for same step_index (within window)
if (existing.status === 'pending' && data.step_index !== undefined && existing.step_index === data.step_index) {
const age = nowMs - (existing.timestamp * 1000);
if (age < DEDUP_WINDOW_MS && age >= 0) {
logToFile(`[DEDUP] skip: step_index ${data.step_index} already pending in ${ef}`);
return;
}
}
} }
} }
catch (dedupErr) { catch (dedupErr) {

File diff suppressed because one or more lines are too long

View File

@@ -407,6 +407,7 @@ const pendingResponses = new Map<string, { approved: boolean } | null>();
let clickTrigger: { action: 'approve' | 'reject'; timestamp: number } | null = null; let clickTrigger: { action: 'approve' | 'reject'; timestamp: number } | null = null;
let sessionStalled = false; // true when session is stalled waiting for approval let sessionStalled = false; // true when session is stalled waiting for approval
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step (module-level for tryApprovalStrategies) let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step (module-level for tryApprovalStrategies)
let stallProbed = false; // prevent repeated step probes during same stall (module-level for processResponseFile reset)
// Deep inspect trigger: curl sets this, renderer picks it up and POSTs results back // Deep inspect trigger: curl sets this, renderer picks it up and POSTs results back
let deepInspectRequested = false; let deepInspectRequested = false;
@@ -1389,7 +1390,7 @@ function setupMonitor() {
let lastPendingTime = 0; // cooldown: minimum gap between pendings let lastPendingTime = 0; // cooldown: minimum gap between pendings
let sawRunningAfterPending = true; // gate: must see delta>0 before next pending let sawRunningAfterPending = true; // gate: must see delta>0 before next pending
let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval let lastModTime = ''; // track lastModifiedTime to distinguish thinking vs approval
let stallProbed = false; // prevent repeated step probes during same stall // stallProbed is module-level (used by processResponseFile to reset after approval)
let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay let lastRelayedTaskText = ''; // dedup TASK_BOUNDARY relay
let wasRunning = false; // track RUNNING→IDLE transition for response capture let wasRunning = false; // track RUNNING→IDLE transition for response capture
let lastUserInputStepIdx = -1; // track user input for response matching let lastUserInputStepIdx = -1; // track user input for response matching
@@ -1973,6 +1974,10 @@ async function processResponseFile(filePath: string) {
logToFile(`[RESPONSE] ${approved ? 'approve' : 'reject'} done (${isDomObserver ? 'dom' : 'step_probe'})`); logToFile(`[RESPONSE] ${approved ? 'approve' : 'reject'} done (${isDomObserver ? 'dom' : 'step_probe'})`);
// Reset stall probe gate so next WAITING step is detected immediately
stallProbed = false;
lastPendingStepIndex = -1;
// Cleanup response file // Cleanup response file
// CRITICAL: DOM observer responses must NOT be deleted here! // CRITICAL: DOM observer responses must NOT be deleted here!
// The renderer polls GET /response/:rid to discover the approval. // The renderer polls GET /response/:rid to discover the approval.
@@ -2129,6 +2134,14 @@ function writePendingApproval(data: { conversation_id: string; command: string;
return; return;
} }
} }
// Dedup: skip if step_probe already created pending for same step_index (within window)
if (existing.status === 'pending' && data.step_index !== undefined && existing.step_index === data.step_index) {
const age = nowMs - (existing.timestamp * 1000);
if (age < DEDUP_WINDOW_MS && age >= 0) {
logToFile(`[DEDUP] skip: step_index ${data.step_index} already pending in ${ef}`);
return;
}
}
} }
} catch (dedupErr: any) { } catch (dedupErr: any) {
logToFile(`[DEDUP] check error (non-fatal): ${dedupErr.message}`); logToFile(`[DEDUP] check error (non-fatal): ${dedupErr.message}`);