fix(bridge): 4 race condition fixes for approval lifecycle
This commit is contained in:
BIN
extension/gravity-bridge-0.3.10.zip
Normal file
BIN
extension/gravity-bridge-0.3.10.zip
Normal file
Binary file not shown.
@@ -481,6 +481,24 @@ async function setupApprovalObserver() {
|
||||
requiredScript: 'jetskiAgent.js', // JS entry point
|
||||
},
|
||||
];
|
||||
// ── FIX #1: File lock to prevent multi-instance HTML patching race ──
|
||||
const lockFile = path.join(scriptDir, '.patch-lock');
|
||||
let lockAcquired = false;
|
||||
try {
|
||||
if (fs.existsSync(lockFile)) {
|
||||
const lockAge = Date.now() - fs.statSync(lockFile).mtimeMs;
|
||||
if (lockAge < 30_000) {
|
||||
logToFile(`[OBSERVER] another instance is patching (lock age=${Math.round(lockAge/1000)}s) — skipping`);
|
||||
return; // Exit setupApprovalObserver entirely
|
||||
}
|
||||
logToFile(`[OBSERVER] stale lock (age=${Math.round(lockAge/1000)}s) — force-acquiring`);
|
||||
}
|
||||
fs.writeFileSync(lockFile, JSON.stringify({ pid: process.pid, ts: Date.now() }), 'utf-8');
|
||||
lockAcquired = true;
|
||||
} catch (lockErr: any) {
|
||||
logToFile(`[OBSERVER] lock acquire error: ${lockErr.message} — proceeding anyway`);
|
||||
}
|
||||
|
||||
for (const spec of htmlFileSpecs) {
|
||||
const htmlPath = path.join(scriptDir, spec.name);
|
||||
const backupPath = htmlPath + '.orig';
|
||||
@@ -584,6 +602,12 @@ async function setupApprovalObserver() {
|
||||
logToFile(`[OBSERVER] ${spec.name} patch error: ${e.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Release patch lock
|
||||
if (lockAcquired) {
|
||||
try { fs.unlinkSync(lockFile); } catch { }
|
||||
logToFile('[OBSERVER] patch lock released');
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Update product.json checksums so vscode-file:// serves our patched files
|
||||
@@ -1918,6 +1942,8 @@ function setupMonitor() {
|
||||
pd.status = 'auto_resolved';
|
||||
fs.writeFileSync(pfPath, JSON.stringify(pd, null, 2), 'utf-8');
|
||||
logToFile(`[AUTO-RESOLVE] step=${lastPendingStepIndex} progressed → marked ${pf} (age=${Math.round(ageMs/1000)}s)`);
|
||||
// FIX #3: Notify Discord that user approved locally
|
||||
writeChatSnapshot(`✅ **AG에서 직접 승인됨** (step ${lastPendingStepIndex})\n\n\`${(pd.command || '').substring(0, 200)}\``);
|
||||
}
|
||||
}
|
||||
} catch (e: any) { logToFile(`[AUTO-RESOLVE] error: ${e.message}`); }
|
||||
@@ -2452,6 +2478,14 @@ async function processResponseFile(filePath: string) {
|
||||
if (fs.existsSync(pendingFile)) {
|
||||
try {
|
||||
const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
|
||||
|
||||
// FIX #2: Skip if pending was already resolved locally (auto_resolve or expired)
|
||||
if (pending.status === 'auto_resolved' || pending.status === 'expired') {
|
||||
logToFile(`[RESPONSE] SKIP — pending already ${pending.status} (rid=${resp.request_id})`);
|
||||
try { fs.unlinkSync(filePath); } catch { }
|
||||
return;
|
||||
}
|
||||
|
||||
sessionId = pending.conversation_id || '';
|
||||
isDomObserver = pending.auto_detected === true
|
||||
|| pending.source === 'dom_observer';
|
||||
|
||||
Reference in New Issue
Block a user