feat: immediate pending detection for all step types
This commit is contained in:
@@ -286,17 +286,13 @@ function setupMonitor() {
|
|||||||
let lastKnownStepCount = 0;
|
let lastKnownStepCount = 0;
|
||||||
let lastNotifyStepIndex = -1;
|
let lastNotifyStepIndex = -1;
|
||||||
let lastTaskStepIndex = -1;
|
let lastTaskStepIndex = -1;
|
||||||
// WAITING detection
|
|
||||||
let stalledPolls = 0;
|
|
||||||
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step
|
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
pollCount++;
|
pollCount++;
|
||||||
try {
|
try {
|
||||||
// Single RPC: GetAllCascadeTrajectories
|
|
||||||
const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
|
const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
|
||||||
if (!allTraj?.trajectorySummaries)
|
if (!allTraj?.trajectorySummaries)
|
||||||
return;
|
return;
|
||||||
// Find the most recently modified session (or current active)
|
|
||||||
let bestSession = null;
|
let bestSession = null;
|
||||||
let bestSessionId = '';
|
let bestSessionId = '';
|
||||||
let bestModTime = '';
|
let bestModTime = '';
|
||||||
@@ -320,88 +316,140 @@ function setupMonitor() {
|
|||||||
lastKnownStepCount = currentCount;
|
lastKnownStepCount = currentCount;
|
||||||
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
|
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
|
||||||
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
|
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
|
||||||
stalledPolls = 0;
|
|
||||||
lastPendingStepIndex = -1;
|
lastPendingStepIndex = -1;
|
||||||
writeRegistration(activeSessionId);
|
writeRegistration(activeSessionId);
|
||||||
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
|
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
|
||||||
return;
|
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
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
stalledPolls = 0;
|
|
||||||
}
|
|
||||||
const delta = currentCount - lastKnownStepCount;
|
const delta = currentCount - lastKnownStepCount;
|
||||||
lastKnownStepCount = currentCount;
|
lastKnownStepCount = currentCount;
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
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) {
|
||||||
|
// 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 {
|
||||||
|
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 = `도구 실행: ${toolName}`;
|
||||||
|
}
|
||||||
|
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, 500),
|
||||||
|
description: desc.substring(0, 200),
|
||||||
|
timestamp: Date.now() / 1000,
|
||||||
|
status: 'pending',
|
||||||
|
project_name: projectName,
|
||||||
|
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;
|
||||||
|
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) {
|
||||||
|
// Only log occasionally to avoid spam
|
||||||
|
if (pollCount % 10 === 0) {
|
||||||
|
logToFile(`[PENDING] step query error: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// ── Process latestNotifyUserStep ──
|
// ── Process latestNotifyUserStep ──
|
||||||
const notifyStep = bestSession.latestNotifyUserStep;
|
const notifyStep = bestSession.latestNotifyUserStep;
|
||||||
if (notifyStep && notifyStep.stepIndex > lastNotifyStepIndex) {
|
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 lastKnownStepCount = 0;
|
||||||
let lastNotifyStepIndex = -1;
|
let lastNotifyStepIndex = -1;
|
||||||
let lastTaskStepIndex = -1;
|
let lastTaskStepIndex = -1;
|
||||||
// WAITING detection
|
|
||||||
let stalledPolls = 0;
|
|
||||||
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step
|
let lastPendingStepIndex = -1; // dedup: don't re-create pending for same step
|
||||||
|
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
pollCount++;
|
pollCount++;
|
||||||
try {
|
try {
|
||||||
// Single RPC: GetAllCascadeTrajectories
|
|
||||||
const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
|
const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
|
||||||
if (!allTraj?.trajectorySummaries) return;
|
if (!allTraj?.trajectorySummaries) return;
|
||||||
|
|
||||||
// Find the most recently modified session (or current active)
|
|
||||||
let bestSession: any = null;
|
let bestSession: any = null;
|
||||||
let bestSessionId = '';
|
let bestSessionId = '';
|
||||||
let bestModTime = '';
|
let bestModTime = '';
|
||||||
@@ -290,87 +286,121 @@ function setupMonitor() {
|
|||||||
lastKnownStepCount = currentCount;
|
lastKnownStepCount = currentCount;
|
||||||
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
|
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
|
||||||
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
|
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
|
||||||
stalledPolls = 0;
|
|
||||||
lastPendingStepIndex = -1;
|
lastPendingStepIndex = -1;
|
||||||
writeRegistration(activeSessionId);
|
writeRegistration(activeSessionId);
|
||||||
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
|
console.log(`Gravity Bridge: [POLL#${pollCount}] session: ${activeSessionId.substring(0, 8)} "${currentTitle}" steps=${currentCount} ${isRunning ? 'RUNNING' : 'idle'}`);
|
||||||
return;
|
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
|
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
stalledPolls = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const delta = currentCount - lastKnownStepCount;
|
const delta = currentCount - lastKnownStepCount;
|
||||||
lastKnownStepCount = currentCount;
|
lastKnownStepCount = currentCount;
|
||||||
|
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
console.log(`Gravity Bridge: [POLL#${pollCount}] +${delta} steps (${currentCount}) "${currentTitle}"`);
|
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) {
|
||||||
|
// 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 { 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 = `도구 실행: ${toolName}`;
|
||||||
|
} 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, 500),
|
||||||
|
description: desc.substring(0, 200),
|
||||||
|
timestamp: Date.now() / 1000,
|
||||||
|
status: 'pending',
|
||||||
|
project_name: projectName,
|
||||||
|
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;
|
||||||
|
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) {
|
||||||
|
// Only log occasionally to avoid spam
|
||||||
|
if (pollCount % 10 === 0) {
|
||||||
|
logToFile(`[PENDING] step query error: ${e.message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Process latestNotifyUserStep ──
|
// ── Process latestNotifyUserStep ──
|
||||||
const notifyStep = bestSession.latestNotifyUserStep;
|
const notifyStep = bestSession.latestNotifyUserStep;
|
||||||
if (notifyStep && notifyStep.stepIndex > lastNotifyStepIndex) {
|
if (notifyStep && notifyStep.stepIndex > lastNotifyStepIndex) {
|
||||||
|
|||||||
Reference in New Issue
Block a user