docs: update devlog with perf optimization entry + compiled output
This commit is contained in:
@@ -4,4 +4,5 @@
|
||||
|---|------|------|------|------|
|
||||
| 001 | 07:00~08:16 | 승인 신호 누락 진단 & 5건 버그 수정 (DEDUP collision, fs.watch fail, default 보호, auto 확인, msg dedup) | `40e3cd5` | ✅ |
|
||||
| 002 | 08:25~08:31 | Extension v0.3.10 버전 범프 & VSIX 빌드 | `10caae1` | ✅ |
|
||||
| 003 | 10:00~10:41 | 승인 라이프사이클 race condition 4건 수정 (HTML lock, pending status skip, auto_resolve Discord 알림, Bot approval_messages) | `015aa79` | ✅ |
|
||||
| 003 | 10:00~10:41 | 승인 라이프사이클 race condition 4건 수정 (HTML lock, pending status skip, auto_resolve Discord 알림, Bot approval_messages) | `f962036` | ✅ |
|
||||
| 004 | 10:41~10:53 | 성능 최적화 3건 (pollResponseGroup 1500ms, renderer adaptive idle, Bot single-pass scanner) + VSIX 빌드 | `ae0509f` | ✅ |
|
||||
|
||||
@@ -502,6 +502,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) {
|
||||
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';
|
||||
@@ -592,6 +610,14 @@ 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
|
||||
updateProductChecksums();
|
||||
@@ -1409,7 +1435,7 @@ function generateApprovalObserverScript(_port) {
|
||||
// ── Poll for Discord response (multi-button group aware) ──
|
||||
function pollResponseGroup(rid,btnRefs,bidList,groupKey){
|
||||
var polls=0;
|
||||
var maxPolls=600; // 5 minutes at 500ms interval
|
||||
var maxPolls=200; // 5 minutes at 1500ms interval
|
||||
var timer=setInterval(function(){
|
||||
polls++;
|
||||
// Check if ANY button in the group is still in DOM
|
||||
@@ -1454,7 +1480,7 @@ function generateApprovalObserverScript(_port) {
|
||||
delete _sent[groupKey];
|
||||
for(var ri=0;ri<bidList.length;ri++){delete _sent[bidList[ri]];}
|
||||
}).catch(function(){});
|
||||
},500);
|
||||
},1500);
|
||||
}
|
||||
|
||||
// Legacy pollResponse for backward compatibility (single button)
|
||||
@@ -1532,19 +1558,27 @@ function generateApprovalObserverScript(_port) {
|
||||
// FALLBACK: periodic scan every 3s for any missed mutations
|
||||
setInterval(scheduleScan,3000);
|
||||
|
||||
// ── Adaptive idle detection for HTTP polls ──
|
||||
var _lastActivity=Date.now();
|
||||
var _idleThreshold=60000; // 60s without DOM changes → slow mode
|
||||
new MutationObserver(function(){_lastActivity=Date.now();}).observe(document.body,{childList:true,subtree:true,attributes:true});
|
||||
function getAdaptiveInterval(){return (Date.now()-_lastActivity>_idleThreshold)?10000:2000;}
|
||||
|
||||
// ── DEEP-INSPECT POLLING: curl→Bridge→Renderer→Results ──
|
||||
setInterval(function(){
|
||||
if(!_ready||!BASE)return;
|
||||
(function pollDeepInspect(){
|
||||
if(_ready&&BASE){
|
||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(d.inspect){log('🔍 Deep inspect triggered via HTTP');runDeepInspect();}
|
||||
}).catch(function(){});
|
||||
},2000);
|
||||
}
|
||||
setTimeout(pollDeepInspect,getAdaptiveInterval());
|
||||
})();
|
||||
|
||||
// ── TRIGGER-CLICK: Extension→Renderer bridge for programmatic button clicks ──
|
||||
// Extension sets clickTrigger via tryApprovalStrategies → renderer polls and clicks
|
||||
// v3: uses deepFindButtons() to traverse iframes, webviews, shadow DOMs
|
||||
setInterval(function(){
|
||||
if(!_ready||!BASE)return;
|
||||
(function pollTriggerClick(){
|
||||
if(_ready&&BASE){
|
||||
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||
if(!d.action)return;
|
||||
log('🔔 TRIGGER-CLICK received: action='+d.action);
|
||||
@@ -1632,7 +1666,9 @@ function generateApprovalObserverScript(_port) {
|
||||
log('⚠️ iframes='+document.querySelectorAll('iframe').length+' webviews='+document.querySelectorAll('webview').length);
|
||||
}
|
||||
}).catch(function(){});
|
||||
},1000);
|
||||
}
|
||||
setTimeout(pollTriggerClick,getAdaptiveInterval());
|
||||
})();
|
||||
|
||||
_obs=true;
|
||||
log('v3 Observer active — deep DOM traversal + MutationObserver + trigger-click polling');
|
||||
@@ -1916,6 +1952,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)}\``);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2482,6 +2520,15 @@ async function processResponseFile(filePath) {
|
||||
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';
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user