feat(action): direct accept/reject buttons — no mirror tab needed, Extension /api/action endpoint
This commit is contained in:
Binary file not shown.
@@ -236,6 +236,35 @@ export async function activate(context: vscode.ExtensionContext) {
|
||||
}
|
||||
});
|
||||
|
||||
// --- 승인/거절 액션 (Gravity Web에서 직접 조작) ---
|
||||
|
||||
app.post('/api/action', async (req: any, res: any) => {
|
||||
try {
|
||||
const { action } = req.body;
|
||||
const commandMap: Record<string, string> = {
|
||||
acceptStep: 'antigravity.agent.acceptAgentStep',
|
||||
rejectStep: 'antigravity.agent.rejectAgentStep',
|
||||
acceptCommand: 'antigravity.command.accept',
|
||||
rejectCommand: 'antigravity.command.reject',
|
||||
acceptTerminal: 'antigravity.terminalCommand.accept',
|
||||
rejectTerminal: 'antigravity.terminalCommand.reject',
|
||||
};
|
||||
|
||||
const cmd = commandMap[action];
|
||||
if (!cmd) {
|
||||
res.status(400).json({ error: `Unknown action: ${action}` });
|
||||
return;
|
||||
}
|
||||
|
||||
await vscode.commands.executeCommand(cmd);
|
||||
output.appendLine(`[Action] ${action} → ${cmd} 실행`);
|
||||
res.json({ success: true, action });
|
||||
} catch (err: any) {
|
||||
output.appendLine(`[Action] 실패: ${err.message}`);
|
||||
res.status(500).json({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
// --- 서버 시작 ---
|
||||
|
||||
server.listen(BRIDGE_PORT, () => {
|
||||
|
||||
@@ -526,6 +526,19 @@ body {
|
||||
background: var(--accent-primary);
|
||||
}
|
||||
|
||||
.msg-action-danger {
|
||||
background: #ef4444;
|
||||
}
|
||||
|
||||
.msg-action-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
.msg-action-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* --- 상태 메시지 --- */
|
||||
.msg-status {
|
||||
text-align: center;
|
||||
|
||||
@@ -230,9 +230,10 @@
|
||||
if (nu.isBlocking) {
|
||||
messages.push({
|
||||
type: 'actions',
|
||||
label: '⚠️ Antigravity에서 리뷰가 필요합니다',
|
||||
label: '⚠️ 사용자 승인 대기 중',
|
||||
buttons: [
|
||||
{ label: '미러 탭에서 확인', action: 'switch_mirror' },
|
||||
{ label: '✅ 진행', action: 'api_call', endpoint: '/api/bridge/action', body: { action: 'acceptStep' } },
|
||||
{ label: '❌ 거절', action: 'api_call', endpoint: '/api/bridge/action', body: { action: 'rejectStep' }, variant: 'danger' },
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@@ -314,12 +314,34 @@ class ChatPanel {
|
||||
|
||||
for (const btn of (msg.buttons || [])) {
|
||||
const el = document.createElement('button');
|
||||
el.className = 'msg-action-btn msg-action-primary';
|
||||
el.className = btn.variant === 'danger' ? 'msg-action-btn msg-action-danger' : 'msg-action-btn msg-action-primary';
|
||||
el.textContent = btn.label || btn;
|
||||
el.style.cursor = 'pointer';
|
||||
|
||||
// action 기반 처리
|
||||
if (btn.action === 'switch_mirror') {
|
||||
// api_call: 직접 API 호출 (승인/거절 등)
|
||||
if (btn.action === 'api_call' && btn.endpoint) {
|
||||
el.addEventListener('click', async () => {
|
||||
el.disabled = true;
|
||||
el.textContent = '처리 중...';
|
||||
try {
|
||||
const res = await fetch(btn.endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(btn.body || {}),
|
||||
});
|
||||
if (res.ok) {
|
||||
el.textContent = '✓ 완료';
|
||||
// 모든 형제 버튼 비활성화
|
||||
div.querySelectorAll('button').forEach(b => { b.disabled = true; });
|
||||
} else {
|
||||
const err = await res.json().catch(() => ({}));
|
||||
el.textContent = `실패: ${err.error || res.status}`;
|
||||
}
|
||||
} catch (e) {
|
||||
el.textContent = `오류: ${e.message}`;
|
||||
}
|
||||
});
|
||||
} else if (btn.action === 'switch_mirror') {
|
||||
el.addEventListener('click', () => {
|
||||
document.getElementById('tabMirror')?.click();
|
||||
});
|
||||
|
||||
@@ -64,24 +64,11 @@ class BridgeClient {
|
||||
}
|
||||
|
||||
/**
|
||||
* 코드 편집 승인
|
||||
* 승인/거절 액션 (통합)
|
||||
* action: 'acceptStep' | 'rejectStep' | 'acceptCommand' | 'rejectCommand' | 'acceptTerminal' | 'rejectTerminal'
|
||||
*/
|
||||
async acceptStep() {
|
||||
return this._post('/api/accept', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 코드 편집 거절
|
||||
*/
|
||||
async rejectStep() {
|
||||
return this._post('/api/reject', {});
|
||||
}
|
||||
|
||||
/**
|
||||
* 터미널 명령 승인
|
||||
*/
|
||||
async acceptTerminal() {
|
||||
return this._post('/api/accept-terminal', {});
|
||||
async sendAction(action) {
|
||||
return this._post('/api/action', { action });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -330,7 +330,15 @@ app.get('/api/bridge/trajectory/:id', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// Bridge WS 이벤트 → 프론트엔드 포워딩
|
||||
app.post('/api/bridge/action', async (req, res) => {
|
||||
try {
|
||||
const { action } = req.body;
|
||||
const result = await bridge.sendAction(action);
|
||||
res.json(result);
|
||||
} catch (e) {
|
||||
res.status(502).json({ error: e.message });
|
||||
}
|
||||
});
|
||||
bridge.connectWs((msg) => {
|
||||
broadcastToAll({ type: 'bridge_event', ...msg });
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user