fix(tui): gracefully continue ExitPlanMode after mode cycling (#1308)
Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
@@ -746,14 +746,14 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if plan file exists
|
// Check if plan file exists
|
||||||
function planFileExists(): boolean {
|
function planFileExists(fallbackPlanFilePath?: string | null): boolean {
|
||||||
const planFilePath = permissionMode.getPlanFilePath();
|
const planFilePath = permissionMode.getPlanFilePath() ?? fallbackPlanFilePath;
|
||||||
return !!planFilePath && existsSync(planFilePath);
|
return !!planFilePath && existsSync(planFilePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read plan content from the plan file
|
// Read plan content from the plan file
|
||||||
function _readPlanFile(): string {
|
function _readPlanFile(fallbackPlanFilePath?: string | null): string {
|
||||||
const planFilePath = permissionMode.getPlanFilePath();
|
const planFilePath = permissionMode.getPlanFilePath() ?? fallbackPlanFilePath;
|
||||||
if (!planFilePath) {
|
if (!planFilePath) {
|
||||||
return "No plan file path set.";
|
return "No plan file path set.";
|
||||||
}
|
}
|
||||||
@@ -12467,18 +12467,37 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
[pendingApprovals, approvalResults, sendAllResults],
|
[pendingApprovals, approvalResults, sendAllResults],
|
||||||
);
|
);
|
||||||
|
|
||||||
// Auto-reject ExitPlanMode if plan mode is not enabled or plan file doesn't exist
|
// Guard ExitPlanMode:
|
||||||
|
// - If not in plan mode, allow graceful continuation when we still have a known plan file path
|
||||||
|
// - Otherwise reject with an expiry message
|
||||||
|
// - If in plan mode but no plan file exists, keep planning
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const currentIndex = approvalResults.length;
|
const currentIndex = approvalResults.length;
|
||||||
const approval = pendingApprovals[currentIndex];
|
const approval = pendingApprovals[currentIndex];
|
||||||
if (approval?.toolName === "ExitPlanMode") {
|
if (approval?.toolName === "ExitPlanMode") {
|
||||||
// First check if plan mode is enabled
|
const mode = permissionMode.getMode();
|
||||||
if (permissionMode.getMode() !== "plan") {
|
const activePlanPath = permissionMode.getPlanFilePath();
|
||||||
// Plan mode state was lost (e.g., CLI restart) - queue rejection with helpful message
|
const fallbackPlanPath = lastPlanFilePathRef.current;
|
||||||
// This is different from immediate rejection because we want the user to see what happened
|
const hasUsablePlan = planFileExists(fallbackPlanPath);
|
||||||
// and be able to type their next message
|
|
||||||
|
|
||||||
// Add status message to explain what happened
|
if (mode !== "plan") {
|
||||||
|
if (hasUsablePlan) {
|
||||||
|
// User likely cycled out of plan mode (e.g., Shift+Tab to acceptEdits/yolo)
|
||||||
|
// Keep approval flow alive and let ExitPlanMode proceed using fallback plan path.
|
||||||
|
const statusId = uid("status");
|
||||||
|
buffersRef.current.byId.set(statusId, {
|
||||||
|
kind: "status",
|
||||||
|
id: statusId,
|
||||||
|
lines: [
|
||||||
|
"ℹ️ Plan mode switched, continuing ExitPlanMode with saved plan file",
|
||||||
|
],
|
||||||
|
});
|
||||||
|
buffersRef.current.order.push(statusId);
|
||||||
|
refreshDerived();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plan mode state was lost and no plan file is recoverable (e.g., CLI restart)
|
||||||
const statusId = uid("status");
|
const statusId = uid("status");
|
||||||
buffersRef.current.byId.set(statusId, {
|
buffersRef.current.byId.set(statusId, {
|
||||||
kind: "status",
|
kind: "status",
|
||||||
@@ -12494,7 +12513,7 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
tool_call_id: approval.toolCallId,
|
tool_call_id: approval.toolCallId,
|
||||||
approve: false,
|
approve: false,
|
||||||
reason:
|
reason:
|
||||||
"Plan mode session expired (CLI restarted). Use EnterPlanMode to re-enter plan mode, or request the user to re-enter plan mode.",
|
"Plan mode session expired (CLI restarted or no recoverable plan file). Use EnterPlanMode to re-enter plan mode, or request the user to re-enter plan mode.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
queueApprovalResults(denialResults);
|
queueApprovalResults(denialResults);
|
||||||
@@ -12515,10 +12534,10 @@ ${SYSTEM_REMINDER_CLOSE}
|
|||||||
setAutoDeniedApprovals([]);
|
setAutoDeniedApprovals([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Then check if plan file exists (keep existing behavior - immediate rejection)
|
|
||||||
// This case means plan mode IS active, but agent forgot to write the plan file
|
// Mode is plan: require an existing plan file (active or fallback)
|
||||||
if (!planFileExists()) {
|
if (!hasUsablePlan) {
|
||||||
const planFilePath = permissionMode.getPlanFilePath();
|
const planFilePath = activePlanPath ?? fallbackPlanPath;
|
||||||
const plansDir = join(homedir(), ".letta", "plans");
|
const plansDir = join(homedir(), ".letta", "plans");
|
||||||
handlePlanKeepPlanning(
|
handlePlanKeepPlanning(
|
||||||
`You must write your plan to a plan file before exiting plan mode.\n` +
|
`You must write your plan to a plan file before exiting plan mode.\n` +
|
||||||
@@ -13116,12 +13135,13 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
|
|||||||
showPreview={showApprovalPreview}
|
showPreview={showApprovalPreview}
|
||||||
planContent={
|
planContent={
|
||||||
currentApproval.toolName === "ExitPlanMode"
|
currentApproval.toolName === "ExitPlanMode"
|
||||||
? _readPlanFile()
|
? _readPlanFile(lastPlanFilePathRef.current)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
planFilePath={
|
planFilePath={
|
||||||
currentApproval.toolName === "ExitPlanMode"
|
currentApproval.toolName === "ExitPlanMode"
|
||||||
? (permissionMode.getPlanFilePath() ??
|
? (permissionMode.getPlanFilePath() ??
|
||||||
|
lastPlanFilePathRef.current ??
|
||||||
undefined)
|
undefined)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
@@ -13212,12 +13232,14 @@ If using apply_patch, use this exact relative patch path: ${applyPatchRelativePa
|
|||||||
showPreview={showApprovalPreview}
|
showPreview={showApprovalPreview}
|
||||||
planContent={
|
planContent={
|
||||||
currentApproval.toolName === "ExitPlanMode"
|
currentApproval.toolName === "ExitPlanMode"
|
||||||
? _readPlanFile()
|
? _readPlanFile(lastPlanFilePathRef.current)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
planFilePath={
|
planFilePath={
|
||||||
currentApproval.toolName === "ExitPlanMode"
|
currentApproval.toolName === "ExitPlanMode"
|
||||||
? (permissionMode.getPlanFilePath() ?? undefined)
|
? (permissionMode.getPlanFilePath() ??
|
||||||
|
lastPlanFilePathRef.current ??
|
||||||
|
undefined)
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
agentName={agentName ?? undefined}
|
agentName={agentName ?? undefined}
|
||||||
|
|||||||
Reference in New Issue
Block a user