fix(bridge): move Always run auto-approve BEFORE filter chain — no more silent drops (v0.5.60) #task-634
This commit is contained in:
4
extension/package-lock.json
generated
4
extension/package-lock.json
generated
@@ -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"
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user