fix(bridge): use GetAllCascadeTrajectories for real-time relay

Root cause: GetCascadeTrajectorySteps has 775-step hard limit,
startStepIndex parameter is completely ignored (verified via direct RPC).

Solution: GetAllCascadeTrajectories returns:
- stepCount: real-time (verified 1413->1457 live)
- latestNotifyUserStep: full notificationContent
- latestTaskBoundaryStep: full taskName/Status/Summary
- stepIndex on each for dedup

E2E verified: Python script -> RPC -> snapshot -> Bot -> Discord
This commit is contained in:
2026-03-08 07:37:39 +09:00
parent c3964f8e7a
commit 854f33b816
4 changed files with 261 additions and 230 deletions

View File

@@ -145,3 +145,16 @@ setInterval (5초마다):
``` ```
이 방식은 `getDiagnostics.lastStepIndex`의 stale 문제를 완전히 우회함. 이 방식은 `getDiagnostics.lastStepIndex`의 stale 문제를 완전히 우회함.
### 추가 발견 (2026-03-08 07:10)
**rawRPC `GetCascadeTrajectorySteps` 775-step 한계**
- RPC는 항상 **최대 775 steps** 반환 (0-774)
- `startStepIndex` 파라미터가 **무시됨** — 항상 처음부터 775개
- 현재 대화가 1275+ steps이면, steps 775-1274는 접근 불가
- 마지막 PLANNER_RESPONSE (24 chars)가 step 774의 오래된 텍스트
**수정**: getDiagnostics로 step count 변화 감지 (3초 폴링) + rawRPC는 content 조회용으로만 사용.
`currentMax` 계산 버그 제거 — getDiagnostics.lastStepIndex를 직접 비교.

View File

