feat: auto-WAITING detection via stall + step query

This commit is contained in:
2026-03-08 09:56:01 +09:00
parent 0bf3217ae1
commit 7a38e7ecc9
3 changed files with 145 additions and 11 deletions

View File

@@ -286,6 +286,9 @@ function setupMonitor() {
let lastKnownStepCount = 0;
let lastNotifyStepIndex = -1;
let lastTaskStepIndex = -1;
// WAITING detection
let stalledPolls = 0;
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step
setInterval(async () => {
pollCount++;
try {
@@ -317,16 +320,82 @@ function setupMonitor() {
lastKnownStepCount = currentCount;
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
stalledPolls = 0;
lastPendingStepIndex = -1;
writeRegistration(activeSessionId);
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
return;
}
// No change?
if (currentCount <= lastKnownStepCount && pollCount > 1) {
if (pollCount % 30 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] idle steps=${currentCount}`);
// ── WAITING Detection ──
// stepCount frozen + session still RUNNING = likely WAITING for user approval
if (currentCount === lastKnownStepCount && isRunning) {
stalledPolls++;
if (stalledPolls >= 2) {
// Query last steps to check for WAITING/PENDING
try {
const stepsResp = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', { cascadeId: bestSessionId });
const steps = stepsResp?.steps || [];
if (steps.length > 0) {
const lastStep = steps[steps.length - 1];
const stepStatus = (lastStep.status || '').replace('CORTEX_STEP_STATUS_', '');
const stepType = (lastStep.type || '').replace('CORTEX_STEP_TYPE_', '');
const stepIdx = lastStep.metadata?.sourceTrajectoryStepInfo?.stepIndex ?? steps.length - 1;
// Non-DONE step while session is RUNNING = user action needed
if (stepStatus !== 'DONE' && stepStatus !== 'REJECTED' && stepIdx > lastPendingStepIndex) {
// Extract command info from step
let cmd = 'unknown';
let desc = `${stepType} (${stepStatus})`;
const toolName = lastStep.metadata?.toolCall?.name || '';
if (lastStep.terminalCommand?.command) {
cmd = lastStep.terminalCommand.command;
desc = `Terminal: ${cmd}`;
}
else if (toolName === 'run_command') {
// Extract from toolCall arguments
try {
const args = JSON.parse(lastStep.metadata.toolCall.argumentsJson || '{}');
cmd = args.CommandLine || args.command || toolName;
desc = `Command: ${cmd}`;
}
catch {
cmd = toolName;
}
}
else if (toolName) {
cmd = toolName;
desc = `Tool: ${toolName}`;
}
// Auto-create pending file
const rid = Date.now().toString();
const pending = {
request_id: rid,
conversation_id: bestSessionId,
command: cmd.substring(0, 200),
description: desc.substring(0, 200),
timestamp: Date.now() / 1000,
status: 'pending',
project_name: projectName,
step_index: stepIdx,
step_type: stepType,
step_status: stepStatus,
auto_detected: true,
};
const pendingDir = path.join(bridgePath, 'pending');
fs.writeFileSync(path.join(pendingDir, `${rid}.json`), JSON.stringify(pending, null, 2));
lastPendingStepIndex = stepIdx;
stalledPolls = 0; // reset stall counter after pending created
logToFile(`[WAITING] detected step=${stepIdx} type=${stepType} status=${stepStatus} cmd=${cmd.substring(0, 60)}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] WAITING detected! step=${stepIdx} cmd=${cmd.substring(0, 40)}`);
}
}
}
catch (e) {
logToFile(`[WAITING] step query failed: ${e.message}`);
}
}
// Still process notify/task below (stepIndex may update without stepCount change)
}
else {
stalledPolls = 0;
}
const delta = currentCount - lastKnownStepCount;
lastKnownStepCount = currentCount;

File diff suppressed because one or more lines are too long

View File

@@ -254,6 +254,9 @@ function setupMonitor() {
let lastKnownStepCount = 0;
let lastNotifyStepIndex = -1;
let lastTaskStepIndex = -1;
// WAITING detection
let stalledPolls = 0;
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step
setInterval(async () => {
pollCount++;
@@ -287,17 +290,79 @@ function setupMonitor() {
lastKnownStepCount = currentCount;
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
stalledPolls = 0;
lastPendingStepIndex = -1;
writeRegistration(activeSessionId);
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
return;
}
// No change?
if (currentCount <= lastKnownStepCount && pollCount > 1) {
if (pollCount % 30 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] idle steps=${currentCount}`);
// ── WAITING Detection ──
// stepCount frozen + session still RUNNING = likely WAITING for user approval
if (currentCount === lastKnownStepCount && isRunning) {
stalledPolls++;
if (stalledPolls >= 2) {
// Query last steps to check for WAITING/PENDING
try {
const stepsResp = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', { cascadeId: bestSessionId });
const steps = stepsResp?.steps || [];
if (steps.length > 0) {
const lastStep = steps[steps.length - 1];
const stepStatus = (lastStep.status || '').replace('CORTEX_STEP_STATUS_', '');
const stepType = (lastStep.type || '').replace('CORTEX_STEP_TYPE_', '');
const stepIdx = lastStep.metadata?.sourceTrajectoryStepInfo?.stepIndex ?? steps.length - 1;
// Non-DONE step while session is RUNNING = user action needed
if (stepStatus !== 'DONE' && stepStatus !== 'REJECTED' && stepIdx > lastPendingStepIndex) {
// Extract command info from step
let cmd = 'unknown';
let desc = `${stepType} (${stepStatus})`;
const toolName = lastStep.metadata?.toolCall?.name || '';
if (lastStep.terminalCommand?.command) {
cmd = lastStep.terminalCommand.command;
desc = `Terminal: ${cmd}`;
} else if (toolName === 'run_command') {
// Extract from toolCall arguments
try {
const args = JSON.parse(lastStep.metadata.toolCall.argumentsJson || '{}');
cmd = args.CommandLine || args.command || toolName;
desc = `Command: ${cmd}`;
} catch { cmd = toolName; }
} else if (toolName) {
cmd = toolName;
desc = `Tool: ${toolName}`;
}
// Auto-create pending file
const rid = Date.now().toString();
const pending = {
request_id: rid,
conversation_id: bestSessionId,
command: cmd.substring(0, 200),
description: desc.substring(0, 200),
timestamp: Date.now() / 1000,
status: 'pending',
project_name: projectName,
step_index: stepIdx,
step_type: stepType,
step_status: stepStatus,
auto_detected: true,
};
const pendingDir = path.join(bridgePath, 'pending');
fs.writeFileSync(path.join(pendingDir, `${rid}.json`), JSON.stringify(pending, null, 2));
lastPendingStepIndex = stepIdx;
stalledPolls = 0; // reset stall counter after pending created
logToFile(`[WAITING] detected step=${stepIdx} type=${stepType} status=${stepStatus} cmd=${cmd.substring(0, 60)}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] WAITING detected! step=${stepIdx} cmd=${cmd.substring(0, 40)}`);
}
}
} catch (e: any) {
logToFile(`[WAITING] step query failed: ${e.message}`);
}
}
// Still process notify/task below (stepIndex may update without stepCount change)
} else {
stalledPolls = 0;
}
const delta = currentCount - lastKnownStepCount;