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:
@@ -275,6 +275,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++;
|
||||
try {
|
||||
@@ -306,16 +308,40 @@ 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;
|
||||
lastKnownStepCount = currentCount;
|
||||
@@ -343,53 +369,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) {
|
||||
// GetCascadeTrajectorySteps failed — not critical
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
if (pollCount <= 5 || pollCount % 20 === 0) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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}`);
|
||||
|
||||
Reference in New Issue
Block a user