fix: patch plan mode (#211)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2025-12-14 21:11:42 -08:00
committed by GitHub
parent 078fcf5476
commit cd270a938d
4 changed files with 32 additions and 4 deletions

View File

@@ -156,6 +156,12 @@ NOTE: At any point in time through this workflow you should feel free to ask the
`;
}
// Check if plan file exists
function planFileExists(): boolean {
const planFilePath = permissionMode.getPlanFilePath();
return !!planFilePath && existsSync(planFilePath);
}
// Read plan content from the plan file
function readPlanFile(): string {
const planFilePath = permissionMode.getPlanFilePath();
@@ -825,8 +831,9 @@ export default function App({
// Check permissions for all approvals (including fancy UI tools)
const approvalResults = await Promise.all(
approvalsToProcess.map(async (approvalItem) => {
// Check if approval is incomplete (missing name or arguments)
if (!approvalItem.toolName || !approvalItem.toolArgs) {
// Check if approval is incomplete (missing name)
// Note: toolArgs can be empty string for tools with no arguments (e.g., EnterPlanMode)
if (!approvalItem.toolName) {
return {
approval: approvalItem,
permission: {
@@ -3251,6 +3258,20 @@ ${recentCommits}
[pendingApprovals, approvalResults, sendAllResults],
);
// Auto-reject ExitPlanMode if plan file doesn't exist
useEffect(() => {
const currentIndex = approvalResults.length;
const approval = pendingApprovals[currentIndex];
if (approval?.toolName === "ExitPlanMode" && !planFileExists()) {
const planFilePath = permissionMode.getPlanFilePath();
handlePlanKeepPlanning(
`You must write your plan to the plan file before exiting plan mode.\n` +
`Plan file path: ${planFilePath || "not set"}\n` +
`Use the Write tool to create your plan, then call ExitPlanMode again.`,
);
}
}, [pendingApprovals, approvalResults.length, handlePlanKeepPlanning]);
const handleQuestionSubmit = useCallback(
async (answers: Record<string, string>) => {
const currentIndex = approvalResults.length;

View File

@@ -272,8 +272,9 @@ export const ToolCallMessage = memo(({ line }: { line: ToolCallLine }) => {
// Format tool denial errors more user-friendly
if (isError && displayText.includes("request to call tool denied")) {
const match = displayText.match(/User reason: (.+)$/);
const reason = match ? match[1] : "(empty)";
// Use [\s\S]+ to match multiline reasons
const match = displayText.match(/User reason: ([\s\S]+)$/);
const reason = match?.[1]?.trim() || "(empty)";
displayText = `User rejected the tool call with reason: ${reason}`;
}

View File

@@ -124,6 +124,11 @@ class PermissionModeManager {
"Grep",
"NotebookRead",
"TodoWrite",
// Plan mode tools (must allow exit!)
"ExitPlanMode",
"exit_plan_mode",
"AskUserQuestion",
"ask_user_question",
// Codex toolset (snake_case)
"read_file",
"list_dir",

View File

@@ -6,6 +6,7 @@
export async function exit_plan_mode(): Promise<{ message: string }> {
// Return confirmation message that plan was approved
// Note: The plan is read from the plan file by the UI before this return is shown
// The UI layer checks if the plan file exists and auto-rejects if not
return {
message:
"User has approved your plan. You can now start coding.\nStart with updating your todo list if applicable",