fix(prewalk): stopped completion turn loop

Limited the hidden continuation safety net to the assistant turn immediately following the plan nudge, so later bash-only completion ends normally.

Added regression coverage for commit-style flows that never call edit or write.

Fixes #5551
This commit is contained in:
roboomp
2026-07-15 08:15:28 +00:00
parent 485b31e8f1
commit 2fe124987b
3 changed files with 68 additions and 17 deletions
@@ -1748,6 +1748,8 @@ export class AgentSession {
#prewalk: Prewalk | undefined;
/** True once the plan nudge has been queued; scrubbed from context at the switch. */
#prewalkPlanInjected = false;
/** True until the first assistant turn after the plan nudge completes. */
#prewalkContinuePending = false;
/** True once any successful `todo` call landed — opens the prewalk
* trigger gate: the switch fires at the first edit/write AFTER the todo
* list exists (sessions without an ACTIVE todo tool skip the gate). */
@@ -2247,23 +2249,22 @@ export class AgentSession {
const prewalk = this.#prewalk;
if (!prewalk || context?.message.role !== "assistant") return;
// Structural safety net: every branch below assumes the agent loop will
// run another turn. It won't if THIS turn had no tool calls — the loop
// treats a text-only turn as "the agent is done" and ends the session
// with no further prompting. The plan nudge explicitly asks for a prose
// reply, which makes a text-only turn common right after it — observed
// silently killing production SWE-bench runs before any code was ever
// written. Force one more turn only in that specific, self-created
// hazard window.
if (this.#prewalkPlanInjected && context.toolResults.length === 0) {
this.agent.steer({
role: "custom",
customType: PREWALK_CONTINUE_MESSAGE_TYPE,
content: prewalkContinuePrompt,
attribution: "agent",
display: false,
timestamp: Date.now(),
});
// The plan nudge can produce a prose-only reply, which the agent loop
// treats as completion before any implementation starts. Keep the
// safety net open only for the turn immediately following that nudge:
// once the model calls any tool, later text-only completion is genuine.
if (this.#prewalkContinuePending) {
this.#prewalkContinuePending = false;
if (context.toolResults.length === 0) {
this.agent.steer({
role: "custom",
customType: PREWALK_CONTINUE_MESSAGE_TYPE,
content: prewalkContinuePrompt,
attribution: "agent",
display: false,
timestamp: Date.now(),
});
}
}
// Todo gate: the plan nudge instructs "finish the plan, then init the
@@ -2284,6 +2285,7 @@ export class AgentSession {
if (!action) {
if (!this.#prewalkPlanInjected) {
this.#prewalkPlanInjected = true;
this.#prewalkContinuePending = true;
this.agent.steer({
role: "custom",
customType: PREWALK_PLAN_MESSAGE_TYPE,
@@ -2344,6 +2346,7 @@ export class AgentSession {
}
this.#prewalk = { target, thinkingLevel };
this.#prewalkPlanInjected = true;
this.#prewalkContinuePending = true;
this.agent.steer({
role: "custom",
customType: PREWALK_PLAN_MESSAGE_TYPE,