fix(bridge): v0.3.11 approval flow architecture fix — eliminate double-fire auto-approve, strip 30+ failed RPC strategies, add project_name DEDUP guard
- Remove Extension-side auto-approve (was double-firing with Bot auto-approve) - Strip failed strategies 0A-1 from tryApprovalStrategies (~150 lines) - Keep only Strategy 0-PROTO (proto RPC) + Strategy 2 (clickTrigger) - Add bot.py AUTO-RESOLVED logging for diagnostics - Update known-issues with 3 new entries - Clean deployment: v0.3.8→v0.3.10→v0.3.11
This commit is contained in:
@@ -2034,11 +2034,8 @@ function setupMonitor() {
|
||||
// Skip pending for workspace-less AG windows (project=default)
|
||||
if (projectName === 'default') {
|
||||
logToFile(`[STEP-PROBE] skip pending: projectName=default (no workspace)`);
|
||||
} else if (autoApproveEnabled) {
|
||||
// Auto-approve: skip Discord, approve directly
|
||||
logToFile(`[AUTO] auto-approving step=${actualIndex} cmd='${command.substring(0, 60)}'`);
|
||||
tryApprovalStrategies(true, activeSessionId, ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search', 'replace_file_content', 'write_to_file', 'multi_replace_file_content'].includes(toolName) ? 'file_permission' : toolName, actualIndex);
|
||||
} else {
|
||||
// Always write pending — Bot decides auto-approve (prevents double-fire)
|
||||
writePendingApproval({
|
||||
conversation_id: activeSessionId,
|
||||
command,
|
||||
@@ -2098,11 +2095,8 @@ function setupMonitor() {
|
||||
// Skip pending for workspace-less AG windows (project=default)
|
||||
if (projectName === 'default') {
|
||||
logToFile(`[STEP-PROBE] skip pending: projectName=default (no workspace)`);
|
||||
} else if (autoApproveEnabled) {
|
||||
// Auto-approve: skip Discord, approve directly
|
||||
logToFile(`[AUTO] auto-approving step=${si} cmd='${command.substring(0, 60)}'`);
|
||||
tryApprovalStrategies(true, activeSessionId, ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search', 'replace_file_content', 'write_to_file', 'multi_replace_file_content'].includes(toolName) ? 'file_permission' : toolName, si);
|
||||
} else {
|
||||
// Always write pending — Bot decides auto-approve (prevents double-fire)
|
||||
writePendingApproval({
|
||||
conversation_id: activeSessionId,
|
||||
command,
|
||||
@@ -2919,153 +2913,11 @@ async function tryApprovalStrategies(approved: boolean, sessionId: string, stepT
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 0A: executeCascadeAction
|
||||
// ══════════════════════════════════════════════════════════
|
||||
const cascadeActionVariants = [
|
||||
{ action: approved ? 'accept' : 'reject' },
|
||||
{ action: approved ? 'ACCEPT' : 'REJECT' },
|
||||
{ action: approved ? 'approve' : 'deny' },
|
||||
{ action: approved ? 'run' : 'reject' },
|
||||
{ cascadeId: sessionId, action: approved ? 'accept' : 'reject' },
|
||||
{ cascadeId: sessionId, type: approved ? 'accept' : 'reject' },
|
||||
approved ? 'accept' : 'reject', // plain string arg
|
||||
approved ? 1 : 0, // numeric arg
|
||||
];
|
||||
for (let i = 0; i < cascadeActionVariants.length; i++) {
|
||||
try {
|
||||
const arg = cascadeActionVariants[i];
|
||||
logToFile(`[APPROVAL-0A-${i}] executeCascadeAction(${JSON.stringify(arg)})`);
|
||||
const result = await vscode.commands.executeCommand('antigravity.executeCascadeAction', arg);
|
||||
logToFile(`[APPROVAL-0A-${i}] ✅ SUCCESS: ${JSON.stringify(result).substring(0, 200)}`);
|
||||
return `CMD-0A:executeCascadeAction(variant=${i})`;
|
||||
} catch (e: any) {
|
||||
logToFile(`[APPROVAL-0A-${i}] ❌ ${e.message.substring(0, 100)}`);
|
||||
}
|
||||
}
|
||||
// ── Strategies 0A-1 REMOVED (v0.3.11) — all confirmed failing, caused log spam + AG interference ──
|
||||
// Kept: Strategy 0-PROTO (above) for correct proto-based RPC
|
||||
// Kept: Strategy 2 (below) for renderer DOM click fallback
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 0B: Direct approval commands (brute-force try all 7 SDK commands)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
const commandsToTry = approved
|
||||
? [
|
||||
'antigravity.terminalCommand.run',
|
||||
'antigravity.terminalCommand.accept',
|
||||
'antigravity.agent.acceptAgentStep',
|
||||
'antigravity.command.accept',
|
||||
]
|
||||
: [
|
||||
'antigravity.terminalCommand.reject',
|
||||
'antigravity.agent.rejectAgentStep',
|
||||
'antigravity.command.reject',
|
||||
];
|
||||
for (const cmd of commandsToTry) {
|
||||
// Try with no args, then with sessionId
|
||||
for (const args of [[], [sessionId], [{ cascadeId: sessionId }]]) {
|
||||
try {
|
||||
logToFile(`[APPROVAL-0B] ${cmd}(${JSON.stringify(args)})`);
|
||||
const result = await vscode.commands.executeCommand(cmd, ...args);
|
||||
logToFile(`[APPROVAL-0B] ✅ SUCCESS: ${JSON.stringify(result).substring(0, 200)}`);
|
||||
return `CMD-0B:${cmd}`;
|
||||
} catch (e: any) {
|
||||
logToFile(`[APPROVAL-0B] ❌ ${e.message.substring(0, 80)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 0C: Electron webContents access (pierce webview isolation)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
try {
|
||||
logToFile(`[APPROVAL-0C] Attempting Electron webContents access...`);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const electron = require('electron');
|
||||
const remote = (electron as any).remote;
|
||||
if (remote) {
|
||||
logToFile(`[APPROVAL-0C] electron.remote available!`);
|
||||
const allWC = remote.webContents.getAllWebContents();
|
||||
logToFile(`[APPROVAL-0C] Found ${allWC.length} webContents`);
|
||||
for (const wc of allWC) {
|
||||
const wcUrl = wc.getURL() || '';
|
||||
logToFile(`[APPROVAL-0C] wc: ${wcUrl.substring(0, 100)}`);
|
||||
if (wcUrl.includes('vscode-webview://') || wcUrl.includes('webview')) {
|
||||
const clickScript = approved
|
||||
? `(function(){ var btns = document.querySelectorAll('button'); for(var b of btns){ if(/Run|Accept|Allow/i.test(b.textContent)){ b.click(); return 'clicked:'+b.textContent; } } return 'no-match'; })()`
|
||||
: `(function(){ var btns = document.querySelectorAll('button'); for(var b of btns){ if(/Reject|Deny|Cancel/i.test(b.textContent)){ b.click(); return 'clicked:'+b.textContent; } } return 'no-match'; })()`;
|
||||
const clickResult = await wc.executeJavaScript(clickScript);
|
||||
logToFile(`[APPROVAL-0C] executeJavaScript result: ${clickResult}`);
|
||||
if (clickResult && clickResult.startsWith('clicked:')) {
|
||||
return `ELECTRON-0C:webContents(${clickResult})`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logToFile(`[APPROVAL-0C] electron.remote NOT available`);
|
||||
// Try without remote (main process context)
|
||||
const wc = (electron as any).webContents;
|
||||
if (wc && typeof wc.getAllWebContents === 'function') {
|
||||
logToFile(`[APPROVAL-0C] Direct electron.webContents available!`);
|
||||
const allWC = wc.getAllWebContents();
|
||||
logToFile(`[APPROVAL-0C] Found ${allWC.length} webContents`);
|
||||
} else {
|
||||
logToFile(`[APPROVAL-0C] No webContents access from Extension Host`);
|
||||
}
|
||||
}
|
||||
} catch (e: any) {
|
||||
logToFile(`[APPROVAL-0C] ❌ ${e.message.substring(0, 150)}`);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 0D: sendChatActionMessage (may route to agent)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
const chatActionVariants = [
|
||||
{ action: approved ? 'accept' : 'reject', cascadeId: sessionId },
|
||||
{ message: approved ? 'accept' : 'reject', conversationId: sessionId },
|
||||
approved ? 'accept' : 'reject',
|
||||
];
|
||||
for (let i = 0; i < chatActionVariants.length; i++) {
|
||||
try {
|
||||
logToFile(`[APPROVAL-0D-${i}] sendChatActionMessage(${JSON.stringify(chatActionVariants[i])})`);
|
||||
const result = await vscode.commands.executeCommand('antigravity.sendChatActionMessage', chatActionVariants[i]);
|
||||
logToFile(`[APPROVAL-0D-${i}] ✅ SUCCESS: ${JSON.stringify(result).substring(0, 200)}`);
|
||||
return `CMD-0D:sendChatActionMessage(variant=${i})`;
|
||||
} catch (e: any) {
|
||||
logToFile(`[APPROVAL-0D-${i}] ❌ ${e.message.substring(0, 80)}`);
|
||||
}
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════════
|
||||
// STRATEGY 1: HandleCascadeUserInteraction RPC (expanded variants)
|
||||
// ══════════════════════════════════════════════════════════
|
||||
if (sdk) {
|
||||
const rpcVariants = [
|
||||
// Original variants
|
||||
{ cascadeId: sessionId, approved: approved },
|
||||
{ cascadeId: sessionId, stepAction: approved ? 'STEP_ACTION_ACCEPT' : 'STEP_ACTION_REJECT' },
|
||||
{ cascadeId: sessionId, userAction: approved ? 'USER_ACTION_APPROVED' : 'USER_ACTION_REJECTED' },
|
||||
// New variants — ConnectRPC protobuf field naming conventions
|
||||
{ cascade_id: sessionId, accepted: approved },
|
||||
{ cascadeId: sessionId, accepted: approved },
|
||||
{ cascade_id: sessionId, user_action: approved ? 1 : 2 }, // enum as int
|
||||
{ cascadeId: sessionId, interaction: approved ? 'ACCEPT' : 'REJECT' },
|
||||
{ cascadeId: sessionId, action: approved ? 'accept' : 'reject' },
|
||||
// With step index from last known waiting step
|
||||
{ cascadeId: sessionId, stepIndex: lastPendingStepIndex, accepted: approved },
|
||||
{ cascadeId: sessionId, stepIndex: lastPendingStepIndex, approved: approved },
|
||||
];
|
||||
for (let i = 0; i < rpcVariants.length; i++) {
|
||||
try {
|
||||
logToFile(`[APPROVAL-1-${i}] HandleCascadeUserInteraction(${JSON.stringify(rpcVariants[i]).substring(0, 120)})`);
|
||||
const rpcResult = await sdk.ls.rawRPC('HandleCascadeUserInteraction', rpcVariants[i]);
|
||||
logToFile(`[APPROVAL-1-${i}] ✅ SUCCESS: ${JSON.stringify(rpcResult).substring(0, 200)}`);
|
||||
return `RPC-1-${i}:HandleCascadeUserInteraction`;
|
||||
} catch (e: any) {
|
||||
logToFile(`[APPROVAL-1-${i}] ❌ ${e.message.substring(0, 80)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Strategy 2: Renderer DOM Click via HTTP Bridge (kept as fallback) ──
|
||||
// ── Strategy 2: Renderer DOM Click via HTTP Bridge (primary fallback) ──
|
||||
try {
|
||||
const triggerAction = approved ? 'approve' : 'reject';
|
||||
logToFile(`[APPROVAL-2] Setting clickTrigger=${triggerAction} for renderer DOM click`);
|
||||
@@ -3075,15 +2927,8 @@ async function tryApprovalStrategies(approved: boolean, sessionId: string, stepT
|
||||
logToFile(`[APPROVAL-2] ❌ FAIL: ${e.message}`);
|
||||
}
|
||||
|
||||
// ── Strategy 3: ResolveOutstandingSteps — DISABLED (too destructive!) ──
|
||||
// This was cancelling AG work when bot sent approved=false.
|
||||
// DO NOT enable without explicit user confirmation.
|
||||
if (!approved && sdk) {
|
||||
logToFile(`[APPROVAL-3] ResolveOutstandingSteps DISABLED — reject only logs, no cancel`);
|
||||
}
|
||||
|
||||
logToFile(`[APPROVAL] ⚠️ ALL strategies attempted — check logs for results`);
|
||||
return `ALL_ATTEMPTED:${action}`;
|
||||
logToFile(`[APPROVAL] strategies complete — check logs for results`);
|
||||
return `STRATEGIES_DONE:${action}`;
|
||||
}
|
||||
|
||||
// ─── Activation ───
|
||||
|
||||
Reference in New Issue
Block a user