fix(extension): guitar_score step-probe UTF-8 loop + approval stepIndex guard (v0.5.11)

This commit is contained in:
Variet Worker
2026-03-28 09:15:11 +09:00
parent d5fdc41f35
commit 7bbd8749d7
4 changed files with 93 additions and 3 deletions

View File

@@ -313,7 +313,8 @@ async function processResponseFile(filePath: string) {
*/
export async function tryApprovalStrategies(approved: boolean, sessionId: string, stepType: string = '', stepIndex: number = -1): Promise<string> {
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<string, any> = {};

View File

@@ -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
}
}
}
}