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:
@@ -145,3 +145,16 @@ setInterval (5초마다):
|
||||
```
|
||||
|
||||
이 방식은 `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를 직접 비교.
|
||||
|
||||
|
||||
@@ -439,135 +439,145 @@ function setupMonitor() {
|
||||
sdk.monitor.start(3000, 2000);
|
||||
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// PRIMARY RELAY: Direct step polling via rawRPC (getDiagnostics is stale!)
|
||||
// getDiagnostics.lastStepIndex does NOT update in real-time.
|
||||
// Instead, we poll GetCascadeTrajectorySteps directly for the active session.
|
||||
// PRIMARY RELAY: GetAllCascadeTrajectories (THE CORRECT API!)
|
||||
//
|
||||
// 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 activeSessionId = '';
|
||||
let activeSessionTitle = '';
|
||||
let polledStepCount = 0;
|
||||
let lastKnownStepCount = 0;
|
||||
let lastNotifyStepIndex = -1;
|
||||
let lastTaskStepIndex = -1;
|
||||
setInterval(async () => {
|
||||
pollCount++;
|
||||
try {
|
||||
// Phase 1: Discover active session (first time or periodically)
|
||||
if (!activeSessionId || pollCount % 12 === 0) {
|
||||
try {
|
||||
const raw = await vscode.commands.executeCommand('antigravity.getDiagnostics');
|
||||
if (raw && typeof raw === 'string') {
|
||||
const diag = JSON.parse(raw);
|
||||
if (Array.isArray(diag.recentTrajectories) && diag.recentTrajectories.length > 0) {
|
||||
const first = diag.recentTrajectories[0];
|
||||
if (first.googleAgentId && first.googleAgentId !== activeSessionId) {
|
||||
activeSessionId = first.googleAgentId;
|
||||
activeSessionTitle = first.summary || 'Untitled';
|
||||
polledStepCount = first.lastStepIndex || 0;
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] 🎯 active session: ${activeSessionId.substring(0, 8)} "${activeSessionTitle}" steps=${polledStepCount}`);
|
||||
writeRegistration(activeSessionId);
|
||||
// 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 = '';
|
||||
for (const [sid, data] of Object.entries(allTraj.trajectorySummaries)) {
|
||||
const modTime = data.lastModifiedTime || '';
|
||||
if (!bestSession || modTime > bestModTime) {
|
||||
bestSession = data;
|
||||
bestSessionId = sid;
|
||||
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 (e) {
|
||||
if (pollCount <= 3) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] getDiag error: ${e.message}`);
|
||||
catch (rpcErr) {
|
||||
// GetCascadeTrajectorySteps failed — not critical
|
||||
}
|
||||
}
|
||||
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) {
|
||||
if (pollCount <= 5 || pollCount % 12 === 0) {
|
||||
if (pollCount <= 5 || pollCount % 20 === 0) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}, 3000);
|
||||
}
|
||||
// ─── Response Watcher (Discord approval → Antigravity RPC) ───
|
||||
let responseWatcher = null;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -422,141 +422,149 @@ function setupMonitor() {
|
||||
console.log('Gravity Bridge: [SDK] monitor started (USS 3s, trajectory 2s)');
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════
|
||||
// PRIMARY RELAY: Direct step polling via rawRPC (getDiagnostics is stale!)
|
||||
// getDiagnostics.lastStepIndex does NOT update in real-time.
|
||||
// Instead, we poll GetCascadeTrajectorySteps directly for the active session.
|
||||
// PRIMARY RELAY: GetAllCascadeTrajectories (THE CORRECT API!)
|
||||
//
|
||||
// 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 activeSessionId = '';
|
||||
let activeSessionTitle = '';
|
||||
let polledStepCount = 0;
|
||||
let lastKnownStepCount = 0;
|
||||
let lastNotifyStepIndex = -1;
|
||||
let lastTaskStepIndex = -1;
|
||||
|
||||
setInterval(async () => {
|
||||
pollCount++;
|
||||
try {
|
||||
// Phase 1: Discover active session (first time or periodically)
|
||||
if (!activeSessionId || pollCount % 12 === 0) {
|
||||
try {
|
||||
const raw = await vscode.commands.executeCommand<string>('antigravity.getDiagnostics');
|
||||
if (raw && typeof raw === 'string') {
|
||||
const diag = JSON.parse(raw);
|
||||
if (Array.isArray(diag.recentTrajectories) && diag.recentTrajectories.length > 0) {
|
||||
const first = diag.recentTrajectories[0];
|
||||
if (first.googleAgentId && first.googleAgentId !== activeSessionId) {
|
||||
activeSessionId = first.googleAgentId;
|
||||
activeSessionTitle = first.summary || 'Untitled';
|
||||
polledStepCount = first.lastStepIndex || 0;
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] 🎯 active session: ${activeSessionId.substring(0, 8)} "${activeSessionTitle}" steps=${polledStepCount}`);
|
||||
writeRegistration(activeSessionId);
|
||||
// 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 = '';
|
||||
for (const [sid, data] of Object.entries(allTraj.trajectorySummaries) as [string, any][]) {
|
||||
const modTime = data.lastModifiedTime || '';
|
||||
if (!bestSession || modTime > bestModTime) {
|
||||
bestSession = data;
|
||||
bestSessionId = sid;
|
||||
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 (e: any) {
|
||||
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`);
|
||||
} catch (rpcErr: any) {
|
||||
// GetCascadeTrajectorySteps failed — not critical
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
if (pollCount <= 5 || pollCount % 12 === 0) {
|
||||
if (pollCount <= 5 || pollCount % 20 === 0) {
|
||||
console.log(`Gravity Bridge: [POLL#${pollCount}] error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
}, 5000);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// ─── Response Watcher (Discord approval → Antigravity RPC) ───
|
||||
|
||||
Reference in New Issue
Block a user