fix: accumulate all diff reads, intercept report_progress in review mode

This commit is contained in:
2026-06-01 14:05:43 -05:00
parent a9bcdd77dd
commit fb32b97857
2 changed files with 57 additions and 38 deletions
+55 -36
View File
@@ -150,11 +150,12 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise<AgentResult> {
let addedContinueNudge = false;
let reportProgressCount = 0;
let selectedModeName = "";
// Accumulates content previews from read_file calls so that when tool
// messages are pruned under context pressure, the model retains a compact
// summary of what it already read. User messages survive pruning.
const readFileLog = new Map<string, string>(); // path → content excerpt
// Accumulates content from each read_file call (all ranges) so that when
// tool messages are pruned, the model retains the diff content it read.
// Keyed by "path:start:end" so multiple ranges of the same file are kept.
const readFileLog: string[] = [];
let injectedFileReadSummary = false;
let editCommentCount = 0;
while (iterations < MAX_ITERATIONS) {
iterations++;
@@ -210,21 +211,22 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise<AgentResult> {
);
if (promptTokens > numCtx * 0.77) {
messages = pruneToolMessages(messages);
// Inject a compact summary of all files read so far as a user message
// so the model retains the context even after tool messages are pruned.
// Inject accumulated diff content as a user message (survives pruning).
// Only inject once — subsequent prune events leave this message intact.
if (readFileLog.size > 0 && !injectedFileReadSummary) {
if (readFileLog.length > 0 && !injectedFileReadSummary) {
injectedFileReadSummary = true;
const entries = [...readFileLog.entries()]
.map(([p, c]) => `### ${p}\n${c}`)
.join("\n\n");
const combined = readFileLog.join("\n---\n").slice(0, 10000);
const isReview =
selectedModeName === "Review" || selectedModeName === "IncrementalReview";
messages.push({
role: "user",
content:
`The following files were read earlier but their content was pruned from context. ` +
`Use these excerpts as your understanding of each file when writing the review:\n\n${entries}`,
`Diff content read earlier (preserved after context pruning):\n\n${combined}\n\n` +
(isReview
? "Now call create_pull_request_review with your review findings. Do NOT call report_progress."
: "Continue the workflow using the content above."),
});
log.info(`» injected file-read summary: ${readFileLog.size} file(s)`);
log.info(`» injected file-read summary: ${readFileLog.length} read(s)`);
}
}
}
@@ -306,23 +308,18 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise<AgentResult> {
content: result,
});
// Track read_file calls so we can reconstruct context after pruning.
// read_file returns { content: "..." } JSON — parse out the actual content.
// Promote args logging to info so we can diagnose the path key format.
// Track every read_file call — accumulate all ranges (same path, different
// start/end lines) since the model reads the diff file in multiple chunks.
// read_file returns { content: "..." } JSON — extract the actual content.
if (toolName === "read_file") {
const args = toolArgs as Record<string, unknown>;
log.info(` read_file args keys: ${Object.keys(args).join(", ")} | path type: ${typeof args.path} | path: ${String(args.path).slice(0, 120)}`);
const path = typeof args.path === "string" ? args.path : undefined;
if (path && !readFileLog.has(path)) {
let excerpt: string;
try {
const parsed = JSON.parse(result) as { content?: string };
excerpt = (parsed.content ?? result).slice(0, 500);
} catch {
excerpt = result.slice(0, 500);
}
readFileLog.set(path, excerpt);
let excerpt: string;
try {
const parsed = JSON.parse(result) as { content?: string };
excerpt = (parsed.content ?? result).slice(0, 500);
} catch {
excerpt = result.slice(0, 500);
}
readFileLog.push(excerpt);
}
// After checkout_pr returns, extract and pin the diffPath as a user
@@ -338,10 +335,10 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise<AgentResult> {
role: "user",
content:
`The diff file is at: ${dp}\n\n` +
`Read each file's diff by calling read_file on this path with the line range from the TOC.\n` +
`Example — for "device.ts (163 lines, 1146-1308)": read_file(path="${dp}", offset=1146, limit=163)\n` +
`Read each file's diff by calling read_file on this path with start_line/end_line from the TOC.\n` +
`Example — for "device.ts (163 lines, 1146-1308)": read_file(path="${dp}", start_line=1146, end_line=1308)\n` +
`IMPORTANT: Do NOT call read_file on source files (apps/..., packages/...). ` +
`Use read_file on the diff file path above with the TOC ranges.`,
`Use read_file on the diff file path above with the TOC line ranges.`,
});
log.info(`» pinned diffPath to context: ${dp}`);
}
@@ -350,12 +347,34 @@ async function runOllamaLoop(ctx: AgentRunContext): Promise<AgentResult> {
}
}
// report_progress called more than once means the model is looping —
// it has no signal to stop since the tool always returns success.
// report_progress is forbidden in Review/IncrementalReview — intercept it.
// In other modes, stop after 2+ calls (loop detection).
if (toolName === "report_progress") {
reportProgressCount++;
if (reportProgressCount >= 2) {
log.info("» report_progress called multiple times — stopping loop");
const isReview =
selectedModeName === "Review" || selectedModeName === "IncrementalReview";
if (isReview) {
log.info("» report_progress intercepted in Review mode — redirecting");
messages.push({
role: "user",
content:
"report_progress is not allowed in Review mode. " +
"Call create_pull_request_review now with your findings.",
});
} else {
reportProgressCount++;
if (reportProgressCount >= 2) {
log.info("» report_progress loop — stopping");
await unloadModel(ollama, model);
return { success: true };
}
}
}
// edit_issue_comment called repeatedly means the model is stuck looping.
if (toolName === "edit_issue_comment") {
editCommentCount++;
if (editCommentCount >= 2) {
log.info("» edit_issue_comment loop — stopping");
await unloadModel(ollama, model);
return { success: true };
}