From 7dbf73aa89c7cc22f43a0a84aaef7825a00886a5 Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Thu, 16 Apr 2026 22:03:09 +0900 Subject: [PATCH] feat(bridge): v17 Always run auto-approve + Retry button relay (v0.5.53) #task-632 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Observer v17: ACTION_WORDS에 'Retry' 추가 — Agent terminated 대화상자의 Retry 버튼 감지/릴레이 - http-bridge: 'Always run' 버튼 자동승인 — response 파일 즉시 생성하여 observer가 바로 클릭 - bot.py: auto_approved 상태 처리 — Discord에 비대화형 '자동 승인' 알림 표시 - Observer matchedType에 'retry' step_type 매핑 --- bot.py | 19 +++++++++++++++- extension/package.json | 2 +- extension/src/http-bridge.ts | 38 ++++++++++++++++++++++++++++++++ extension/src/observer-script.ts | 10 ++++----- 4 files changed, 62 insertions(+), 7 deletions(-) diff --git a/bot.py b/bot.py index 2138722..7b42bbb 100644 --- a/bot.py +++ b/bot.py @@ -687,11 +687,28 @@ class GravityBot(commands.Bot): if request_id in self._sent_approval_ids: return - # Check auto_resolved status + # Check auto_resolved / auto_approved status status = data.get("status", "pending") if status in ("auto_resolved", "expired"): await self._handle_auto_resolved(request_id, status) return + if status == "auto_approved": + # Bridge-level auto-approve (e.g. "Always run") — show notification only + channel = await self._get_channel(project) + if channel: + cmd_text = data.get("command", "")[:200] + desc_text = data.get("description", "")[:300] + embed = discord.Embed( + title="🤖 자동 승인됨 (Always run)", + description=f"✅ **{cmd_text}**" + (f"\n```\n{desc_text}\n```" if desc_text and len(desc_text) > 3 else ""), + color=discord.Color.green(), + ) + embed.set_footer(text=f"auto-approve | {request_id[:12]}") + await channel.send(embed=embed) + self._cap_dict(self._sent_approval_ids) + self._sent_approval_ids[request_id] = True + logger.info(f"[HUB-PENDING] Auto-approved (Always run): {request_id[:12]} project={project}") + return instance_number = data.get("_instance_number", 0) pc_name = data.get("_pc_name", "") diff --git a/extension/package.json b/extension/package.json index adcb7e0..9ac22c2 100644 --- a/extension/package.json +++ b/extension/package.json @@ -2,7 +2,7 @@ "name": "gravity-bridge", "displayName": "Gravity Bridge", "description": "Discord-based unified approval system for Antigravity AI interactions.", - "version": "0.5.52", + "version": "0.5.53", "publisher": "variet", "engines": { "vscode": "^1.100.0" diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts index 0fd3a0f..beb3409 100644 --- a/extension/src/http-bridge.ts +++ b/extension/src/http-bridge.ts @@ -361,6 +361,44 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) { buttons: data.buttons, step_index: ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : undefined, }; + // v17: "Always run" auto-approve — click button immediately without Discord roundtrip + // rawCmd is the original button text before enrichment. "Always run" means the user + // already trusts this command pattern, so we auto-approve at the bridge level. + if (/^Always\s+run$/i.test(rawCmd)) { + ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run": enriched="${enrichedCmd.substring(0, 80)}"`); + // Write response file so observer's pollResponseGroup picks it up and clicks the button + const responseDir = path.join(ctx.bridgePath, 'response'); + if (!fs.existsSync(responseDir)) { + fs.mkdirSync(responseDir, { recursive: true }); + } + const respPayload = { + request_id: rid, + approved: true, + button_index: 0, // "Always run" is always the first button + step_type: data.step_type || 'command', + project_name: ctx.projectName, + }; + fs.writeFileSync( + path.join(responseDir, `${rid}.json`), + JSON.stringify(respPayload), + 'utf-8' + ); + // Notify Discord (non-interactive "자동 승인" embed) + if (ctx.wsBridge && ctx.wsBridge.isConnected()) { + ctx.wsBridge.sendPending({ + request_id: rid, + command: enrichedCmd || rawCmd, + description: enrichedDesc, + step_type: pending.step_type, + status: 'auto_approved', + buttons: pending.buttons, + project_name: ctx.projectName, + }); + } + res.writeHead(200, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ ok: true, request_id: rid, auto_approved: true })); + return; + } // File permission: inject multi-choice buttons const cmdLower = enrichedCmd.toLowerCase(); if (cmdLower.includes('allow') && !pending.buttons) { diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts index 86e560a..6b7d8b8 100644 --- a/extension/src/observer-script.ts +++ b/extension/src/observer-script.ts @@ -1,7 +1,7 @@ export function generateApprovalObserverScript(_port: number): string { return ` -// ── Gravity Bridge v16: AG Native Chat Relay + Style Strip ── -// v16: AG Native style/script strip + #conversation + .leading-relaxed.select-text chat body scanning +// ── Gravity Bridge v17: Always Run Auto-Approve + Retry Detection ── +// v17: "Always run" auto-approve at bridge level + Retry button relay to Discord (function(){ 'use strict'; var BASE='',_obs=false,_sent={},_ready=false; @@ -10,7 +10,7 @@ export function generateApprovalObserverScript(_port: number): string { var CLEANUP_MS=300000; function log(m){console.log('[GB Observer] '+m);} - log('v16 Script loaded — AG Native Chat Relay + Style Strip'); + log('v17 Script loaded — Always Run Auto-Approve + Retry Detection'); // DIAGNOSTIC BEACON: immediate POST to confirm script execution in renderer try { @@ -231,7 +231,7 @@ export function generateApprovalObserverScript(_port: number): string { return extractStepContext(b); } - var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', 'Always run']; + var ACTION_WORDS = ['Allow', 'Run', 'Approve', 'Accept', 'Always allow', 'Always run', 'Retry']; var REJECT_WORDS = ['Reject', 'Cancel', 'Deny', 'Stop', 'Decline', 'Dismiss']; function isActionBtn(txt) { @@ -713,7 +713,7 @@ export function generateApprovalObserverScript(_port: number): string { continue; } - var matchedType = txt.includes('Accept') ? 'diff_review' : (txt.includes('Run') || txt.includes('Allow') ? 'command' : 'permission'); + var matchedType = txt.includes('Accept') ? 'diff_review' : (txt === 'Retry' ? 'retry' : (txt.includes('Run') || txt.includes('Allow') ? 'command' : 'permission')); // v7: Use step-index for more unique group key var stepContainer = getStepContainer(b);