From 02d9799f20d8d09e8519af7d876c38f299c490af Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Tue, 14 Apr 2026 07:38:01 +0900 Subject: [PATCH] =?UTF-8?q?diag(observer):=20v10-diag=20=E2=80=94=20extrac?= =?UTF-8?q?tContextFromNearby=20trail=20logging=20for=20context=20extracti?= =?UTF-8?q?on=20failure=20analysis=20#task-619?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/devlog/2026-04-14.md | 5 +++ docs/devlog/entries/20260414-001.md | 49 +++++++++++++++++++++++++++++ extension/src/http-bridge.ts | 3 ++ extension/src/observer-script.ts | 27 ++++++++++++++-- 4 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 docs/devlog/2026-04-14.md create mode 100644 docs/devlog/entries/20260414-001.md diff --git a/docs/devlog/2026-04-14.md b/docs/devlog/2026-04-14.md new file mode 100644 index 0000000..1e4ffe7 --- /dev/null +++ b/docs/devlog/2026-04-14.md @@ -0,0 +1,5 @@ +# 2026-04-14 + +| NNN | HH:MM | 작업 설명 | `커밋해시` | 완료여부 | +|-------|-------|----------|-----------|----------| +| 001 | 07:29 | Observer v9 E2E 검증 — BEACON/ping 동작 확인, pending POST 수신 확인, 컨텍스트 추출 실패 진단 (ctx="Always run"), v10-diag 진단 로깅 추가 (observer + http-bridge), V8 캐시 삭제(523MB) | `c1e61d8` | 🔧 | diff --git a/docs/devlog/entries/20260414-001.md b/docs/devlog/entries/20260414-001.md new file mode 100644 index 0000000..eb59810 --- /dev/null +++ b/docs/devlog/entries/20260414-001.md @@ -0,0 +1,49 @@ +# Observer v9 E2E 검증 + v10-diag 진단 추가 + +- **시간**: 2026-04-14 07:29~07:36 +- **Commit**: `pending` +- **Vikunja**: #task-619 → 진행중 + +## 검증 결과 + +### ✅ 동작 확인 (정상) +1. **Observer v9 스크립트 실행**: `/ping` 수신 확인 (22:30:13~) +2. **BEACON ping**: `/ping?beacon=1&t=...` 형태로 GET /ping 수신 +3. **DOM dump**: `dump_html_5.json` (7858 bytes) 정상 저장 +4. **WS Hub 연결**: `wsConnected: true` +5. **세션 감지**: `activeSessionId: 39c51225` (현재 세션) +6. **pending POST**: `/pending` → `1776119428450_gnpw` 등 다수 수신 + +### ❌ 실패: 컨텍스트 추출 +- 모든 pending에서 `cmd="Always run"`, `btns=1`, `ctx="Always run"` +- `extractContextFromNearby()` 실패: pre/code 요소를 찾지 못함 +- `collectSiblingButtons()` 실패: 1개 버튼만 감지 ("Cancel" 미감지) +- `sessionStalled: true` + `lastPendingStepIndex: -1` (trajectory not found) + +### 원인 분석 (추정) +- AG Native UI의 "Always run" 버튼 근처에 `
`, ``, `[class*="terminal"]` 요소가 없음
+- 실제 명령어 텍스트가 다른 요소 유형(span, div 등)에 담겨있을 가능성
+- DOM 구조가 이전 세션에서 확인한 것과 다를 수 있음 (Settings/Launchpad dump vs 대화 dump)
+
+## 수정 사항 (v10-diag)
+
+### observer-script.ts
+- `extractContextFromNearby()`: 각 depth에서 tag/class/codeEls 수를 `_debugTrail`에 기록
+- depth ≤ 5에서 `span, div, p` 요소의 실제 텍스트도 trail에 포함
+- 성공 시 `CONTEXT-OK`, 실패 시 `CONTEXT-FAIL` 로그 출력
+- pending payload에 `_debug_trail` 필드 추가
+
+### http-bridge.ts
+- `_handlePending()`에서 `_debug_trail` 필드를 `[HTTP-DIAG] trail:` 로그로 출력
+
+## 미완료
+1. **AG 재시작 필요** — v10-diag 빌드 완료 + V8 캐시 삭제됨, AG 재시작 후 trail 분석 필요
+2. **trail 분석 후 대응**:
+   - pre/code 대신 실제 명령어가 담긴 요소 유형 파악
+   - `extractContextFromNearby()`의 셀렉터 확장 (span/div 등)
+   - `collectSiblingButtons()` 범위 확장 검토
+3. **"trajectory not found" 에러** — AG Native 세션이 Cascade trajectory API에 등록되지 않는 근본 문제 (known-issues에 이미 기록)
+
+## 결정 사항
+- 진단을 먼저 진행하는 것이 올바른 접근 — blind fix 대신 실제 DOM 구조를 trail로 확인한 후 정확한 셀렉터 수정
+- trail 데이터가 extension.log에 기록되므로 AG 재시작 후 즉시 확인 가능
diff --git a/extension/src/http-bridge.ts b/extension/src/http-bridge.ts
index 0f6aeb0..6d9ba49 100644
--- a/extension/src/http-bridge.ts
+++ b/extension/src/http-bridge.ts
@@ -328,6 +328,9 @@ function _handlePending(req: any, res: any, ctx: HttpBridgeContext) {
                 ctx.logToFile(`[HTTP-WS] pending sent via WS: ${rid}`);
             }
             ctx.logToFile(`[HTTP] pending created: ${rid} cmd="${data.command}" btns=${(data.buttons || []).length} ctx="${(data.description || '').substring(0, 50)}"`);
+            if (data._debug_trail) {
+                ctx.logToFile(`[HTTP-DIAG] trail: ${data._debug_trail.substring(0, 500)}`);
+            }
             res.writeHead(200, { 'Content-Type': 'application/json' });
             res.end(JSON.stringify({ ok: true, request_id: rid }));
         } catch (e: any) {
diff --git a/extension/src/observer-script.ts b/extension/src/observer-script.ts
index a7052ed..94f9195 100644
--- a/extension/src/observer-script.ts
+++ b/extension/src/observer-script.ts
@@ -110,12 +110,15 @@ export function generateApprovalObserverScript(_port: number): string {
   }
 
   // v9: Climb DOM tree to find pre/code content near the button (no data-step-index needed)
+  // v10-diag: Added diagnostic trail logging
   function extractContextFromNearby(btn) {
     var node = btn;
+    var _debugTrail = [];
     for (var depth = 0; depth < 20 && node; depth++) {
-      if (!node.querySelector) { node = node.parentElement; continue; }
+      if (!node.querySelector) { _debugTrail.push('d'+depth+':noQS'); node = node.parentElement; continue; }
       // Look for code/pre blocks (actual command text)
       var codeEls = node.querySelectorAll('pre, code, [class*="terminal"]');
+      _debugTrail.push('d'+depth+':tag='+((node.tagName||'?').toLowerCase())+',cls='+(((typeof node.className==='string')?node.className:'').substring(0,60))+',codeEls='+codeEls.length);
       for (var ci = 0; ci < codeEls.length; ci++) {
         var codeText = cleanLines((codeEls[ci].textContent || '').trim().substring(0, 500));
         if (codeText && codeText.length > 5 && !/^Running\\s*\\d/i.test(codeText)) {
@@ -133,16 +136,35 @@ export function generateApprovalObserverScript(_port: number): string {
           var parts = [];
           if (headerText) parts.push(headerText);
           parts.push(codeText);
+          log('CONTEXT-OK d='+depth+' trail='+_debugTrail.join(' > '));
           return parts.join(' — ');
         }
       }
+      // v10-diag: also look for any text-bearing elements (span, div, p) with substantial text
+      if (depth <= 5) {
+        var textEls = node.querySelectorAll('span, div, p');
+        var foundTexts = [];
+        for (var ti = 0; ti < Math.min(textEls.length, 10); ti++) {
+          var tText = (textEls[ti].textContent || '').trim();
+          if (tText.length > 10 && tText.length < 300 && !isNoiseLine(tText)) {
+            foundTexts.push(tText.substring(0, 80));
+          }
+        }
+        if (foundTexts.length > 0) {
+          _debugTrail.push('texts=['+foundTexts.join('|')+']');
+        }
+      }
       node = node.parentElement;
     }
     // Last resort: try aria-label or title on the button
     var ariaLabel = btn.getAttribute('aria-label') || btn.getAttribute('title') || '';
+    log('CONTEXT-FAIL trail='+_debugTrail.join(' > '));
+    // v10-diag: dump button ancestor chain as diagnostic
+    _lastContextDebug = _debugTrail.join(' > ');
     if (ariaLabel && ariaLabel.length > 5) return ariaLabel;
     return cleanButtonText(btn);
   }
+  var _lastContextDebug = '';
 
   function extractStepContext(btn) {
     var stepEl = getStepContainer(btn);
@@ -625,7 +647,8 @@ export function generateApprovalObserverScript(_port: number): string {
           command:txt2,
           description:desc2,
           step_type:type2,
-          buttons:buttonsArr2
+          buttons:buttonsArr2,
+          _debug_trail:_lastContextDebug||''
         };
         fetch(BASE+'/pending',{
           method:'POST',