feat: immediate pending detection for all step types
This commit is contained in:
@@ -286,17 +286,13 @@ 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 {
|
||||
// Single RPC: GetAllCascadeTrajectories
|
||||
const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
|
||||
if (!allTraj?.trajectorySummaries)
|
||||
return;
|
||||
// Find the most recently modified session (or current active)
|
||||
let bestSession = null;
|
||||
let bestSessionId = '';
|
||||
let bestModTime = '';
|
||||
@@ -320,57 +316,115 @@ 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;
|
||||
}
|
||||
// ── 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
|
||||
const delta = currentCount - lastKnownStepCount;
|
||||
lastKnownStepCount = currentCount;
|
||||
if (delta > 0) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
||||
}
|
||||
// ── IMMEDIATE PENDING DETECTION ──
|
||||
// On EVERY poll: check last 3 steps for non-DONE status
|
||||
// This catches: file review, file access permission, command approval
|
||||
if (isRunning) {
|
||||
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
|
||||
// Check last 3 steps (some may be in-flight)
|
||||
const checkCount = Math.min(3, steps.length);
|
||||
for (let i = steps.length - checkCount; i < steps.length; i++) {
|
||||
const step = steps[i];
|
||||
const stepStatus = (step.status || '').replace('CORTEX_STEP_STATUS_', '');
|
||||
const stepType = (step.type || '').replace('CORTEX_STEP_TYPE_', '');
|
||||
const stepIdx = step.metadata?.sourceTrajectoryStepInfo?.stepIndex ?? i;
|
||||
// Skip already-handled steps
|
||||
if (stepIdx <= lastPendingStepIndex)
|
||||
continue;
|
||||
// Skip completed/rejected steps
|
||||
if (stepStatus === 'DONE' || stepStatus === 'REJECTED')
|
||||
continue;
|
||||
// ── Non-DONE step found! Create pending based on type ──
|
||||
let cmd = '';
|
||||
let desc = '';
|
||||
const toolName = step.metadata?.toolCall?.name || '';
|
||||
let argsJson = '';
|
||||
try {
|
||||
const args = JSON.parse(lastStep.metadata.toolCall.argumentsJson || '{}');
|
||||
cmd = args.CommandLine || args.command || toolName;
|
||||
desc = `Command: ${cmd}`;
|
||||
argsJson = step.metadata?.toolCall?.argumentsJson || '';
|
||||
}
|
||||
catch { }
|
||||
if (toolName === 'run_command' || toolName === 'send_command_input') {
|
||||
// Command execution approval
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.CommandLine || args.command || args.Input || toolName;
|
||||
}
|
||||
catch {
|
||||
cmd = toolName;
|
||||
}
|
||||
desc = `명령어 실행 승인 (${stepType})`;
|
||||
}
|
||||
else if (toolName === 'browser_subagent') {
|
||||
// Browser subagent
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.Task?.substring(0, 150) || 'browser task';
|
||||
}
|
||||
catch {
|
||||
cmd = 'browser_subagent';
|
||||
}
|
||||
desc = `브라우저 서브에이전트 실행`;
|
||||
}
|
||||
else if (stepType === 'CODE_ACTION' || toolName === 'replace_file_content' || toolName === 'multi_replace_file_content' || toolName === 'write_to_file') {
|
||||
// File modification review
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.TargetFile || args.target_file || toolName;
|
||||
}
|
||||
catch {
|
||||
cmd = toolName;
|
||||
}
|
||||
desc = `파일 수정 검토 요청`;
|
||||
}
|
||||
else if (toolName === 'view_file' || toolName === 'view_file_outline' || toolName === 'view_code_item') {
|
||||
// File access (usually auto-approved, but handle if pending)
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.AbsolutePath || args.File || toolName;
|
||||
}
|
||||
catch {
|
||||
cmd = toolName;
|
||||
}
|
||||
desc = `파일 접근 권한 요청`;
|
||||
}
|
||||
else if (toolName === 'notify_user') {
|
||||
// AI asking for user feedback — this needs a different response
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.Message?.substring(0, 200) || 'notify_user';
|
||||
}
|
||||
catch {
|
||||
cmd = 'notify_user';
|
||||
}
|
||||
desc = `사용자 피드백 요청`;
|
||||
}
|
||||
else if (toolName) {
|
||||
cmd = toolName;
|
||||
desc = `Tool: ${toolName}`;
|
||||
desc = `도구 실행: ${toolName}`;
|
||||
}
|
||||
// Auto-create pending file
|
||||
else {
|
||||
cmd = stepType || 'unknown';
|
||||
desc = `${stepType} (${stepStatus})`;
|
||||
}
|
||||
// Create pending for Discord
|
||||
const rid = Date.now().toString();
|
||||
const pending = {
|
||||
request_id: rid,
|
||||
conversation_id: bestSessionId,
|
||||
command: cmd.substring(0, 200),
|
||||
command: cmd.substring(0, 500),
|
||||
description: desc.substring(0, 200),
|
||||
timestamp: Date.now() / 1000,
|
||||
status: 'pending',
|
||||
@@ -378,30 +432,24 @@ function setupMonitor() {
|
||||
step_index: stepIdx,
|
||||
step_type: stepType,
|
||||
step_status: stepStatus,
|
||||
tool_name: toolName,
|
||||
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)}`);
|
||||
logToFile(`[PENDING] step=${stepIdx} type=${stepType} status=${stepStatus} tool=${toolName} cmd=${cmd.substring(0, 60)}`);
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] PENDING! step=${stepIdx} ${toolName || stepType} → pending/${rid}.json`);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
logToFile(`[WAITING] step query failed: ${e.message}`);
|
||||
// Only log occasionally to avoid spam
|
||||
if (pollCount % 10 === 0) {
|
||||
logToFile(`[PENDING] step query error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
stalledPolls = 0;
|
||||
}
|
||||
const delta = currentCount - lastKnownStepCount;
|
||||
lastKnownStepCount = currentCount;
|
||||
if (delta > 0) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
||||
}
|
||||
// ── Process latestNotifyUserStep ──
|
||||
const notifyStep = bestSession.latestNotifyUserStep;
|
||||
if (notifyStep && notifyStep.stepIndex > lastNotifyStepIndex) {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -254,18 +254,14 @@ 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 {
|
||||
// Single RPC: GetAllCascadeTrajectories
|
||||
const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
|
||||
if (!allTraj?.trajectorySummaries) return;
|
||||
|
||||
// Find the most recently modified session (or current active)
|
||||
let bestSession: any = null;
|
||||
let bestSessionId = '';
|
||||
let bestModTime = '';
|
||||
@@ -290,56 +286,96 @@ 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;
|
||||
}
|
||||
|
||||
// ── 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
|
||||
const delta = currentCount - lastKnownStepCount;
|
||||
lastKnownStepCount = currentCount;
|
||||
|
||||
if (delta > 0) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
||||
}
|
||||
|
||||
// ── IMMEDIATE PENDING DETECTION ──
|
||||
// On EVERY poll: check last 3 steps for non-DONE status
|
||||
// This catches: file review, file access permission, command approval
|
||||
if (isRunning) {
|
||||
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;
|
||||
// Check last 3 steps (some may be in-flight)
|
||||
const checkCount = Math.min(3, steps.length);
|
||||
for (let i = steps.length - checkCount; i < steps.length; i++) {
|
||||
const step = steps[i];
|
||||
const stepStatus = (step.status || '').replace('CORTEX_STEP_STATUS_', '');
|
||||
const stepType = (step.type || '').replace('CORTEX_STEP_TYPE_', '');
|
||||
const stepIdx = step.metadata?.sourceTrajectoryStepInfo?.stepIndex ?? i;
|
||||
|
||||
// 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 || '';
|
||||
// Skip already-handled steps
|
||||
if (stepIdx <= lastPendingStepIndex) continue;
|
||||
// Skip completed/rejected steps
|
||||
if (stepStatus === 'DONE' || stepStatus === 'REJECTED') continue;
|
||||
|
||||
if (lastStep.terminalCommand?.command) {
|
||||
cmd = lastStep.terminalCommand.command;
|
||||
desc = `Terminal: ${cmd}`;
|
||||
} else if (toolName === 'run_command') {
|
||||
// Extract from toolCall arguments
|
||||
// ── Non-DONE step found! Create pending based on type ──
|
||||
let cmd = '';
|
||||
let desc = '';
|
||||
const toolName = step.metadata?.toolCall?.name || '';
|
||||
let argsJson = '';
|
||||
try { argsJson = step.metadata?.toolCall?.argumentsJson || ''; } catch { }
|
||||
|
||||
if (toolName === 'run_command' || toolName === 'send_command_input') {
|
||||
// Command execution approval
|
||||
try {
|
||||
const args = JSON.parse(lastStep.metadata.toolCall.argumentsJson || '{}');
|
||||
cmd = args.CommandLine || args.command || toolName;
|
||||
desc = `Command: ${cmd}`;
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.CommandLine || args.command || args.Input || toolName;
|
||||
} catch { cmd = toolName; }
|
||||
desc = `명령어 실행 승인 (${stepType})`;
|
||||
} else if (toolName === 'browser_subagent') {
|
||||
// Browser subagent
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.Task?.substring(0, 150) || 'browser task';
|
||||
} catch { cmd = 'browser_subagent'; }
|
||||
desc = `브라우저 서브에이전트 실행`;
|
||||
} else if (stepType === 'CODE_ACTION' || toolName === 'replace_file_content' || toolName === 'multi_replace_file_content' || toolName === 'write_to_file') {
|
||||
// File modification review
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.TargetFile || args.target_file || toolName;
|
||||
} catch { cmd = toolName; }
|
||||
desc = `파일 수정 검토 요청`;
|
||||
} else if (toolName === 'view_file' || toolName === 'view_file_outline' || toolName === 'view_code_item') {
|
||||
// File access (usually auto-approved, but handle if pending)
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.AbsolutePath || args.File || toolName;
|
||||
} catch { cmd = toolName; }
|
||||
desc = `파일 접근 권한 요청`;
|
||||
} else if (toolName === 'notify_user') {
|
||||
// AI asking for user feedback — this needs a different response
|
||||
try {
|
||||
const args = JSON.parse(argsJson || '{}');
|
||||
cmd = args.Message?.substring(0, 200) || 'notify_user';
|
||||
} catch { cmd = 'notify_user'; }
|
||||
desc = `사용자 피드백 요청`;
|
||||
} else if (toolName) {
|
||||
cmd = toolName;
|
||||
desc = `Tool: ${toolName}`;
|
||||
desc = `도구 실행: ${toolName}`;
|
||||
} else {
|
||||
cmd = stepType || 'unknown';
|
||||
desc = `${stepType} (${stepStatus})`;
|
||||
}
|
||||
|
||||
// Auto-create pending file
|
||||
// Create pending for Discord
|
||||
const rid = Date.now().toString();
|
||||
const pending = {
|
||||
request_id: rid,
|
||||
conversation_id: bestSessionId,
|
||||
command: cmd.substring(0, 200),
|
||||
command: cmd.substring(0, 500),
|
||||
description: desc.substring(0, 200),
|
||||
timestamp: Date.now() / 1000,
|
||||
status: 'pending',
|
||||
@@ -347,28 +383,22 @@ function setupMonitor() {
|
||||
step_index: stepIdx,
|
||||
step_type: stepType,
|
||||
step_status: stepStatus,
|
||||
tool_name: toolName,
|
||||
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)}`);
|
||||
logToFile(`[PENDING] step=${stepIdx} type=${stepType} status=${stepStatus} tool=${toolName} cmd=${cmd.substring(0, 60)}`);
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] PENDING! step=${stepIdx} ${toolName || stepType} → pending/${rid}.json`);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
logToFile(`[WAITING] step query failed: ${e.message}`);
|
||||
// Only log occasionally to avoid spam
|
||||
if (pollCount % 10 === 0) {
|
||||
logToFile(`[PENDING] step query error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
stalledPolls = 0;
|
||||
}
|
||||
|
||||
const delta = currentCount - lastKnownStepCount;
|
||||
lastKnownStepCount = currentCount;
|
||||
if (delta > 0) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
||||
}
|
||||
|
||||
// ── Process latestNotifyUserStep ──
|
||||
|
||||
Reference in New Issue
Block a user