Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/plan-mode.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 工具的区别

Expand Down
7 changes: 4 additions & 3 deletions docs/plan-mode_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
22 changes: 22 additions & 0 deletions packages/cli/src/tests/prompt-input-keys.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
buildInitPromptSubmission,
buildPromptDraftFromSessionMessage,
extractProposedPlan,
getClearContextImplementationPrompt,
getImplementationPrompt,
getPlanImplementationChoice,
disableTerminalExtendedKeys,
Expand Down Expand Up @@ -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");
});
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/ui/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down
22 changes: 19 additions & 3 deletions packages/cli/src/ui/views/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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),
Expand All @@ -576,7 +592,7 @@ function App({ projectRoot, initialPrompt, resumeSessionId, forkSessionId, onRes
});
}
},
[handleSubmit, pendingPlanImplementation]
[handleSubmit, pendingPlanImplementation, resetToWelcome]
);

const handleExitShortcut = useCallback(() => {
Expand Down
17 changes: 14 additions & 3 deletions packages/cli/src/ui/views/PlanImplementationPrompt.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,25 @@ 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;
};

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) {
Expand All @@ -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<InputKey, "escape" | "return">,
Expand All @@ -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;
Expand Down Expand Up @@ -81,7 +92,7 @@ export function PlanImplementationPrompt({ onSelect }: Props): React.ReactElemen
))}
</Box>
<Box marginTop={1}>
<Text dimColor>1-3 select · ↑/↓ move · Enter select</Text>
<Text dimColor>1-4 select · ↑/↓ move · Enter select</Text>
</Box>
</Box>
);
Expand Down
Loading