fix(plan-mode): promoted reviewed plan path into state before refine
handlePlanApproval and the ACP rejection path selected a resolved draft that could differ from PlanModeState.planFilePath but never updated the state, so a refine turn was rebuilt by #buildPlanModeMessage() from the stale path. Both paths now promote the reviewed path into plan-mode state. Fixes #6569
This commit is contained in:
@@ -1691,6 +1691,12 @@ export class AcpAgent implements Agent {
|
||||
planExists: true,
|
||||
};
|
||||
if (!approved) {
|
||||
// Rejection keeps plan mode active for another planning turn. Promote the
|
||||
// reviewed path into plan-mode state so the next `#buildPlanModeMessage()`
|
||||
// targets the plan just reviewed, not the stale state path.
|
||||
if (state.planFilePath !== planFilePath) {
|
||||
session.setPlanModeState({ ...state, planFilePath });
|
||||
}
|
||||
const normalizedTitle = normalizePlanTitle(resolvedTitle).title;
|
||||
return {
|
||||
content: [
|
||||
|
||||
@@ -3708,6 +3708,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
||||
return;
|
||||
}
|
||||
|
||||
// resolveApprovedPlan may return a newer draft than the path recorded in
|
||||
// plan-mode state. `AgentSession.#buildPlanModeMessage()` reads that state,
|
||||
// so if the operator refines (or dismisses and keeps planning) the next
|
||||
// planning turn must target the plan just reviewed — promote the reviewed
|
||||
// path into plan-mode state now, mirroring the print-mode approval handler.
|
||||
const planState = this.session.getPlanModeState();
|
||||
if (planState?.enabled && planState.planFilePath !== planFilePath) {
|
||||
this.session.setPlanModeState({ ...planState, planFilePath });
|
||||
this.sessionManager.appendModeChange("plan", { planFilePath });
|
||||
}
|
||||
|
||||
const contextUsage = this.#getPlanApprovalContextUsage();
|
||||
const keepContextLabel = this.#formatKeepContextLabel(contextUsage);
|
||||
const keepContextDisabled = this.#isKeepContextDisabled(contextUsage);
|
||||
|
||||
@@ -394,6 +394,34 @@ describe("InteractiveMode plan review rendering", () => {
|
||||
expect(onInput).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("promotes the reviewed plan path into plan-mode state before refining", async () => {
|
||||
const resolve = (url: string) =>
|
||||
resolveLocalUrlToPath(url, {
|
||||
getArtifactsDir: () => session.sessionManager.getArtifactsDir(),
|
||||
getSessionId: () => session.sessionManager.getSessionId(),
|
||||
});
|
||||
const oldPlanPath = "local://old-plan.md";
|
||||
const newPlanPath = "local://new-draft-plan.md";
|
||||
await Bun.write(resolve(oldPlanPath), "# Old\n\nold body");
|
||||
await Bun.write(resolve(newPlanPath), "# New\n\nnew body");
|
||||
|
||||
mode.planModeEnabled = true;
|
||||
mode.planModePlanFilePath = oldPlanPath;
|
||||
// State still points at the previously reviewed (older) plan.
|
||||
session.setPlanModeState({ enabled: true, planFilePath: oldPlanPath, workflow: "parallel", reentry: true });
|
||||
|
||||
const feedback = "Refinement feedback:\n- add more detail\n";
|
||||
vi.spyOn(mode, "showPlanReview").mockImplementation(async (_plan, _title, _options, dialogOptions) => {
|
||||
dialogOptions?.onFeedbackChange?.(feedback);
|
||||
return "Refine plan";
|
||||
});
|
||||
vi.spyOn(session, "prompt").mockResolvedValue(undefined as never);
|
||||
|
||||
await mode.handlePlanApproval({ planFilePath: newPlanPath, planExists: true, title: "NEW" });
|
||||
|
||||
expect(session.getPlanModeState()?.planFilePath).toBe(newPlanPath);
|
||||
});
|
||||
|
||||
it("opens the annotation external editor from the real plan review overlay", async () => {
|
||||
const editorPath = path.join(tempDir.path(), "annotation-editor.sh");
|
||||
await Bun.write(
|
||||
|
||||
Reference in New Issue
Block a user