fix(extension): step_type mapping bug + diff_review handler refactor

- Separate read tools (file_permission) from write tools (code_edit)
- write_to_file/replace_file_content now use AcknowledgeCascadeCodeEdit RPC
- diff_review: 2-strategy approach (RPC first, openReviewChanges fallback)
- Track modified_files and edit_step_indices in diff_review pending
- known-issues: 3 new entries (pending accumulation, step_type bug, isDirty failure)
This commit is contained in:
Variet Worker
2026-03-16 13:52:02 +09:00
parent 078f721187
commit d521dd5fa3
6 changed files with 252 additions and 63 deletions

View File

@@ -1746,6 +1746,8 @@ function setupMonitor() {
let wasRunning = false; // track RUNNING→IDLE transition for response capture
let lastUserInputStepIdx = -1; // track user input for response matching
let pendingModifiedFiles = []; // accumulate modified files during RUNNING
let pendingModifiedFilePaths = []; // full paths for diff review
let pendingEditStepIndices = []; // step indices for AcknowledgeCascadeCodeEdit
let lastResponseCaptureStep = -1; // dedup: don't capture same response twice
setInterval(async () => {
pollCount++;
@@ -1885,6 +1887,8 @@ function setupMonitor() {
const bn = tf.split(/[\\/]/).pop() || tf;
if (!pendingModifiedFiles.includes(bn)) {
pendingModifiedFiles.push(bn);
pendingModifiedFilePaths.push(tf);
pendingEditStepIndices.push(actualIdx);
logToFile(`[DIFF-TRACK] + ${bn} (step ${actualIdx})`);
}
}
@@ -2070,7 +2074,9 @@ function setupMonitor() {
conversation_id: activeSessionId,
command,
description: `Step #${actualIndex} (${(oStep.type || '').replace('CORTEX_STEP_TYPE_', '')})`,
step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search', 'replace_file_content', 'write_to_file', 'multi_replace_file_content'].includes(toolName) ? 'file_permission' : toolName,
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'
: toolName,
step_index: actualIndex,
source: 'step_probe_offset',
});
@@ -2133,7 +2139,9 @@ function setupMonitor() {
conversation_id: activeSessionId,
command,
description,
step_type: ['view_file', 'list_dir', 'find_by_name', 'read_file', 'grep_search', 'replace_file_content', 'write_to_file', 'multi_replace_file_content'].includes(toolName) ? 'file_permission' : toolName,
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'
: toolName,
step_index: si,
source: 'step_probe',
});
@@ -2437,8 +2445,12 @@ function setupMonitor() {
{ text: 'Accept all', index: 0 },
{ text: 'Reject all', index: 1 },
],
modified_files: pendingModifiedFilePaths.slice(0, 20),
edit_step_indices: pendingEditStepIndices.slice(0, 20),
});
pendingModifiedFiles = []; // reset after notification
pendingModifiedFilePaths = [];
pendingEditStepIndices = [];
}
wasRunning = isRunning;
}
@@ -2587,39 +2599,96 @@ async function processResponseFile(filePath) {
// DOM observer: renderer handles clicking via pollResponse
// Step probe/stall: try RPC → VS Code commands → log results
const approved = resp.approved;
// ── diff_review: Accept all / Reject all via VS Code commands ──
// ── diff_review: Accept all / Reject all ──
if (pendingStepType === 'diff_review') {
const btnIdx = resp.button_index ?? -1;
const isAccept = btnIdx === 0 || (btnIdx === -1 && approved);
const cmd = isAccept
? 'antigravity.prioritized.agentAcceptAllInFile'
: 'antigravity.prioritized.agentRejectAllInFile';
logToFile(`[RESPONSE] diff_review → ${cmd} (btnIdx=${btnIdx})`);
try {
// Find dirty documents and focus each before executing
const dirtyDocs = vscode.workspace.textDocuments.filter(d => d.isDirty);
logToFile(`[RESPONSE] diff_review: ${dirtyDocs.length} dirty docs found`);
if (dirtyDocs.length > 0) {
for (const doc of dirtyDocs) {
try {
await vscode.window.showTextDocument(doc, { preview: false });
await new Promise(r => setTimeout(r, 200)); // brief wait for focus
await vscode.commands.executeCommand(cmd);
logToFile(`[RESPONSE] diff_review: ${cmd} on ${doc.fileName.split(/[\\/]/).pop()} OK`);
}
catch (perFileErr) {
logToFile(`[RESPONSE] diff_review per-file error: ${perFileErr.message?.substring(0, 80)}`);
logToFile(`[RESPONSE] diff_review → ${isAccept ? 'ACCEPT' : 'REJECT'} (btnIdx=${btnIdx})`);
let diffReviewDone = false;
const targetSession = sessionId || activeSessionId;
// ── Strategy 1: AcknowledgeCascadeCodeEdit RPC ──
// Accept/reject all pending code edits via protocol (no UI interaction needed)
if (sdk) {
try {
// Get tracked step indices from pending data (or use all recent edit steps)
const trackedSteps = [];
const pendingDir = path.join(bridgePath, 'pending');
try {
const pendingFile = path.join(pendingDir, `${resp.request_id}.json`);
if (fs.existsSync(pendingFile)) {
const pd = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
if (pd.edit_step_indices)
trackedSteps.push(...pd.edit_step_indices);
}
}
catch { }
// If no tracked steps, use the step_index from the pending
if (trackedSteps.length === 0 && pendingStepIndex > 0) {
trackedSteps.push(pendingStepIndex);
}
logToFile(`[DIFF-REVIEW-RPC] AcknowledgeCascadeCodeEdit(session=${targetSession.substring(0, 8)}, accept=${isAccept}, steps=[${trackedSteps.join(',')}])`);
const ackResult = await sdk.ls.rawRPC('AcknowledgeCascadeCodeEdit', {
cascadeId: targetSession,
accept: isAccept,
...(trackedSteps.length > 0 ? { stepIndices: trackedSteps } : {}),
});
logToFile(`[DIFF-REVIEW-RPC] ✅ SUCCESS: ${JSON.stringify(ackResult).substring(0, 200)}`);
diffReviewDone = true;
}
else {
// No dirty docs — try command anyway
await vscode.commands.executeCommand(cmd);
logToFile(`[RESPONSE] diff_review: no dirty docs, command executed anyway`);
catch (rpcErr) {
logToFile(`[DIFF-REVIEW-RPC] ❌ ${rpcErr.message.substring(0, 200)}`);
}
}
catch (cmdErr) {
logToFile(`[RESPONSE] diff_review command error: ${cmdErr.message}`);
// ── Strategy 2: Open review panel + focus file + VS Code command ──
if (!diffReviewDone) {
try {
// Step 2a: Open the Review Changes panel
try {
await vscode.commands.executeCommand('antigravity.openReviewChanges');
logToFile(`[DIFF-REVIEW-CMD] openReviewChanges OK`);
await new Promise(r => setTimeout(r, 500));
}
catch { }
// Step 2b: Find modified files from pending data
let modifiedFiles = [];
try {
const pendingFile = path.join(bridgePath, 'pending', `${resp.request_id}.json`);
if (fs.existsSync(pendingFile)) {
const pd = JSON.parse(fs.readFileSync(pendingFile, 'utf-8'));
if (pd.modified_files)
modifiedFiles = pd.modified_files;
}
}
catch { }
// Step 2c: Open and focus each modified file, then execute
if (modifiedFiles.length > 0) {
for (const filePath of modifiedFiles) {
try {
const uri = vscode.Uri.file(filePath);
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc, { preview: false });
await new Promise(r => setTimeout(r, 300));
await vscode.commands.executeCommand(cmd);
logToFile(`[DIFF-REVIEW-CMD] ${cmd} on ${filePath.split(/[\\/]/).pop()} OK`);
}
catch (e) {
logToFile(`[DIFF-REVIEW-CMD] per-file error on ${filePath}: ${e.message?.substring(0, 80)}`);
}
}
}
else {
// No file list — just execute command (best effort)
await vscode.commands.executeCommand(cmd);
logToFile(`[DIFF-REVIEW-CMD] ${cmd} executed (no file list)`);
}
diffReviewDone = true;
}
catch (cmdErr) {
logToFile(`[DIFF-REVIEW-CMD] error: ${cmdErr.message}`);
}
}
}
else if (isDomObserver) {
@@ -2949,21 +3018,23 @@ async function tryApprovalStrategies(approved, sessionId, stepType = '', stepInd
// Build interaction sub-message based on step_type
const typeLower = stepType.toLowerCase().replace('cortex_step_type_', '');
let interactionPayload = {};
if (typeLower.includes('write_to_file') || typeLower.includes('propose_code') || typeLower.includes('write_cascade_edit')) {
if (typeLower.includes('code_edit') || typeLower.includes('write_to_file') || typeLower.includes('propose_code') || typeLower.includes('write_cascade_edit')) {
// CODE EDIT: Uses separate AcknowledgeCascadeCodeEdit RPC
try {
logToFile(`[APPROVAL-PROTO-ACK] AcknowledgeCascadeCodeEdit(cascadeId=${sessionId.substring(0, 8)}, accept=true, stepIndices=[${effectiveStepIndex}])`);
logToFile(`[APPROVAL-CODE-EDIT] AcknowledgeCascadeCodeEdit(cascadeId=${sessionId.substring(0, 8)}, accept=${approved}, stepIndices=[${effectiveStepIndex}])`);
const ackResult = await sdk.ls.rawRPC('AcknowledgeCascadeCodeEdit', {
cascadeId: sessionId,
accept: true,
accept: approved,
stepIndices: [effectiveStepIndex],
});
logToFile(`[APPROVAL-PROTO-ACK] ✅ SUCCESS: ${JSON.stringify(ackResult).substring(0, 200)}`);
return `RPC-PROTO-ACK:AcknowledgeCascadeCodeEdit`;
logToFile(`[APPROVAL-CODE-EDIT] ✅ SUCCESS: ${JSON.stringify(ackResult).substring(0, 200)}`);
return `RPC:AcknowledgeCascadeCodeEdit(accept=${approved})`;
}
catch (e) {
logToFile(`[APPROVAL-PROTO-ACK] ❌ ${e.message.substring(0, 200)}`);
// Fall through to HandleCascadeUserInteraction
logToFile(`[APPROVAL-CODE-EDIT] ❌ ${e.message.substring(0, 200)}`);
// Fallback: try HandleCascadeUserInteraction with runCommand
logToFile(`[APPROVAL-CODE-EDIT] falling back to HandleCascadeUserInteraction`);
interactionPayload = { runCommand: { confirm: true } };
}
}
// Map step_type to interaction sub-message field