fix(extension): diff_review steps=[] race condition — in-memory metadata cache (v0.3.13)

Root cause: Collector deletes pending file before Extension reads edit_step_indices.
Fix: diffReviewMetadata Map caches step indices in Extension memory.
Known issue added. Devlog entry 003.
This commit is contained in:
Variet Worker
2026-03-16 16:09:04 +09:00
parent 12a1cf8692
commit 9ef2c3f07c
9 changed files with 163 additions and 43 deletions

View File

@@ -87,6 +87,9 @@ const sentPendingIds = new Set();
// Map<string, number> = `${conversationId}:${stepIndex}` → creation timestamp
const recentPendingSteps = new Map();
const PENDING_MEMORY_TTL_MS = 60_000; // 60 seconds memory retention
// In-memory cache for diff_review metadata (survives pending file deletion by Collector).
// Map<request_id, { edit_step_indices, modified_files }>
const diffReviewMetadata = new Map();
// ─── Project Detection ───
function detectProjectName() {
const config = vscode.workspace.getConfiguration('gravityBridge');
@@ -2622,22 +2625,35 @@ async function processResponseFile(filePath) {
logToFile(`[RESPONSE] diff_review → ${isAccept ? 'ACCEPT' : 'REJECT'} (btnIdx=${btnIdx})`);
let diffReviewDone = false;
const targetSession = sessionId || activeSessionId;
let modifiedFiles = []; // shared between Strategy 1 and 2
// ── 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)
// Get tracked step indices from in-memory cache FIRST (pending file may be deleted by Collector)
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);
}
const memMeta = diffReviewMetadata.get(resp.request_id);
if (memMeta) {
trackedSteps.push(...memMeta.edit_step_indices);
modifiedFiles = memMeta.modified_files;
diffReviewMetadata.delete(resp.request_id); // cleanup
logToFile(`[DIFF-REVIEW-RPC] loaded from memory: steps=[${trackedSteps.join(',')}] files=${modifiedFiles.length}`);
}
else {
// Fallback: try pending file (may already be deleted)
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);
if (pd.modified_files)
modifiedFiles = pd.modified_files;
}
}
catch { }
}
catch { }
// If no tracked steps, use the step_index from the pending
if (trackedSteps.length === 0 && pendingStepIndex > 0) {
trackedSteps.push(pendingStepIndex);
@@ -2665,17 +2681,7 @@ async function processResponseFile(filePath) {
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 2b: Use modifiedFiles from Strategy 1 (already loaded from memory/file above)
// Step 2c: Open and focus each modified file, then execute
if (modifiedFiles.length > 0) {
for (const filePath of modifiedFiles) {
@@ -2967,9 +2973,19 @@ function writePendingApproval(data) {
...(data.step_index !== undefined ? { step_index: data.step_index } : {}),
...(data.source ? { source: data.source } : {}),
...(buttons ? { buttons } : {}),
...(data.modified_files ? { modified_files: data.modified_files } : {}),
...(data.edit_step_indices && data.edit_step_indices.length > 0 ? { edit_step_indices: data.edit_step_indices } : {}),
};
fs.writeFileSync(path.join(pendingDir, `${id}.json`), JSON.stringify(payload, null, 2), 'utf-8');
console.log(`Gravity Bridge: pending approval written → ${id}.json`);
// Cache diff_review metadata in-memory (survives pending file deletion by Collector/Bot)
if (data.step_type === 'diff_review' && (data.edit_step_indices?.length || data.modified_files?.length)) {
diffReviewMetadata.set(id, {
edit_step_indices: data.edit_step_indices || [],
modified_files: data.modified_files || [],
});
logToFile(`[DIFF-REVIEW-CACHE] stored metadata for rid=${id}: steps=[${(data.edit_step_indices || []).join(',')}] files=${(data.modified_files || []).length}`);
}
// Record in memory dedup cache (survives file deletion by Collector/Bot)
if (data.step_index !== undefined && data.conversation_id) {
recentPendingSteps.set(`${data.conversation_id}:${data.step_index}`, nowMs);