fix(tui): auto-approve plan mode in YOLO (#1345)
This commit is contained in:
@@ -12451,13 +12451,23 @@ ${SYSTEM_REMINDER_CLOSE}
|
||||
const hasUsablePlan = planFileExists(fallbackPlanPath);
|
||||
|
||||
if (mode !== "plan") {
|
||||
if (hasUsablePlan) {
|
||||
if (mode === "bypassPermissions") {
|
||||
// User cycled to YOLO mode — auto-approve ExitPlanMode
|
||||
// so they don't need to manually click through the approval.
|
||||
if (hasUsablePlan) {
|
||||
// YOLO mode with a plan file — auto-approve ExitPlanMode.
|
||||
handlePlanApprove();
|
||||
return;
|
||||
}
|
||||
// YOLO mode but no plan file yet — tell agent to write it first.
|
||||
const planFilePath = activePlanPath ?? fallbackPlanPath;
|
||||
const plansDir = join(homedir(), ".letta", "plans");
|
||||
handlePlanKeepPlanning(
|
||||
`You must write your plan to a plan file before exiting plan mode.\n` +
|
||||
(planFilePath ? `Plan file path: ${planFilePath}\n` : "") +
|
||||
`Use a write tool to create your plan in ${plansDir}, then use ExitPlanMode to present the plan to the user.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (hasUsablePlan) {
|
||||
// Other modes: keep approval flow alive and let user manually approve.
|
||||
return;
|
||||
}
|
||||
@@ -12580,7 +12590,8 @@ ${SYSTEM_REMINDER_CLOSE}
|
||||
[pendingApprovals, approvalResults, sendAllResults, refreshDerived],
|
||||
);
|
||||
|
||||
const handleEnterPlanModeApprove = useCallback(async () => {
|
||||
const handleEnterPlanModeApprove = useCallback(
|
||||
async (preserveMode: boolean = false) => {
|
||||
const currentIndex = approvalResults.length;
|
||||
const approval = pendingApprovals[currentIndex];
|
||||
if (!approval) return;
|
||||
@@ -12594,11 +12605,15 @@ ${SYSTEM_REMINDER_CLOSE}
|
||||
planFilePath,
|
||||
).replace(/\\/g, "/");
|
||||
|
||||
// Toggle plan mode on and store plan file path
|
||||
permissionMode.setMode("plan");
|
||||
// Store plan file path
|
||||
permissionMode.setPlanFilePath(planFilePath);
|
||||
cacheLastPlanFilePath(planFilePath);
|
||||
|
||||
if (!preserveMode) {
|
||||
// Normal flow: switch to plan mode
|
||||
permissionMode.setMode("plan");
|
||||
setUiPermissionMode("plan");
|
||||
}
|
||||
|
||||
// Get the tool return message from the implementation
|
||||
const toolReturn = `Entered plan mode. You should now focus on exploring the codebase and designing an implementation approach.
|
||||
@@ -12648,14 +12663,16 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
|
||||
} else {
|
||||
setApprovalResults((prev) => [...prev, decision]);
|
||||
}
|
||||
}, [
|
||||
},
|
||||
[
|
||||
pendingApprovals,
|
||||
approvalResults,
|
||||
sendAllResults,
|
||||
refreshDerived,
|
||||
setUiPermissionMode,
|
||||
cacheLastPlanFilePath,
|
||||
]);
|
||||
],
|
||||
);
|
||||
|
||||
const handleEnterPlanModeReject = useCallback(async () => {
|
||||
const currentIndex = approvalResults.length;
|
||||
@@ -12681,6 +12698,20 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
|
||||
}
|
||||
}, [pendingApprovals, approvalResults, sendAllResults]);
|
||||
|
||||
// Guard EnterPlanMode:
|
||||
// When in bypassPermissions (YOLO) mode, auto-approve EnterPlanMode and stay
|
||||
// in YOLO — the agent gets plan instructions but keeps full permissions.
|
||||
// The existing ExitPlanMode guard then auto-approves the exit too.
|
||||
useEffect(() => {
|
||||
const currentIndex = approvalResults.length;
|
||||
const approval = pendingApprovals[currentIndex];
|
||||
if (approval?.toolName === "EnterPlanMode") {
|
||||
if (permissionMode.getMode() === "bypassPermissions") {
|
||||
handleEnterPlanModeApprove(true);
|
||||
}
|
||||
}
|
||||
}, [pendingApprovals, approvalResults.length, handleEnterPlanModeApprove]);
|
||||
|
||||
// Live area shows only in-progress items
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: staticItems.length and deferredCommitAt are intentional triggers to recompute when items are promoted to static or deferred commits complete
|
||||
const liveItems = useMemo(() => {
|
||||
|
||||
@@ -60,7 +60,7 @@ describe("permission mode retry wiring", () => {
|
||||
);
|
||||
|
||||
const enterPlanStart = source.indexOf(
|
||||
"const handleEnterPlanModeApprove = useCallback(async () => {",
|
||||
"const handleEnterPlanModeApprove = useCallback(",
|
||||
);
|
||||
const enterPlanEnd = source.indexOf(
|
||||
"const handleEnterPlanModeReject = useCallback(async () => {",
|
||||
@@ -133,6 +133,44 @@ describe("permission mode retry wiring", () => {
|
||||
expect(segment).toContain("continue;");
|
||||
});
|
||||
|
||||
test("handleEnterPlanModeApprove supports preserveMode to stay in YOLO", () => {
|
||||
const source = readAppSource();
|
||||
|
||||
const start = source.indexOf(
|
||||
"const handleEnterPlanModeApprove = useCallback(",
|
||||
);
|
||||
const end = source.indexOf(
|
||||
"const handleEnterPlanModeReject = useCallback(async () => {",
|
||||
);
|
||||
expect(start).toBeGreaterThan(-1);
|
||||
expect(end).toBeGreaterThan(start);
|
||||
|
||||
const segment = source.slice(start, end);
|
||||
expect(segment).toContain("preserveMode: boolean = false");
|
||||
expect(segment).toContain("if (!preserveMode)");
|
||||
expect(segment).toContain('permissionMode.setMode("plan")');
|
||||
});
|
||||
|
||||
test("auto-approves EnterPlanMode in bypassPermissions mode", () => {
|
||||
const source = readAppSource();
|
||||
|
||||
const guardStart = source.indexOf("Guard EnterPlanMode:");
|
||||
expect(guardStart).toBeGreaterThan(-1);
|
||||
|
||||
const guardEnd = source.indexOf(
|
||||
"// Live area shows only in-progress items",
|
||||
guardStart,
|
||||
);
|
||||
expect(guardEnd).toBeGreaterThan(guardStart);
|
||||
|
||||
const segment = source.slice(guardStart, guardEnd);
|
||||
expect(segment).toContain('approval?.toolName === "EnterPlanMode"');
|
||||
expect(segment).toContain(
|
||||
'permissionMode.getMode() === "bypassPermissions"',
|
||||
);
|
||||
expect(segment).toContain("handleEnterPlanModeApprove(true)");
|
||||
});
|
||||
|
||||
test("preserves saved plan path when approving ExitPlanMode after mode cycling", () => {
|
||||
const source = readAppSource();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user