diff --git a/docs/plan-mode.md b/docs/plan-mode.md index c1ece359..a3cfc1e2 100644 --- a/docs/plan-mode.md +++ b/docs/plan-mode.md @@ -90,10 +90,11 @@ Plan Mode 的核心规则是**只规划,不动手**。例如以下操作是** | 选项 | 作用 | | ---- | ---- | | **1. implement this plan** | 退出 Plan Mode,自动发送实现指令,让 AI 开始按方案写代码 | -| **2. stay in Plan mode** | 保持在 Plan Mode,继续修改或完善方案 | -| **3. switch to Default mode** | 退出 Plan Mode,回到默认模式(不自动开始实现) | +| **2. clear context and implement** | 保留当前会话,创建一个仅携带已确认方案的新会话,并在 Default mode 下开始实现 | +| **3. stay in Plan mode** | 保持在 Plan Mode,继续修改或完善方案 | +| **4. switch to Default mode** | 退出 Plan Mode,回到默认模式(不自动开始实现) | -你可以用数字键 `1-3` 直接选择,也可以用 `↑/↓` 移动光标后按 `Enter` 确认。按 `Esc` 等同于选择 "stay in Plan mode"。 +你可以用数字键 `1-4` 直接选择,也可以用 `↑/↓` 移动光标后按 `Enter` 确认。按 `Esc` 等同于选择 "stay in Plan mode"。选择清除上下文后,原 Plan 会话仍可通过 `/resume` 恢复。新会话不会继承旧对话消息。 ## Plan Mode 与 UpdatePlan 工具的区别 diff --git a/docs/plan-mode_en.md b/docs/plan-mode_en.md index 5b53c801..6a330768 100644 --- a/docs/plan-mode_en.md +++ b/docs/plan-mode_en.md @@ -90,10 +90,11 @@ After the plan is output, Deep Code automatically shows a choice dialog—no ext | Option | Effect | | ------ | ------ | | **1. implement this plan** | Leave Plan Mode and automatically send an implementation prompt so the AI starts coding | -| **2. stay in Plan mode** | Stay in Plan Mode to continue refining the plan | -| **3. switch to Default mode** | Leave Plan Mode and return to Default mode without starting implementation | +| **2. clear context and implement** | Preserve the current session, create a new session carrying only the approved plan, and start implementing in Default mode | +| **3. stay in Plan mode** | Stay in Plan Mode to continue refining the plan | +| **4. switch to Default mode** | Leave Plan Mode and return to Default mode without starting implementation | -You can press `1-3` to select directly, or use `↑/↓` to move the cursor and `Enter` to confirm. Pressing `Esc` is equivalent to choosing "stay in Plan mode." +You can press `1-4` to select directly, or use `↑/↓` to move the cursor and `Enter` to confirm. Pressing `Esc` is equivalent to choosing "stay in Plan mode." After clearing context, the original Plan session remains available through `/resume`. The new session does not inherit its conversation messages. ## Plan Mode vs. UpdatePlan Tool diff --git a/packages/cli/src/tests/prompt-input-keys.test.ts b/packages/cli/src/tests/prompt-input-keys.test.ts index 07e03acb..0477a528 100644 --- a/packages/cli/src/tests/prompt-input-keys.test.ts +++ b/packages/cli/src/tests/prompt-input-keys.test.ts @@ -23,6 +23,7 @@ import { buildInitPromptSubmission, buildPromptDraftFromSessionMessage, extractProposedPlan, + getClearContextImplementationPrompt, getImplementationPrompt, getPlanImplementationChoice, disableTerminalExtendedKeys, @@ -181,6 +182,27 @@ test("getImplementationPrompt uses Chinese only above five full-width punctuatio assert.equal(getImplementationPrompt(",、;。;。"), "实现此方案。"); }); +test("getClearContextImplementationPrompt carries the complete plan into a fresh context", () => { + const plan = "# 方案\n\n- 保留 `Markdown`\n- Verify tests"; + const prompt = getClearContextImplementationPrompt(plan); + + assert.equal( + prompt, + "A previous agent produced the plan below to accomplish the user's task. " + + "Implement the plan in a fresh context. Treat the plan as the source of " + + "user intent, re-read files as needed, and carry the work through " + + `implementation and verification.\n\n${plan}` + ); +}); + +test("getPlanImplementationChoice maps all four number shortcuts", () => { + const key = { escape: false, return: false }; + assert.equal(getPlanImplementationChoice("1", key, 0), "implement"); + assert.equal(getPlanImplementationChoice("2", key, 0), "clear-context"); + assert.equal(getPlanImplementationChoice("3", key, 0), "stay"); + assert.equal(getPlanImplementationChoice("4", key, 0), "default"); +}); + test("getPlanImplementationChoice treats escape as staying in Plan Mode", () => { assert.equal(getPlanImplementationChoice("", { escape: true, return: false }, 0), "stay"); }); diff --git a/packages/cli/src/ui/index.ts b/packages/cli/src/ui/index.ts index bfb98f3b..ce9c53bc 100644 --- a/packages/cli/src/ui/index.ts +++ b/packages/cli/src/ui/index.ts @@ -18,8 +18,10 @@ export { AskUserQuestionPrompt, applyOtherAnswerEdit } from "./views/AskUserQues export { PlanImplementationPrompt, extractProposedPlan, + getClearContextImplementationPrompt, getImplementationPrompt, getPlanImplementationChoice, + type PlanImplementationChoice, } from "./views/PlanImplementationPrompt"; export { MessageView } from "./components"; export { parseDiffPreview } from "./components/MessageView/utils"; diff --git a/packages/cli/src/ui/views/App.tsx b/packages/cli/src/ui/views/App.tsx index 4f107fd6..3be237b0 100644 --- a/packages/cli/src/ui/views/App.tsx +++ b/packages/cli/src/ui/views/App.tsx @@ -20,7 +20,13 @@ import { formatAskUserQuestionAnswers, } from "../core/ask-user-question"; import { PermissionPrompt, type PermissionPromptResult } from "./PermissionPrompt"; -import { PlanImplementationPrompt, extractProposedPlan, getImplementationPrompt } from "./PlanImplementationPrompt"; +import { + PlanImplementationPrompt, + extractProposedPlan, + getClearContextImplementationPrompt, + getImplementationPrompt, + type PlanImplementationChoice, +} from "./PlanImplementationPrompt"; import { buildExitSummaryText, buildPluginRateLimitHintText, buildResumeHintText } from "../exit-summary"; import { RawMode, useRawModeContext } from "../contexts"; import { renderMessageToStdout } from "../components/MessageView/utils"; @@ -561,13 +567,23 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes ); const handlePlanImplementationChoice = useCallback( - (choice: "implement" | "stay" | "default") => { + (choice: PlanImplementationChoice) => { const proposedPlan = pendingPlanImplementation; setPendingPlanImplementation(null); if (choice === "stay") { return; } setPlanMode(false); + if (choice === "clear-context" && proposedPlan) { + void resetToWelcome().then(() => { + handleSubmit({ + text: getClearContextImplementationPrompt(proposedPlan), + imageUrls: [], + planMode: false, + }); + }); + return; + } if (choice === "implement" && proposedPlan) { handleSubmit({ text: getImplementationPrompt(proposedPlan), @@ -576,7 +592,7 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes }); } }, - [handleSubmit, pendingPlanImplementation] + [handleSubmit, pendingPlanImplementation, resetToWelcome] ); const handleExitShortcut = useCallback(() => { diff --git a/packages/cli/src/ui/views/PlanImplementationPrompt.tsx b/packages/cli/src/ui/views/PlanImplementationPrompt.tsx index e97b361a..2edd3031 100644 --- a/packages/cli/src/ui/views/PlanImplementationPrompt.tsx +++ b/packages/cli/src/ui/views/PlanImplementationPrompt.tsx @@ -3,7 +3,7 @@ import { Box, Text } from "ink"; import { useTerminalInput } from "../hooks"; import type { InputKey } from "../hooks"; -type PlanImplementationChoice = "implement" | "stay" | "default"; +export type PlanImplementationChoice = "implement" | "clear-context" | "stay" | "default"; type Props = { onSelect: (choice: PlanImplementationChoice) => void; @@ -11,10 +11,17 @@ type Props = { const CHOICES: Array<{ value: PlanImplementationChoice; label: string }> = [ { value: "implement", label: "implement this plan" }, + { value: "clear-context", label: "clear context and implement" }, { value: "stay", label: "stay in Plan mode" }, { value: "default", label: "switch to Default mode" }, ]; +const CLEAR_CONTEXT_IMPLEMENTATION_PREFIX = + "A previous agent produced the plan below to accomplish the user's task. " + + "Implement the plan in a fresh context. Treat the plan as the source of " + + "user intent, re-read files as needed, and carry the work through " + + "implementation and verification."; + /** Return only a complete proposed plan, so historical or partial tags cannot trigger the chooser. */ export function extractProposedPlan(reply: string | null): string | null { if (!reply) { @@ -29,6 +36,10 @@ export function getImplementationPrompt(plan: string): string { return fullWidthPunctuationCount > 5 ? "实现此方案。" : "Implement the plan."; } +export function getClearContextImplementationPrompt(plan: string): string { + return `${CLEAR_CONTEXT_IMPLEMENTATION_PREFIX}\n\n${plan}`; +} + export function getPlanImplementationChoice( input: string, key: Pick, @@ -37,7 +48,7 @@ export function getPlanImplementationChoice( if (key.escape) { return "stay"; } - if (input && /^[1-3]$/.test(input)) { + if (input && /^[1-4]$/.test(input)) { return CHOICES[Number(input) - 1]!.value; } return key.return ? CHOICES[cursor]!.value : null; @@ -81,7 +92,7 @@ export function PlanImplementationPrompt({ onSelect }: Props): React.ReactElemen ))} - 1-3 select · ↑/↓ move · Enter select + 1-4 select · ↑/↓ move · Enter select );