fix(bridge): move Always run auto-approve BEFORE filter chain — no more silent drops (v0.5.60) #task-634

This commit is contained in:
Variet Worker
2026-04-18 06:54:15 +09:00
parent 98b7585e3c
commit 326454be40
3 changed files with 63 additions and 52 deletions

View File

@@ -1,12 +1,12 @@
{ {
"name": "gravity-bridge", "name": "gravity-bridge",
"version": "0.5.59", "version": "0.5.60",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "gravity-bridge", "name": "gravity-bridge",
"version": "0.5.59", "version": "0.5.60",
"dependencies": { "dependencies": {
"cheerio": "^1.2.0", "cheerio": "^1.2.0",
"ws": "^8.19.0" "ws": "^8.19.0"

View File

@@ -2,7 +2,7 @@
"name": "gravity-bridge", "name": "gravity-bridge",
"displayName": "Gravity Bridge", "displayName": "Gravity Bridge",
"description": "Discord-based unified approval system for Antigravity AI interactions.", "description": "Discord-based unified approval system for Antigravity AI interactions.",
"version": "0.5.59", "version": "0.5.60",
"publisher": "variet", "publisher": "variet",
"engines": { "engines": {
"vscode": "^1.100.0" "vscode": "^1.100.0"

View File

@@ -266,6 +266,64 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
const GENERIC_BTN_RE = /^(?:Always\s*)?(?:Run|Allow|Accept|Approve)$/i; const GENERIC_BTN_RE = /^(?:Always\s*)?(?:Run|Allow|Accept|Approve)$/i;
let enrichedCmd = rawCmd; let enrichedCmd = rawCmd;
let enrichedDesc = rawDesc; let enrichedDesc = rawDesc;
// v19: "Always run" auto-approve — MUST run BEFORE any filter can reject it
// Detects from rawCmd OR from buttons array (Observer may detect sibling first)
let alwaysRunDetected = /^Always\s+run$/i.test(rawCmd);
let alwaysRunBtnIndex = alwaysRunDetected ? 0 : -1;
if (!alwaysRunDetected && Array.isArray(data.buttons)) {
for (let bi = 0; bi < data.buttons.length; bi++) {
if (/^Always\s+run$/i.test((data.buttons[bi].text || '').trim())) {
alwaysRunDetected = true;
alwaysRunBtnIndex = bi;
break;
}
}
}
if (alwaysRunDetected) {
// Try enrichment for better Discord display text
let displayCmd = rawCmd;
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {
const promptMatch = rawDesc.match(/[>»]\s*(.+)/);
if (promptMatch && promptMatch[1].trim().length > 3) {
displayCmd = promptMatch[1].trim().substring(0, 200);
}
}
const rid = data.request_id || Date.now().toString();
ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run" (btnIdx=${alwaysRunBtnIndex}): cmd="${displayCmd.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: alwaysRunBtnIndex >= 0 ? alwaysRunBtnIndex : 0,
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: displayCmd,
description: rawDesc ? `[${rawCmd}] ${rawDesc}` : rawCmd,
step_type: data.step_type || 'command',
status: 'auto_approved',
buttons: data.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;
}
if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) { if (GENERIC_BTN_RE.test(rawCmd) && rawDesc.length > 10 && rawDesc !== rawCmd) {
// Extract the actual command from description (often includes terminal prompt) // Extract the actual command from description (often includes terminal prompt)
// Pattern: "…\project_name > actual_command" // Pattern: "…\project_name > actual_command"
@@ -361,55 +419,8 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
buttons: data.buttons, buttons: data.buttons,
step_index: ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : undefined, step_index: ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : undefined,
}; };
// v17+: "Always run" auto-approve — click button immediately without Discord roundtrip // v19: "Always run" auto-approve was already handled above (before filter chain)
// Check rawCmd first, then fall back to scanning the buttons array // No need for duplicate check here.
// (Observer may detect "Run" first while "Always run" is a sibling button)
let alwaysRunIndex = -1;
if (/^Always\s+run$/i.test(rawCmd)) {
alwaysRunIndex = 0;
} else if (Array.isArray(data.buttons)) {
for (let bi = 0; bi < data.buttons.length; bi++) {
if (/^Always\s+run$/i.test((data.buttons[bi].text || '').trim())) {
alwaysRunIndex = bi;
break;
}
}
}
if (alwaysRunIndex >= 0) {
ctx.logToFile(`[HTTP] AUTO-APPROVE "Always run" (btnIdx=${alwaysRunIndex}): 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: alwaysRunIndex,
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 // File permission: inject multi-choice buttons
const cmdLower = enrichedCmd.toLowerCase(); const cmdLower = enrichedCmd.toLowerCase();
if (cmdLower.includes('allow') && !pending.buttons) { if (cmdLower.includes('allow') && !pending.buttons) {