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` | ✅ |
|
| 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` | ✅ |
|
| 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
|
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) {
|
for (const spec of htmlFileSpecs) {
|
||||||
const htmlPath = path.join(scriptDir, spec.name);
|
const htmlPath = path.join(scriptDir, spec.name);
|
||||||
const backupPath = htmlPath + '.orig';
|
const backupPath = htmlPath + '.orig';
|
||||||
@@ -592,6 +610,14 @@ async function setupApprovalObserver() {
|
|||||||
logToFile(`[OBSERVER] ${spec.name} patch error: ${e.message}`);
|
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
|
// 4. Update product.json checksums so vscode-file:// serves our patched files
|
||||||
updateProductChecksums();
|
updateProductChecksums();
|
||||||
@@ -1409,7 +1435,7 @@ function generateApprovalObserverScript(_port) {
|
|||||||
// ── Poll for Discord response (multi-button group aware) ──
|
// ── Poll for Discord response (multi-button group aware) ──
|
||||||
function pollResponseGroup(rid,btnRefs,bidList,groupKey){
|
function pollResponseGroup(rid,btnRefs,bidList,groupKey){
|
||||||
var polls=0;
|
var polls=0;
|
||||||
var maxPolls=600; // 5 minutes at 500ms interval
|
var maxPolls=200; // 5 minutes at 1500ms interval
|
||||||
var timer=setInterval(function(){
|
var timer=setInterval(function(){
|
||||||
polls++;
|
polls++;
|
||||||
// Check if ANY button in the group is still in DOM
|
// Check if ANY button in the group is still in DOM
|
||||||
@@ -1454,7 +1480,7 @@ function generateApprovalObserverScript(_port) {
|
|||||||
delete _sent[groupKey];
|
delete _sent[groupKey];
|
||||||
for(var ri=0;ri<bidList.length;ri++){delete _sent[bidList[ri]];}
|
for(var ri=0;ri<bidList.length;ri++){delete _sent[bidList[ri]];}
|
||||||
}).catch(function(){});
|
}).catch(function(){});
|
||||||
},500);
|
},1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Legacy pollResponse for backward compatibility (single button)
|
// Legacy pollResponse for backward compatibility (single button)
|
||||||
@@ -1532,19 +1558,27 @@ function generateApprovalObserverScript(_port) {
|
|||||||
// FALLBACK: periodic scan every 3s for any missed mutations
|
// FALLBACK: periodic scan every 3s for any missed mutations
|
||||||
setInterval(scheduleScan,3000);
|
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 ──
|
// ── DEEP-INSPECT POLLING: curl→Bridge→Renderer→Results ──
|
||||||
setInterval(function(){
|
(function pollDeepInspect(){
|
||||||
if(!_ready||!BASE)return;
|
if(_ready&&BASE){
|
||||||
fetch(BASE+'/deep-inspect-trigger?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
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();}
|
if(d.inspect){log('🔍 Deep inspect triggered via HTTP');runDeepInspect();}
|
||||||
}).catch(function(){});
|
}).catch(function(){});
|
||||||
},2000);
|
}
|
||||||
|
setTimeout(pollDeepInspect,getAdaptiveInterval());
|
||||||
|
})();
|
||||||
|
|
||||||
// ── TRIGGER-CLICK: Extension→Renderer bridge for programmatic button clicks ──
|
// ── TRIGGER-CLICK: Extension→Renderer bridge for programmatic button clicks ──
|
||||||
// Extension sets clickTrigger via tryApprovalStrategies → renderer polls and clicks
|
// Extension sets clickTrigger via tryApprovalStrategies → renderer polls and clicks
|
||||||
// v3: uses deepFindButtons() to traverse iframes, webviews, shadow DOMs
|
// v3: uses deepFindButtons() to traverse iframes, webviews, shadow DOMs
|
||||||
setInterval(function(){
|
(function pollTriggerClick(){
|
||||||
if(!_ready||!BASE)return;
|
if(_ready&&BASE){
|
||||||
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
fetch(BASE+'/trigger-click?t='+Date.now()).then(function(r){return r.json();}).then(function(d){
|
||||||
if(!d.action)return;
|
if(!d.action)return;
|
||||||
log('🔔 TRIGGER-CLICK received: action='+d.action);
|
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);
|
log('⚠️ iframes='+document.querySelectorAll('iframe').length+' webviews='+document.querySelectorAll('webview').length);
|
||||||
}
|
}
|
||||||
}).catch(function(){});
|
}).catch(function(){});
|
||||||
},1000);
|
}
|
||||||
|
setTimeout(pollTriggerClick,getAdaptiveInterval());
|
||||||
|
})();
|
||||||
|
|
||||||
_obs=true;
|
_obs=true;
|
||||||
log('v3 Observer active — deep DOM traversal + MutationObserver + trigger-click polling');
|
log('v3 Observer active — deep DOM traversal + MutationObserver + trigger-click polling');
|
||||||
@@ -1916,6 +1952,8 @@ function setupMonitor() {
|
|||||||
pd.status = 'auto_resolved';
|
pd.status = 'auto_resolved';
|
||||||
fs.writeFileSync(pfPath, JSON.stringify(pd, null, 2), 'utf-8');
|
fs.writeFileSync(pfPath, JSON.stringify(pd, null, 2), 'utf-8');
|
||||||
logToFile(`[AUTO-RESOLVE] step=${lastPendingStepIndex} progressed → marked ${pf} (age=${Math.round(ageMs / 1000)}s)`);
|
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)) {
|
if (fs.existsSync(pendingFile)) {
|
||||||
try {
|
try {
|
||||||
const pending = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
|
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 || '';
|
sessionId = pending.conversation_id || '';
|
||||||
isDomObserver = pending.auto_detected === true
|
isDomObserver = pending.auto_detected === true
|
||||||
|| pending.source === 'dom_observer';
|
|| pending.source === 'dom_observer';
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user