From 7bbd8749d7e3ed0b80ba70e3e519e36c95696acc Mon Sep 17 00:00:00 2001 From: Variet Worker Date: Sat, 28 Mar 2026 09:15:11 +0900 Subject: [PATCH] fix(extension): guitar_score step-probe UTF-8 loop + approval stepIndex guard (v0.5.11) --- .agents/references/known-issues.md | 12 +++++ docs/devlog/2026-03-28.md | 5 ++ extension/src/approval-handler.ts | 5 +- extension/src/step-probe.ts | 74 +++++++++++++++++++++++++++++- 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 docs/devlog/2026-03-28.md diff --git a/.agents/references/known-issues.md b/.agents/references/known-issues.md index 75ad1a6..c343f77 100644 --- a/.agents/references/known-issues.md +++ b/.agents/references/known-issues.md @@ -29,6 +29,18 @@ ## πŸ”΄ Active/Recent Issues +### [2026-03-28] [step-probe] GetCascadeTrajectorySteps UTF-8 μ—λŸ¬ λ¬΄ν•œ 루프 +- **증상**: `guitar_score` ν”„λ‘œμ νŠΈμ—μ„œ `[STEP-PROBE] error: ...invalid UTF-8` μ—λŸ¬κ°€ 5μ΄ˆλ§ˆλ‹€ 반볡되며 Discord 승인 μ‹ ν˜Έκ°€ μ „λ‹¬λ˜μ§€ μ•ŠμŒ. +- **원인**: AG LS μ„œλ²„μ—μ„œ νŠΉμ • step의 `CortexStepEphemeralMessage.content`에 λ°”μ΄λ„ˆλ¦¬ 데이터(이미지 λ“±) 포함 β†’ proto UTF-8 직렬화 500 μ—λŸ¬. `catch(e)` λΈ”λ‘μ—μ„œ `stallProbed=true`λ₯Ό μ„€μ •ν•˜μ§€ μ•Šμ•„ `!ctx.stallProbed` 쑰건이 항상 true β†’ 5μ΄ˆλ§ˆλ‹€ 동일 μš”μ²­ λ¬΄ν•œ μž¬μ‹œλ„. +- **ν•΄κ²°** (v0.5.11): `catch` λΈ”λ‘μ—μ„œ UTF-8 μ—λŸ¬ 감지 μ‹œ `stepOffset=currentCount-20`으둜 fallback μš”μ²­. offset도 μ‹€νŒ¨ μ‹œ `stallProbed=true` μ„€μ •ν•˜μ—¬ 루프 차단. `delta>0` 이벀트 λ°œμƒ μ‹œ L433μ—μ„œ μžλ™ 리셋. +- **주의**: `stallProbed=true`λŠ” 영ꡬ Lock이 μ•„λ‹˜ β€” `delta>0` μ‹œ μžλ™ 리셋. UTF-8 μ—λŸ¬λŠ” AG μ„œλ²„ μΈ‘ 문제(이미지/λ°”μ΄λ„ˆλ¦¬ 데이터가 ephemeral message에 포함)μ΄λ―€λ‘œ Extensionμ—μ„œ graceful fallback만 처리. + +### [2026-03-28] [approval-handler] stepIndex λ―Έν™•μ • μ‹œ wrong-stepIndex RPC λ‚­λΉ„ +- **증상**: DOM observer 경둜둜 `terminal_command` pending 생성 ν›„ Discord 승인 μ‹œ `HandleCascadeUserInteraction(stepIndex=0)` β†’ `"input not registered for step 0"` β†’ LS reconnect β†’ μž¬μ‹œλ„ β†’ DOM click fallback으둜 μ €ν•˜. (wrong-LS와 λ™μΌν•œ μ¦μƒμ΄λ‚˜ λ‹€λ₯Έ 원인) +- **원인**: `ctx.lastPendingStepIndex=-1` (step-probeκ°€ UTF-8 μ—λŸ¬λ‘œ WAITING 미감지)μž„μ—λ„ `Math.max(0, -1)=0`으둜 clampλ˜μ–΄ μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” step 0에 RPC 전솑. +- **ν•΄κ²°** (v0.5.11): `effectiveStepIndex = stepIndex >= 0 ? stepIndex : (lastPendingStepIndex >= 0 ? lastPendingStepIndex : -1)`. `effectiveStepIndex < 0`이면 RPC 블둝 전체 skip β†’ DOM click 직행 (κΈ°μ‘΄κ³Ό λ™μž‘ 동일, LS reconnect λ‚­λΉ„ 제거). +- **주의**: κΈ°μ‘΄ κ·œμΉ™ #14(`uint32`에 음수 κΈˆμ§€)와 좩돌처럼 λ³΄μ΄λ‚˜, `effectiveStepIndex=-1`일 λ•Œ RPC 자체λ₯Ό **μ „μ†‘ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ** μœ„λ°˜ μ•„λ‹˜. RPC 전솑 μ‹œμ—λŠ” μ—¬μ „νžˆ μœ νš¨ν•œ stepIndex만 μ‚¬μš©. + ### [2026-03-25] [Architecture] Discord Signal Drop & Extension Freezes - **증상**: μž₯μ‹œκ°„ μžλ¦¬λΉ„μ›€ ν›„ 볡귀 μ‹œ Discord둜 승인 μ‹ ν˜Έκ°€ μ˜€μ§€ μ•Šκ±°λ‚˜ VS Code UIκ°€ 간헐적/μ§€μ†μ μœΌλ‘œ 멈좀(Freeze). - **원인**: diff --git a/docs/devlog/2026-03-28.md b/docs/devlog/2026-03-28.md new file mode 100644 index 0000000..d66f07f --- /dev/null +++ b/docs/devlog/2026-03-28.md @@ -0,0 +1,5 @@ +# Devlog β€” 2026-03-28 + +| # | μ‹œκ°„ | μž‘μ—… | 컀밋 | μƒνƒœ | +|---|------|------|------|------| +| 001 | 09:12 | guitar_score step-probe UTF-8 λ¬΄ν•œλ£¨ν”„ μˆ˜μ • + approval stepIndex 보정 (v0.5.11) | pending | βœ… | diff --git a/extension/src/approval-handler.ts b/extension/src/approval-handler.ts index 4d2b169..22106a1 100644 --- a/extension/src/approval-handler.ts +++ b/extension/src/approval-handler.ts @@ -313,7 +313,8 @@ async function processResponseFile(filePath: string) { */ export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise { const action = approved ? 'APPROVE' : 'REJECT'; - const effectiveStepIndex = Math.max(0, stepIndex >= 0 ? stepIndex : ctx.lastPendingStepIndex); + const effectiveStepIndex = stepIndex >= 0 ? stepIndex + : (ctx.lastPendingStepIndex >= 0 ? ctx.lastPendingStepIndex : -1); ctx.logToFile(`[APPROVAL] Starting ${action} strategies for session ${sessionId.substring(0, 8)} stepType=${stepType} stepIndex=${effectiveStepIndex}`); // ── Dynamic Command Discovery (log what's available during WAITING state) ── @@ -338,7 +339,7 @@ export async function tryApprovalStrategies(approved: boolean, sessionId: string // ══════════════════════════════════════════════════════════ // STRATEGY 0-PROTO: Correct proto-based RPC (decoded from AG source) // ══════════════════════════════════════════════════════════ - if (ctx.sdk && approved) { + if (ctx.sdk && approved && effectiveStepIndex >= 0) { // Build interaction sub-message based on step_type const typeLower = stepType.toLowerCase().replace('cortex_step_type_', ''); let interactionPayload: Record = {}; diff --git a/extension/src/step-probe.ts b/extension/src/step-probe.ts index 02fd3e7..7fa66f5 100644 --- a/extension/src/step-probe.ts +++ b/extension/src/step-probe.ts @@ -601,7 +601,79 @@ function setupMonitor() { } } } catch (e: any) { - ctx.logToFile(`[STEP-PROBE] error: ${e.message}`); + ctx.logToFile(`[STEP-PROBE] error: ${e.message?.substring(0, 150)}`); + // UTF-8 invalid data in a step causes a permanent 500 error on full fetch. + // Attempt stepOffset to skip that step and fetch only recent steps. + const isUtf8Error = e.message?.includes('invalid UTF-8') || e.message?.includes('proto:'); + if (isUtf8Error && ctx.sdk) { + try { + const utf8Offset = Math.max(0, currentCount - 20); + ctx.logToFile(`[STEP-PROBE] UTF-8 fallback: retrying with stepOffset=${utf8Offset}`); + const offsetResp = await ctx.sdk.ls.rawRPC('GetCascadeTrajectorySteps', { + cascadeId: bestSessionId, + stepOffset: utf8Offset, + verbosity: 1, + }); + if (offsetResp?.steps?.length > 0) { + const offsetSteps = offsetResp.steps; + ctx.logToFile(`[STEP-PROBE] UTF-8 offset=${utf8Offset} returned ${offsetSteps.length} steps`); + let foundWaitingInOffset = false; + for (let osi = offsetSteps.length - 1; osi >= 0; osi--) { + const oStep = offsetSteps[osi]; + if (oStep?.status === 'CORTEX_STEP_STATUS_WAITING') { + foundWaitingInOffset = true; + const toolCall = oStep?.metadata?.toolCall; + const toolName = toolCall?.name || (oStep.type || '').replace('CORTEX_STEP_TYPE_', '').toLowerCase(); + let command = toolName; + if (toolCall?.argumentsJson) { + try { + const args = JSON.parse(toolCall.argumentsJson); + if (args.CommandLine) command = `${toolName}: ${args.CommandLine.substring(0, 1500)}`; + else if (args.TargetFile) command = `${toolName}: ${args.TargetFile}`; + else { + const val = args.DirectoryPath || args.SearchPath || args.AbsolutePath || args.Url || args.Query || args.Prompt || Object.values(args).find((v: any) => typeof v === 'string' && v.length > 2); + command = val ? `${toolName}: ${String(val).substring(0, 500)}` : `${toolName}: ${Object.keys(args).join(', ')}`; + } + } catch { command = toolName; } + } + const actualIndex = utf8Offset + osi; + ctx.logToFile(`[STEP-PROBE] β˜… WAITING (via UTF-8 offset)! step=${actualIndex} type=${oStep.type} cmd='${command}'`); + if (actualIndex !== ctx.lastPendingStepIndex) { + ctx.stallProbed = true; + if (actualIndex > ctx.lastPendingStepIndex) ctx.lastPendingStepIndex = actualIndex; + lastPendingTime = Date.now(); + ctx.sawRunningAfterPending = false; + if (ctx.projectName !== 'default') { + writePendingApproval({ + conversation_id: ctx.activeSessionId, + command, + description: `Step #${actualIndex} (${(oStep.type || '').replace('CORTEX_STEP_TYPE_', '')})`, + step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search'].includes(toolName) ? 'file_permission' + : ['write_to_file', 'replace_file_content', 'multi_replace_file_content'].includes(toolName) ? 'code_edit' + : ['browser_subagent', 'open_browser_url'].includes(toolName) ? 'browser_subagent' + : toolName, + step_index: actualIndex, + source: 'step_probe_utf8_offset', + }); + } + } + // NOTE: no break β€” process ALL parallel WAITING steps + } + } + if (!foundWaitingInOffset) { + ctx.logToFile(`[STEP-PROBE] UTF-8 offset: no WAITING found β€” stallProbed=true to prevent loop`); + ctx.stallProbed = true; // prevent retry loop; resets on delta>0 + ctx.sessionStalled = false; + } + } else { + ctx.logToFile(`[STEP-PROBE] UTF-8 offset returned empty β€” stallProbed=true`); + ctx.stallProbed = true; + } + } catch (oe: any) { + ctx.logToFile(`[STEP-PROBE] UTF-8 offset also failed: ${oe.message?.substring(0, 100)}`); + ctx.stallProbed = true; // permanent error β€” block retry loop; resets on delta>0 + } + } } }