fix(bridge): stall-based approval detection + known issues from deep debugging
- IDLE→stall detection: RUNNING+delta=0 for 6 polls (30s) - lastModifiedTime-based thinking filter (partial) - ResolveOutstandingSteps confirmed CANCELS steps (removed) - HandleCascadeUserInteraction always socket hang up (removed) - VS Code accept commands: silent success, no effect - Hybrid approval: focus+all commands sequential, no break - logToFile: console.log backup added - Known issues: 4 critical findings documented - better-antigravity reference added for future research
This commit is contained in:
400
better-antigravity-main/fixes/auto-run-fix/patch.js
Normal file
400
better-antigravity-main/fixes/auto-run-fix/patch.js
Normal file
@@ -0,0 +1,400 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Antigravity "Always Proceed" Auto-Run Fix
|
||||
* ==========================================
|
||||
*
|
||||
* Fixes a bug where the "Always Proceed" terminal execution policy doesn't
|
||||
* actually auto-execute commands. Uses regex patterns to find code structures
|
||||
* regardless of minified variable names — works across versions.
|
||||
*
|
||||
* Usage:
|
||||
* node patch.js - Apply patch
|
||||
* node patch.js --revert - Restore original files
|
||||
* node patch.js --check - Check patch status
|
||||
*
|
||||
* License: MIT
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
// ─── Installation Detection ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Validates that a candidate directory is a real Antigravity installation
|
||||
* by checking for the workbench main JS file.
|
||||
*/
|
||||
function isAntigravityDir(dir) {
|
||||
if (!dir) return false;
|
||||
try {
|
||||
const workbench = path.join(dir, 'resources', 'app', 'out', 'vs', 'workbench', 'workbench.desktop.main.js');
|
||||
return fs.existsSync(workbench);
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a directory looks like the Antigravity installation root
|
||||
* (contains Antigravity.exe or antigravity binary).
|
||||
*/
|
||||
function looksLikeAntigravityRoot(dir) {
|
||||
if (!dir) return false;
|
||||
try {
|
||||
const exe = process.platform === 'win32' ? 'Antigravity.exe' : 'antigravity';
|
||||
return fs.existsSync(path.join(dir, exe));
|
||||
} catch { return false; }
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find Antigravity installation path from Windows Registry.
|
||||
* InnoSetup writes uninstall info to HKCU or HKLM.
|
||||
*/
|
||||
function findFromRegistry() {
|
||||
if (process.platform !== 'win32') return null;
|
||||
try {
|
||||
const { execSync } = require('child_process');
|
||||
// InnoSetup typically writes to this key; try HKCU first, then HKLM
|
||||
const regPaths = [
|
||||
'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Antigravity_is1',
|
||||
'HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Antigravity_is1',
|
||||
'HKLM\\Software\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\Antigravity_is1',
|
||||
];
|
||||
for (const regPath of regPaths) {
|
||||
try {
|
||||
const output = execSync(
|
||||
`reg query "${regPath}" /v InstallLocation`,
|
||||
{ encoding: 'utf8', timeout: 3000, stdio: ['pipe', 'pipe', 'pipe'] }
|
||||
);
|
||||
const match = output.match(/InstallLocation\s+REG_SZ\s+(.+)/i);
|
||||
if (match) {
|
||||
const dir = match[1].trim().replace(/\\$/, '');
|
||||
if (isAntigravityDir(dir)) return dir;
|
||||
}
|
||||
} catch { /* key not found, try next */ }
|
||||
}
|
||||
} catch { /* child_process failed */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find Antigravity by looking at PATH entries for the executable.
|
||||
*/
|
||||
function findFromPath() {
|
||||
try {
|
||||
const pathDirs = (process.env.PATH || '').split(path.delimiter);
|
||||
const exe = process.platform === 'win32' ? 'Antigravity.exe' : 'antigravity';
|
||||
for (const dir of pathDirs) {
|
||||
if (!dir) continue;
|
||||
if (fs.existsSync(path.join(dir, exe))) {
|
||||
// The exe could be in the root or in a bin/ subdirectory
|
||||
if (isAntigravityDir(dir)) return dir;
|
||||
const parent = path.dirname(dir);
|
||||
if (isAntigravityDir(parent)) return parent;
|
||||
}
|
||||
}
|
||||
} catch { /* PATH parsing failed */ }
|
||||
return null;
|
||||
}
|
||||
|
||||
function findAntigravityPath() {
|
||||
// 1. Check CWD and its ancestors (user may run from install dir or a subdir)
|
||||
let dir = process.cwd();
|
||||
const root = path.parse(dir).root;
|
||||
while (dir && dir !== root) {
|
||||
if (looksLikeAntigravityRoot(dir) && isAntigravityDir(dir)) return dir;
|
||||
dir = path.dirname(dir);
|
||||
}
|
||||
|
||||
// 2. Check PATH
|
||||
const fromPath = findFromPath();
|
||||
if (fromPath) return fromPath;
|
||||
|
||||
// 3. Check Windows Registry (InnoSetup uninstall keys)
|
||||
const fromReg = findFromRegistry();
|
||||
if (fromReg) return fromReg;
|
||||
|
||||
// 4. Hardcoded well-known locations
|
||||
const candidates = [];
|
||||
if (process.platform === 'win32') {
|
||||
candidates.push(
|
||||
path.join(process.env.LOCALAPPDATA || '', 'Programs', 'Antigravity'),
|
||||
path.join(process.env.PROGRAMFILES || '', 'Antigravity'),
|
||||
);
|
||||
} else if (process.platform === 'darwin') {
|
||||
candidates.push(
|
||||
'/Applications/Antigravity.app/Contents/Resources',
|
||||
path.join(os.homedir(), 'Applications', 'Antigravity.app', 'Contents', 'Resources')
|
||||
);
|
||||
} else {
|
||||
candidates.push('/usr/share/antigravity', '/opt/antigravity',
|
||||
path.join(os.homedir(), '.local', 'share', 'antigravity'));
|
||||
}
|
||||
for (const c of candidates) {
|
||||
if (isAntigravityDir(c)) return c;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Smart Pattern Matching ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Finds the onChange handler for terminalAutoExecutionPolicy and extracts
|
||||
* variable names from context, regardless of minification.
|
||||
*
|
||||
* Pattern we're looking for (structure, not exact names):
|
||||
* <VAR_CONFIRM>=<useCallback>((<ARG>)=>{
|
||||
* <stepHandler>?.setTerminalAutoExecutionPolicy?.(<ARG>),
|
||||
* <ARG>===<ENUM>.EAGER&&<CONFIRM_FN>(!0)
|
||||
* },[...])
|
||||
*
|
||||
* From the surrounding context we also extract:
|
||||
* <POLICY_VAR> = <stepHandler>?.terminalAutoExecutionPolicy ?? <ENUM>.OFF
|
||||
* <SECURE_VAR> = <stepHandler>?.secureModeEnabled ?? !1
|
||||
*/
|
||||
function analyzeFile(content, label) {
|
||||
// 1. Find the onChange handler: contains setTerminalAutoExecutionPolicy AND .EAGER
|
||||
// Pattern: VARNAME=CALLBACK(ARG=>{...setTerminalAutoExecutionPolicy...,ARG===ENUM.EAGER&&CONFIRM(!0)},[...])
|
||||
const onChangeRe = /(\w+)=(\w+)\((\w+)=>\{\w+\?\.setTerminalAutoExecutionPolicy\?\.\(\3\),\3===(\w+)\.EAGER&&(\w+)\(!0\)\},\[[\w,]*\]\)/;
|
||||
const onChangeMatch = content.match(onChangeRe);
|
||||
|
||||
if (!onChangeMatch) {
|
||||
console.log(` ❌ [${label}] Could not find onChange handler pattern`);
|
||||
return null;
|
||||
}
|
||||
|
||||
const [fullMatch, assignVar, callbackAlias, argName, enumAlias, confirmFn] = onChangeMatch;
|
||||
const matchIndex = content.indexOf(fullMatch);
|
||||
|
||||
console.log(` 📋 [${label}] Found onChange at offset ${matchIndex}`);
|
||||
console.log(` callback=${callbackAlias}, enum=${enumAlias}, confirm=${confirmFn}`);
|
||||
|
||||
// 2. Find policy variable: VARNAME=HANDLER?.terminalAutoExecutionPolicy??ENUM.OFF
|
||||
const policyRe = new RegExp(`(\\w+)=\\w+\\?\\.terminalAutoExecutionPolicy\\?\\?${enumAlias}\\.OFF`);
|
||||
const policyMatch = content.substring(Math.max(0, matchIndex - 2000), matchIndex).match(policyRe);
|
||||
|
||||
if (!policyMatch) {
|
||||
console.log(` ❌ [${label}] Could not find policy variable`);
|
||||
return null;
|
||||
}
|
||||
const policyVar = policyMatch[1];
|
||||
console.log(` policyVar=${policyVar}`);
|
||||
|
||||
// 3. Find secureMode variable: VARNAME=HANDLER?.secureModeEnabled??!1
|
||||
const secureRe = /(\w+)=\w+\?\.secureModeEnabled\?\?!1/;
|
||||
const secureMatch = content.substring(Math.max(0, matchIndex - 2000), matchIndex).match(secureRe);
|
||||
|
||||
if (!secureMatch) {
|
||||
console.log(` ❌ [${label}] Could not find secureMode variable`);
|
||||
return null;
|
||||
}
|
||||
const secureVar = secureMatch[1];
|
||||
console.log(` secureVar=${secureVar}`);
|
||||
|
||||
// 4. Find useEffect alias: look for ALIAS(()=>{...},[...]) calls nearby (not useCallback/useMemo)
|
||||
const nearbyCode = content.substring(Math.max(0, matchIndex - 5000), matchIndex + 5000);
|
||||
const effectCandidates = {};
|
||||
const effectRe = /\b(\w{2,3})\(\(\)=>\{[^}]{3,80}\},\[/g;
|
||||
let m;
|
||||
while ((m = effectRe.exec(nearbyCode)) !== null) {
|
||||
const alias = m[1];
|
||||
if (alias !== callbackAlias && alias !== 'var' && alias !== 'new') {
|
||||
effectCandidates[alias] = (effectCandidates[alias] || 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Also check broader file for common useEffect patterns (with cleanup return)
|
||||
const cleanupRe = /\b(\w{2,3})\(\(\)=>\{[^}]*return\s*\(\)=>/g;
|
||||
while ((m = cleanupRe.exec(content)) !== null) {
|
||||
const alias = m[1];
|
||||
if (alias !== callbackAlias) {
|
||||
effectCandidates[alias] = (effectCandidates[alias] || 0) + 5; // higher weight
|
||||
}
|
||||
}
|
||||
|
||||
// Remove known non-useEffect aliases (useMemo patterns)
|
||||
// useMemo: alias(()=>EXPRESSION,[deps]) — returns a value, often assigned
|
||||
// useEffect: alias(()=>{STATEMENTS},[deps]) — no return value
|
||||
|
||||
// Pick the most common candidate
|
||||
let useEffectAlias = null;
|
||||
let maxCount = 0;
|
||||
for (const [alias, count] of Object.entries(effectCandidates)) {
|
||||
if (count > maxCount) {
|
||||
maxCount = count;
|
||||
useEffectAlias = alias;
|
||||
}
|
||||
}
|
||||
|
||||
if (!useEffectAlias) {
|
||||
console.log(` ❌ [${label}] Could not determine useEffect alias`);
|
||||
return null;
|
||||
}
|
||||
console.log(` useEffect=${useEffectAlias} (confidence: ${maxCount} hits)`);
|
||||
|
||||
// 5. Build patch
|
||||
const patchCode = `_aep=${useEffectAlias}(()=>{${policyVar}===${enumAlias}.EAGER&&!${secureVar}&&${confirmFn}(!0)},[]),`;
|
||||
|
||||
return {
|
||||
target: fullMatch,
|
||||
replacement: patchCode + fullMatch,
|
||||
patchMarker: `_aep=${useEffectAlias}(()=>{${policyVar}===${enumAlias}.EAGER`,
|
||||
label
|
||||
};
|
||||
}
|
||||
|
||||
// ─── File Operations ────────────────────────────────────────────────────────
|
||||
|
||||
function patchFile(filePath, label) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log(` ❌ [${label}] File not found: ${filePath}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Check if already patched
|
||||
if (content.includes('_aep=')) {
|
||||
const existingPatch = content.match(/_aep=\w+\(\(\)=>\{[^}]+EAGER[^}]+\},\[\]\)/);
|
||||
if (existingPatch) {
|
||||
console.log(` ⏭️ [${label}] Already patched`);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const analysis = analyzeFile(content, label);
|
||||
if (!analysis) return false;
|
||||
|
||||
// Verify target is unique
|
||||
const count = content.split(analysis.target).length - 1;
|
||||
if (count !== 1) {
|
||||
console.log(` ❌ [${label}] Target found ${count} times (expected 1)`);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Backup
|
||||
if (!fs.existsSync(filePath + '.bak')) {
|
||||
fs.copyFileSync(filePath, filePath + '.bak');
|
||||
console.log(` 📦 [${label}] Backup created`);
|
||||
}
|
||||
|
||||
// Apply
|
||||
const patched = content.replace(analysis.target, analysis.replacement);
|
||||
fs.writeFileSync(filePath, patched, 'utf8');
|
||||
|
||||
const diff = fs.statSync(filePath).size - fs.statSync(filePath + '.bak').size;
|
||||
console.log(` ✅ [${label}] Patched (+${diff} bytes)`);
|
||||
return true;
|
||||
}
|
||||
|
||||
function revertFile(filePath, label) {
|
||||
const bak = filePath + '.bak';
|
||||
if (!fs.existsSync(bak)) {
|
||||
console.log(` ⏭️ [${label}] No backup, skipping`);
|
||||
return;
|
||||
}
|
||||
fs.copyFileSync(bak, filePath);
|
||||
console.log(` ✅ [${label}] Restored`);
|
||||
}
|
||||
|
||||
function checkFile(filePath, label) {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
console.log(` ❌ [${label}] Not found`);
|
||||
return false;
|
||||
}
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
const patched = content.includes('_aep=') && /_aep=\w+\(\(\)=>\{[^}]+EAGER/.test(content);
|
||||
const hasBak = fs.existsSync(filePath + '.bak');
|
||||
|
||||
if (patched) {
|
||||
console.log(` ✅ [${label}] PATCHED` + (hasBak ? ' (backup exists)' : ''));
|
||||
} else {
|
||||
const analysis = analyzeFile(content, label);
|
||||
if (analysis) {
|
||||
console.log(` ⬜ [${label}] NOT PATCHED (patchable)`);
|
||||
} else {
|
||||
console.log(` ⚠️ [${label}] NOT PATCHED (may be incompatible)`);
|
||||
}
|
||||
}
|
||||
return patched;
|
||||
}
|
||||
|
||||
// ─── Version Info ───────────────────────────────────────────────────────────
|
||||
|
||||
function getVersion(basePath) {
|
||||
try {
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(basePath, 'resources', 'app', 'package.json'), 'utf8'));
|
||||
const product = JSON.parse(fs.readFileSync(path.join(basePath, 'resources', 'app', 'product.json'), 'utf8'));
|
||||
return `${pkg.version} (IDE ${product.ideVersion})`;
|
||||
} catch { return 'unknown'; }
|
||||
}
|
||||
|
||||
// ─── Main ───────────────────────────────────────────────────────────────────
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const action = args.includes('--revert') ? 'revert' : args.includes('--check') ? 'check' : 'apply';
|
||||
|
||||
// Parse --path flag
|
||||
let explicitPath = null;
|
||||
const pathIdx = args.indexOf('--path');
|
||||
if (pathIdx !== -1 && args[pathIdx + 1]) {
|
||||
explicitPath = path.resolve(args[pathIdx + 1]);
|
||||
}
|
||||
|
||||
console.log('');
|
||||
console.log('╔══════════════════════════════════════════════════╗');
|
||||
console.log('║ Antigravity "Always Proceed" Auto-Run Fix ║');
|
||||
console.log('╚══════════════════════════════════════════════════╝');
|
||||
|
||||
let basePath;
|
||||
if (explicitPath) {
|
||||
if (!isAntigravityDir(explicitPath)) {
|
||||
console.log(`\n\u274C --path "${explicitPath}" does not look like an Antigravity installation.`);
|
||||
console.log(' Expected to find: resources/app/out/vs/workbench/workbench.desktop.main.js');
|
||||
process.exit(1);
|
||||
}
|
||||
basePath = explicitPath;
|
||||
} else {
|
||||
basePath = findAntigravityPath();
|
||||
}
|
||||
|
||||
if (!basePath) {
|
||||
console.log('\n\u274C Antigravity installation not found!');
|
||||
console.log('');
|
||||
console.log(' Try one of:');
|
||||
console.log(' 1. Run from the Antigravity install directory:');
|
||||
console.log(' cd "C:\\Path\\To\\Antigravity" && npx better-antigravity auto-run');
|
||||
console.log(' 2. Specify the path explicitly:');
|
||||
console.log(' npx better-antigravity auto-run --path "D:\\Antigravity"');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`\n📍 ${basePath}`);
|
||||
console.log(`📦 Version: ${getVersion(basePath)}`);
|
||||
console.log('');
|
||||
|
||||
const files = [
|
||||
{ path: path.join(basePath, 'resources', 'app', 'out', 'vs', 'workbench', 'workbench.desktop.main.js'), label: 'workbench' },
|
||||
{ path: path.join(basePath, 'resources', 'app', 'out', 'jetskiAgent', 'main.js'), label: 'jetskiAgent' },
|
||||
];
|
||||
|
||||
switch (action) {
|
||||
case 'check':
|
||||
files.forEach(f => checkFile(f.path, f.label));
|
||||
break;
|
||||
case 'revert':
|
||||
files.forEach(f => revertFile(f.path, f.label));
|
||||
console.log('\n✨ Restored! Restart Antigravity.');
|
||||
break;
|
||||
case 'apply':
|
||||
const ok = files.every(f => patchFile(f.path, f.label));
|
||||
console.log(ok
|
||||
? '\n✨ Done! Restart Antigravity.\n💡 Run with --revert to undo.\n⚠️ Re-run after Antigravity updates.'
|
||||
: '\n⚠️ Some patches failed.');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
Reference in New Issue
Block a user