fix(bridge): stall-based WAITING detection, remove GetCascadeTrajectorySteps

GetCascadeTrajectorySteps has 775-step hard limit and cannot see
WAITING steps beyond that. No RPC exists for direct WAITING detection.

New approach: if stepCount frozen for 2+ polls (~10s) while status is
RUNNING, treat as WAITING and write pending approval to Discord.
30s cooldown prevents duplicate pending messages.

Also removes the last GetCascadeTrajectorySteps call from Extension -
now only a single GetAllCascadeTrajectories RPC per 5s poll cycle.
This commit is contained in:
2026-03-08 08:05:41 +09:00
parent f6ae9c87a5
commit 9b9c9c71fe
3 changed files with 63 additions and 104 deletions

View File

@@ -244,6 +244,8 @@ function setupMonitor() {
let lastKnownStepCount = 0;
let lastNotifyStepIndex = -1;
let lastTaskStepIndex = -1;
let stallCount = 0; // consecutive polls with no step change while RUNNING
let lastPendingAt = 0; // timestamp of last pending approval write (dedup)
setInterval(async () => {
pollCount++;
@@ -277,17 +279,41 @@ function setupMonitor() {
lastKnownStepCount = currentCount;
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
stallCount = 0;
writeRegistration(activeSessionId);
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
return;
}
// No change in step count?
if (currentCount <= lastKnownStepCount && pollCount > 1) {
if (pollCount % 20 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] idle: ${activeSessionId.substring(0, 8)} steps=${currentCount}`);
// ── STALL DETECTION for WAITING steps ──
// If stepCount is frozen while status is RUNNING → AI is likely waiting for user approval
if (currentCount === lastKnownStepCount && isRunning) {
stallCount++;
// After 2 consecutive stalls (~10s), write pending approval (once per stall period)
if (stallCount === 2 && (Date.now() - lastPendingAt) > 30000) {
lastPendingAt = Date.now();
console.log(`Gravity Bridge: [POLL#${pollCount}] STALL detected (${stallCount} polls, steps=${currentCount}) → writing pending`);
writePendingApproval({
conversation_id: activeSessionId,
command: `⏳ 사용자 확인 대기 중`,
description: `⏳ **AI가 사용자 확인을 기다리고 있습니다**\n\n세션: ${currentTitle}\nstep: ${currentCount}\n\n✅ 승인 또는 ❌ 거부를 선택하세요.`,
});
}
return;
if (pollCount % 20 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] stall=${stallCount} steps=${currentCount} RUNNING`);
}
} else {
// Step count changed or not running — reset stall
if (stallCount > 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] stall cleared (was ${stallCount})`);
}
stallCount = 0;
}
// No new steps? (but process notify/task even without delta)
if (currentCount <= lastKnownStepCount && pollCount > 1) {
// Still process notify/task changes even without step count change
// (they might arrive as updates to existing steps)
}
const delta = currentCount - lastKnownStepCount;
@@ -318,52 +344,6 @@ function setupMonitor() {
console.log(`Gravity Bridge: [POLL#${pollCount}] TASK step=${taskStep.stepIndex} "${tb.taskName}"`);
}
}
// ── Check for WAITING status (pending user approval) ──
if (isRunning) {
// Check lastUserInputStepIndex — if it's far behind stepCount,
// AI might be waiting for user input
const lastUserInput = bestSession.lastUserInputStepIndex || 0;
const gap = currentCount - lastUserInput;
// If gap is small and status is RUNNING, the AI might have a pending step
// We can check via GetCascadeTrajectorySteps for the last few steps (within 775 limit)
if (delta > 0 && gap < 50) {
try {
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
cascadeId: activeSessionId
});
if (stepsData?.steps) {
const last5 = stepsData.steps.slice(-5);
for (const step of last5) {
const sType = String(step.type || '');
const sStatus = String(step.status || '');
if (sStatus.includes('WAITING')) {
const shortType = sType.replace(/CORTEX_STEP_TYPE_/g, '');
let cmd = `${shortType}`;
let desc = `⏳ **대기 중**: ${shortType}`;
if (sType.includes('RUN_COMMAND')) {
const cmdLine = step.runCommand?.commandLine || '';
cmd = `▶️ ${cmdLine.substring(0, 80)}`;
desc = `▶️ **명령 실행 확인**\n\`\`\`\n${cmdLine}\n\`\`\``;
} else if (sType.includes('CODE_ACTION') || sType.includes('WRITE')) {
const file = step.codeAction?.filePath || step.writeToFile?.filePath || '';
cmd = `✏️ 파일: ${file}`;
desc = `✏️ **파일 수정 확인**\n파일: \`${file}\``;
}
writePendingApproval({
conversation_id: activeSessionId,
command: cmd,
description: desc,
});
console.log(`Gravity Bridge: [POLL#${pollCount}] WAITING ${shortType}`);
}
}
}
} catch (rpcErr: any) {
// GetCascadeTrajectorySteps failed — not critical
}
}
}
} catch (e: any) {
if (pollCount <= 5 || pollCount % 20 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);