@@ -439,135 +439,145 @@ function setupMonitor() {
sdk.monitor.start(3000, 2000); sdk.monitor.start(3000, 2000);
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)'); console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
// PRIMARY RELAY: Direct step polling via rawRPC (getDiagnostics is stale!) // PRIMARY RELAY: GetAllCascadeTrajectories (THE CORRECT API!)
// getDiagnostics.lastStepIndex does NOT update in real-time. //
// Instead, we poll GetCascadeTrajectorySteps directly for the active session. // PROVEN VIA DIRECT RPC TESTING:
// - GetCascadeTrajectorySteps: 775-step hard limit, startStepIndex IGNORED
// - getDiagnostics.lastStepIndex: stale (can lag behind)
// - GetAllCascadeTrajectories:
// stepCount: REAL-TIME (verified 1413→1429 live)
// latestNotifyUserStep: contains FULL notificationContent
// latestTaskBoundaryStep: contains FULL taskName/Status/Summary
// stepIndex on each → perfect for dedup
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
let pollCount = 0; let pollCount = 0;
let activeSessionId = ''; let activeSessionId = '';
let activeSessionTitle = ''; let activeSessionTitle = '';
let polledStepCount = 0; let lastKnownStepCount = 0;
let lastNotifyStepIndex = -1;
let lastTaskStepIndex = -1;
setInterval(async () => { setInterval(async () => {
pollCount++; pollCount++;
try { try {
// Phase 1: Discover active session (first time or periodically) // Single RPC: GetAllCascadeTrajectories
if (!activeSessionId || pollCount % 12 === 0) { const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
try { if (!allTraj?.trajectorySummaries)
const raw = await vscode.commands.executeCommand('antigravity.getDiagnostics'); return;
if (raw && typeof raw === 'string') { // Find the most recently modified session (or current active)
const diag = JSON.parse(raw); let bestSession = null;
if (Array.isArray(diag.recentTrajectories) && diag.recentTrajectories.length > 0) { let bestSessionId = '';
const first = diag.recentTrajectories[0]; let bestModTime = '';
if (first.googleAgentId && first.googleAgentId !== activeSessionId) { for (const [sid, data] of Object.entries(allTraj.trajectorySummaries)) {
activeSessionId = first.googleAgentId; const modTime = data.lastModifiedTime || '';
activeSessionTitle = first.summary || 'Untitled'; if (!bestSession || modTime > bestModTime) {
polledStepCount = first.lastStepIndex || 0; bestSession = data;
console.log(`Gravity Bridge: [POLL#${pollCount}] 🎯 active session: ${activeSessionId.substring(0, 8)} "${activeSessionTitle}" steps=${polledStepCount}`); bestSessionId = sid;
writeRegistration(activeSessionId); bestModTime = modTime;
}
}
if (!bestSession)
return;
const currentCount = bestSession.stepCount || 0;
const currentTitle = (bestSession.summary || 'Untitled').substring(0, 50);
const isRunning = String(bestSession.status || '').includes('RUNNING');
// Session changed?
if (bestSessionId !== activeSessionId) {
activeSessionId = bestSessionId;
activeSessionTitle = currentTitle;
lastKnownStepCount = currentCount;
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
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}`);
}
return;
}
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) {
lastNotifyStepIndex = notifyStep.stepIndex;
const content = notifyStep.step?.notifyUser?.notificationContent || '';
if (content.length > 10) {
writeChatSnapshot(`📣 **알림** (step ${notifyStep.stepIndex})\n\n${content}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] NOTIFY step=${notifyStep.stepIndex} ${content.length} chars`);
}
}
// ── Process latestTaskBoundaryStep ──
const taskStep = bestSession.latestTaskBoundaryStep;
if (taskStep && taskStep.stepIndex > lastTaskStepIndex) {
lastTaskStepIndex = taskStep.stepIndex;
const tb = taskStep.step?.taskBoundary;
if (tb?.taskName) {
const mode = tb.mode ? tb.mode.replace('AGENT_MODE_', '') : '';
writeChatSnapshot(`📋 **[${mode}] ${tb.taskName}**\n${tb.taskStatus || ''}\n\n${tb.taskSummary || ''}`);
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) {
catch (e) { // GetCascadeTrajectorySteps failed — not critical
if (pollCount <= 3) {
console.log(`Gravity Bridge: [POLL#${pollCount}] getDiag error: ${e.message}`);
} }
} }
if (!activeSessionId)
return;
}
// Phase 2: Fetch latest steps via rawRPC (RELIABLE, not stale!)
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
cascadeId: activeSessionId,
startStepIndex: polledStepCount > 2 ? polledStepCount - 2 : 0
});
if (!stepsData || !Array.isArray(stepsData.steps)) {
if (pollCount <= 3) {
console.log(`Gravity Bridge: [POLL#${pollCount}] no steps data`);
}
return;
}
const allSteps = stepsData.steps;
const currentMax = allSteps.length > 0
? Math.max(...allSteps.map((s) => s.stepIndex ?? s.index ?? 0), allSteps.length)
: polledStepCount;
if (currentMax <= polledStepCount) {
// No new steps — log every 12th poll
if (pollCount % 12 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] no change: ${activeSessionId.substring(0, 8)} steps=${currentMax}`);
}
return;
}
// Phase 3: New steps detected! Process them.
const delta = currentMax - polledStepCount;
console.log(`Gravity Bridge: [POLL#${pollCount}] 🆕 +${delta} steps (${polledStepCount}${currentMax}) "${activeSessionTitle}"`);
const newSteps = allSteps.slice(-delta);
polledStepCount = currentMax;
let lastPlannerText = '';
for (const step of newSteps) {
const sType = String(step.type || '');
const sStatus = String(step.status || '');
// PLANNER_RESPONSE → AI text (main content)
if (sType.includes('PLANNER_RESPONSE') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) {
const pr = step.plannerResponse;
const text = pr?.modifiedResponse || pr?.response || '';
if (text && text.length > 0) {
lastPlannerText = text;
console.log(`Gravity Bridge: [POLL#${pollCount}] 📝 planner ${text.length} chars`);
}
}
// NOTIFY_USER → user notification
if (sType.includes('NOTIFY_USER') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) {
const nu = step.notifyUser;
const content = nu?.notificationContent || '';
if (content && content.length > 0) {
writeChatSnapshot(`📣 **알림**\n\n${content}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] 📣 notify ${content.length} chars`);
}
}
// TASK_BOUNDARY → task status update
if (sType.includes('TASK_BOUNDARY') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) {
const tb = step.taskBoundary;
if (tb?.taskName) {
writeChatSnapshot(`📋 **작업**: ${tb.taskName}\n상태: ${tb.taskStatus || ''}\n${tb.taskSummary || ''}`);
}
}
// WAITING steps → pending approval (any type)
if (sStatus.includes('WAITING')) {
const shortType = sType.replace(/CORTEX_STEP_TYPE_/g, '');
let command = `${shortType}`;
let description = `⏳ **대기 중**: ${shortType}`;
if (sType.includes('RUN_COMMAND')) {
const cmd = step.runCommand?.commandLine || '';
command = `▶️ ${cmd.substring(0, 80)}`;
description = `▶️ **명령 실행 확인**\n\`\`\`\n${cmd}\n\`\`\``;
}
else if (sType.includes('CODE_ACTION') || sType.includes('WRITE')) {
const file = step.codeAction?.filePath || step.writeToFile?.filePath || '';
command = `✏️ 파일 수정: ${file}`;
description = `✏️ **파일 수정 확인**\n파일: \`${file}\``;
}
writePendingApproval({
conversation_id: activeSessionId,
command: command,
description: description,
});
console.log(`Gravity Bridge: [POLL#${pollCount}] ⏳ ${shortType} WAITING`);
}
}
// Write latest planner response as snapshot (dedup)
if (lastPlannerText && lastPlannerText !== lastSnapshotText.get(activeSessionId)) {
lastSnapshotText.set(activeSessionId, lastPlannerText);
writeChatSnapshot(`🤖 **${activeSessionTitle}**\n\n${lastPlannerText}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] 💬 snapshot ${lastPlannerText.length} chars`);
} }
} }
catch (e) { catch (e) {
if (pollCount <= 5 || pollCount % 12 === 0) { if (pollCount <= 5 || pollCount % 20 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`); console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);
} }
} }
}, 5000); }, 3000);
} }
// ─── Response Watcher (Discord approval → Antigravity RPC) ─── // ─── Response Watcher (Discord approval → Antigravity RPC) ───
let responseWatcher = null; let responseWatcher = null;

File diff suppressed because one or more lines are too long

View File

@@ -422,141 +422,149 @@ function setupMonitor() {
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)'); console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
// PRIMARY RELAY: Direct step polling via rawRPC (getDiagnostics is stale!) // PRIMARY RELAY: GetAllCascadeTrajectories (THE CORRECT API!)
// getDiagnostics.lastStepIndex does NOT update in real-time. //
// Instead, we poll GetCascadeTrajectorySteps directly for the active session. // PROVEN VIA DIRECT RPC TESTING:
// - GetCascadeTrajectorySteps: 775-step hard limit, startStepIndex IGNORED
// - getDiagnostics.lastStepIndex: stale (can lag behind)
// - GetAllCascadeTrajectories:
// stepCount: REAL-TIME (verified 1413→1429 live)
// latestNotifyUserStep: contains FULL notificationContent
// latestTaskBoundaryStep: contains FULL taskName/Status/Summary
// stepIndex on each → perfect for dedup
// ══════════════════════════════════════════════════════════════════════ // ══════════════════════════════════════════════════════════════════════
let pollCount = 0; let pollCount = 0;
let activeSessionId = ''; let activeSessionId = '';
let activeSessionTitle = ''; let activeSessionTitle = '';
let polledStepCount = 0; let lastKnownStepCount = 0;
let lastNotifyStepIndex = -1;
let lastTaskStepIndex = -1;
setInterval(async () => { setInterval(async () => {
pollCount++; pollCount++;
try { try {
// Phase 1: Discover active session (first time or periodically) // Single RPC: GetAllCascadeTrajectories
if (!activeSessionId || pollCount % 12 === 0) { const allTraj = await sdk.ls.rawRPC('GetAllCascadeTrajectories', {});
try { if (!allTraj?.trajectorySummaries) return;
const raw = await vscode.commands.executeCommand<string>('antigravity.getDiagnostics');
if (raw && typeof raw === 'string') { // Find the most recently modified session (or current active)
const diag = JSON.parse(raw); let bestSession: any = null;
if (Array.isArray(diag.recentTrajectories) && diag.recentTrajectories.length > 0) { let bestSessionId = '';
const first = diag.recentTrajectories[0]; let bestModTime = '';
if (first.googleAgentId && first.googleAgentId !== activeSessionId) { for (const [sid, data] of Object.entries(allTraj.trajectorySummaries) as [string, any][]) {
activeSessionId = first.googleAgentId; const modTime = data.lastModifiedTime || '';
activeSessionTitle = first.summary || 'Untitled'; if (!bestSession || modTime > bestModTime) {
polledStepCount = first.lastStepIndex || 0; bestSession = data;
console.log(`Gravity Bridge: [POLL#${pollCount}] 🎯 active session: ${activeSessionId.substring(0, 8)} "${activeSessionTitle}" steps=${polledStepCount}`); bestSessionId = sid;
writeRegistration(activeSessionId); bestModTime = modTime;
}
}
if (!bestSession) return;
const currentCount = bestSession.stepCount || 0;
const currentTitle = (bestSession.summary || 'Untitled').substring(0, 50);
const isRunning = String(bestSession.status || '').includes('RUNNING');
// Session changed?
if (bestSessionId !== activeSessionId) {
activeSessionId = bestSessionId;
activeSessionTitle = currentTitle;
lastKnownStepCount = currentCount;
lastNotifyStepIndex = bestSession.latestNotifyUserStep?.stepIndex ?? -1;
lastTaskStepIndex = bestSession.latestTaskBoundaryStep?.stepIndex ?? -1;
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}`);
}
return;
}
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) {
lastNotifyStepIndex = notifyStep.stepIndex;
const content = notifyStep.step?.notifyUser?.notificationContent || '';
if (content.length > 10) {
writeChatSnapshot(`📣 **알림** (step ${notifyStep.stepIndex})\n\n${content}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] NOTIFY step=${notifyStep.stepIndex} ${content.length} chars`);
}
}
// ── Process latestTaskBoundaryStep ──
const taskStep = bestSession.latestTaskBoundaryStep;
if (taskStep && taskStep.stepIndex > lastTaskStepIndex) {
lastTaskStepIndex = taskStep.stepIndex;
const tb = taskStep.step?.taskBoundary;
if (tb?.taskName) {
const mode = tb.mode ? tb.mode.replace('AGENT_MODE_', '') : '';
writeChatSnapshot(`📋 **[${mode}] ${tb.taskName}**\n${tb.taskStatus || ''}\n\n${tb.taskSummary || ''}`);
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) {
} catch (e: any) { // GetCascadeTrajectorySteps failed — not critical
if (pollCount <= 3) { console.log(`Gravity Bridge: [POLL#${pollCount}] getDiag error: ${e.message}`); }
}
if (!activeSessionId) return;
}
// Phase 2: Fetch latest steps via rawRPC (RELIABLE, not stale!)
const stepsData = await sdk.ls.rawRPC('GetCascadeTrajectorySteps', {
cascadeId: activeSessionId,
startStepIndex: polledStepCount > 2 ? polledStepCount - 2 : 0
});
if (!stepsData || !Array.isArray(stepsData.steps)) {
if (pollCount <= 3) { console.log(`Gravity Bridge: [POLL#${pollCount}] no steps data`); }
return;
}
const allSteps = stepsData.steps;
const currentMax = allSteps.length > 0
? Math.max(...allSteps.map((s: any) => s.stepIndex ?? s.index ?? 0), allSteps.length)
: polledStepCount;
if (currentMax <= polledStepCount) {
// No new steps — log every 12th poll
if (pollCount % 12 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] no change: ${activeSessionId.substring(0, 8)} steps=${currentMax}`);
}
return;
}
// Phase 3: New steps detected! Process them.
const delta = currentMax - polledStepCount;
console.log(`Gravity Bridge: [POLL#${pollCount}] 🆕 +${delta} steps (${polledStepCount}${currentMax}) "${activeSessionTitle}"`);
const newSteps = allSteps.slice(-delta);
polledStepCount = currentMax;
let lastPlannerText = '';
for (const step of newSteps) {
const sType = String(step.type || '');
const sStatus = String(step.status || '');
// PLANNER_RESPONSE → AI text (main content)
if (sType.includes('PLANNER_RESPONSE') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) {
const pr = step.plannerResponse;
const text = pr?.modifiedResponse || pr?.response || '';
if (text && text.length > 0) {
lastPlannerText = text;
console.log(`Gravity Bridge: [POLL#${pollCount}] 📝 planner ${text.length} chars`);
} }
} }
// NOTIFY_USER → user notification
if (sType.includes('NOTIFY_USER') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) {
const nu = step.notifyUser;
const content = nu?.notificationContent || '';
if (content && content.length > 0) {
writeChatSnapshot(`📣 **알림**\n\n${content}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] 📣 notify ${content.length} chars`);
}
}
// TASK_BOUNDARY → task status update
if (sType.includes('TASK_BOUNDARY') && (sStatus.includes('DONE') || sStatus.includes('COMPLETED'))) {
const tb = step.taskBoundary;
if (tb?.taskName) {
writeChatSnapshot(`📋 **작업**: ${tb.taskName}\n상태: ${tb.taskStatus || ''}\n${tb.taskSummary || ''}`);
}
}
// WAITING steps → pending approval (any type)
if (sStatus.includes('WAITING')) {
const shortType = sType.replace(/CORTEX_STEP_TYPE_/g, '');
let command = `${shortType}`;
let description = `⏳ **대기 중**: ${shortType}`;
if (sType.includes('RUN_COMMAND')) {
const cmd = step.runCommand?.commandLine || '';
command = `▶️ ${cmd.substring(0, 80)}`;
description = `▶️ **명령 실행 확인**\n\`\`\`\n${cmd}\n\`\`\``;
} else if (sType.includes('CODE_ACTION') || sType.includes('WRITE')) {
const file = step.codeAction?.filePath || step.writeToFile?.filePath || '';
command = `✏️ 파일 수정: ${file}`;
description = `✏️ **파일 수정 확인**\n파일: \`${file}\``;
}
writePendingApproval({
conversation_id: activeSessionId,
command: command,
description: description,
});
console.log(`Gravity Bridge: [POLL#${pollCount}] ⏳ ${shortType} WAITING`);
}
}
// Write latest planner response as snapshot (dedup)
if (lastPlannerText && lastPlannerText !== lastSnapshotText.get(activeSessionId)) {
lastSnapshotText.set(activeSessionId, lastPlannerText);
writeChatSnapshot(`🤖 **${activeSessionTitle}**\n\n${lastPlannerText}`);
console.log(`Gravity Bridge: [POLL#${pollCount}] 💬 snapshot ${lastPlannerText.length} chars`);
} }
} catch (e: any) { } catch (e: any) {
if (pollCount <= 5 || pollCount % 12 === 0) { if (pollCount <= 5 || pollCount % 20 === 0) {
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`); console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);
} }
} }
}, 5000); }, 3000);
} }
// ─── Response Watcher (Discord approval → Antigravity RPC) ─── // ─── Response Watcher (Discord approval → Antigravity RPC) ───