refactor: remove --continue flag (now redundant) (#1458)

Co-authored-by: Letta Code <noreply@letta.com>
This commit is contained in:
Devansh Jain
2026-03-19 17:54:12 -07:00
committed by GitHub
parent 232e24940c
commit 8a505569fd
9 changed files with 7 additions and 290 deletions

View File

@@ -61,7 +61,6 @@ const SCENARIOS: ScenarioConfig[] = [
args: [ args: [
"-p", "-p",
"What is 3+3? Reply with just the number.", "What is 3+3? Reply with just the number.",
"--continue",
"--yolo", "--yolo",
"--output-format", "--output-format",
"json", "json",
@@ -73,7 +72,6 @@ const SCENARIOS: ScenarioConfig[] = [
args: [ args: [
"-p", "-p",
"What is 5+5? Reply with just the number.", "What is 5+5? Reply with just the number.",
"--continue",
"--yolo", "--yolo",
"--output-format", "--output-format",
"json", "json",

View File

@@ -340,7 +340,7 @@ const TOOL_CALL_COMMIT_DEFER_MS = 50;
const ANIMATION_RESUME_HYSTERESIS_ROWS = 2; const ANIMATION_RESUME_HYSTERESIS_ROWS = 2;
// Eager approval checking is now CONDITIONAL (LET-7101): // Eager approval checking is now CONDITIONAL (LET-7101):
// - Enabled when resuming a session (--resume, --continue, or startupApprovals exist) // - Enabled when resuming a session (--resume or startupApprovals exist)
// - Disabled for normal messages (lazy recovery handles edge cases) // - Disabled for normal messages (lazy recovery handles edge cases)
// This saves ~2s latency per message in the common case. // This saves ~2s latency per message in the common case.

View File

@@ -36,13 +36,6 @@ export const CLI_FLAG_CATALOG = {
mode: "both", mode: "both",
help: { description: "Show current directory, skills, and pinned agents" }, help: { description: "Show current directory, skills, and pinned agents" },
}, },
continue: {
parser: { type: "boolean", short: "c" },
mode: "both",
help: {
description: "Resume last session (agent + conversation) directly",
},
},
resume: { resume: {
parser: { type: "boolean", short: "r" }, parser: { type: "boolean", short: "r" },
mode: "interactive", mode: "interactive",

View File

@@ -367,7 +367,6 @@ export async function handleHeadlessCommand(
console.error( console.error(
"Error: --resume is for interactive mode only (opens conversation selector).\n" + "Error: --resume is for interactive mode only (opens conversation selector).\n" +
"In headless mode, use:\n" + "In headless mode, use:\n" +
" --continue Resume the last session (agent + conversation)\n" +
" --conversation <id> Resume a specific conversation by ID", " --conversation <id> Resume a specific conversation by ID",
); );
process.exit(1); process.exit(1);
@@ -382,7 +381,6 @@ export async function handleHeadlessCommand(
let specifiedAgentId = values.agent; let specifiedAgentId = values.agent;
const specifiedAgentName = values.name; const specifiedAgentName = values.name;
let specifiedConversationId = values.conversation; let specifiedConversationId = values.conversation;
const shouldContinue = values.continue;
const forceNew = values["new-agent"]; const forceNew = values["new-agent"];
const systemPromptPreset = values.system; const systemPromptPreset = values.system;
const systemCustom = values["system-custom"]; const systemCustom = values["system-custom"];
@@ -509,10 +507,6 @@ export async function handleHeadlessCommand(
); );
process.exit(1); process.exit(1);
} }
if (shouldContinue) {
console.error("Error: --from-agent cannot be used with --continue");
process.exit(1);
}
if (forceNew) { if (forceNew) {
console.error("Error: --from-agent cannot be used with --new-agent"); console.error("Error: --from-agent cannot be used with --new-agent");
process.exit(1); process.exit(1);
@@ -543,20 +537,12 @@ export async function handleHeadlessCommand(
when: fromAfFile, when: fromAfFile,
message: "--conversation cannot be used with --import", message: "--conversation cannot be used with --import",
}, },
{
when: shouldContinue,
message: "--conversation cannot be used with --continue",
},
], ],
}); });
validateFlagConflicts({ validateFlagConflicts({
guard: forceNewConversation, guard: forceNewConversation,
checks: [ checks: [
{
when: shouldContinue,
message: "--new cannot be used with --continue",
},
{ {
when: specifiedConversationId, when: specifiedConversationId,
message: "--new cannot be used with --conversation", message: "--new cannot be used with --conversation",
@@ -586,10 +572,6 @@ export async function handleHeadlessCommand(
when: specifiedAgentName, when: specifiedAgentName,
message: "--import cannot be used with --name", message: "--import cannot be used with --name",
}, },
{
when: shouldContinue,
message: "--import cannot be used with --continue",
},
{ {
when: forceNew, when: forceNew,
message: "--import cannot be used with --new-agent", message: "--import cannot be used with --new-agent",
@@ -860,14 +842,7 @@ export async function handleHeadlessCommand(
} }
} }
// Priority 6: --continue with no agent found → error // Priority 6: Fresh user with no LRU - create default agent
if (!agent && shouldContinue) {
console.error("No recent session found in .letta/ or ~/.letta.");
console.error("Run 'letta' to get started.");
process.exit(1);
}
// Priority 7: Fresh user with no LRU - create default agent
if (!agent) { if (!agent) {
const { ensureDefaultAgents } = await import("./agent/defaults"); const { ensureDefaultAgents } = await import("./agent/defaults");
const defaultAgent = await ensureDefaultAgents(client, { const defaultAgent = await ensureDefaultAgents(client, {
@@ -886,11 +861,7 @@ export async function handleHeadlessCommand(
markMilestone("HEADLESS_AGENT_RESOLVED"); markMilestone("HEADLESS_AGENT_RESOLVED");
// Check if we're resuming an existing agent (not creating a new one) // Check if we're resuming an existing agent (not creating a new one)
const isResumingAgent = !!( const isResumingAgent = !!(specifiedAgentId || (!forceNew && !fromAfFile));
specifiedAgentId ||
shouldContinue ||
(!forceNew && !fromAfFile)
);
// If resuming, always refresh model settings from presets to keep // If resuming, always refresh model settings from presets to keep
// preset-derived fields in sync, then apply optional command-line // preset-derived fields in sync, then apply optional command-line
@@ -1111,45 +1082,6 @@ export async function handleHeadlessCommand(
process.exit(1); process.exit(1);
} }
} }
} else if (shouldContinue) {
// Try to resume the last conversation for this agent
await settingsManager.loadLocalProjectSettings();
const lastSession =
settingsManager.getLocalLastSession(process.cwd()) ??
settingsManager.getGlobalLastSession();
if (lastSession && lastSession.agentId === agent.id) {
if (lastSession.conversationId === "default") {
// "default" is always valid - just use it directly
conversationId = "default";
} else {
// Verify the conversation still exists
try {
debugLog(
"conversations",
`retrieve(${lastSession.conversationId}) [headless lastSession resume]`,
);
await client.conversations.retrieve(lastSession.conversationId);
conversationId = lastSession.conversationId;
} catch {
// Conversation no longer exists - error with helpful message
console.error(
`Attempting to resume conversation ${lastSession.conversationId}, but conversation was not found.`,
);
console.error(
"Resume the default conversation with 'letta -p ...', view recent conversations with 'letta --resume', or start a new conversation with 'letta -p ... --new'.",
);
process.exit(1);
}
}
} else {
// No matching session - error with helpful message
console.error("No previous session found for this agent to resume.");
console.error(
"Resume the default conversation with 'letta -p ...', or start a new conversation with 'letta -p ... --new'.",
);
process.exit(1);
}
} else if (forceNewConversation) { } else if (forceNewConversation) {
// --new flag: create a new conversation (for concurrent sessions) // --new flag: create a new conversation (for concurrent sessions)
const conversation = await client.conversations.create({ const conversation = await client.conversations.create({

View File

@@ -68,7 +68,6 @@ USAGE
# interactive TUI # interactive TUI
letta Resume last conversation for this project letta Resume last conversation for this project
letta --new Create a new conversation (for concurrent sessions) letta --new Create a new conversation (for concurrent sessions)
letta --continue Resume last session (agent + conversation) directly
letta --resume Open agent selector UI to pick agent/conversation letta --resume Open agent selector UI to pick agent/conversation
letta --new-agent Create a new agent directly (skip profile selector) letta --new-agent Create a new agent directly (skip profile selector)
letta --agent <id> Open a specific agent by ID letta --agent <id> Open a specific agent by ID
@@ -452,8 +451,6 @@ async function main(): Promise<void> {
process.exit(result.success ? 0 : 1); process.exit(result.success ? 0 : 1);
} }
// --continue: Resume last session (agent + conversation) automatically
const shouldContinue = values.continue ?? false;
// --resume: Open agent selector UI after loading // --resume: Open agent selector UI after loading
const shouldResume = values.resume ?? false; const shouldResume = values.resume ?? false;
let specifiedConversationId = values.conversation ?? null; // Specific conversation to resume let specifiedConversationId = values.conversation ?? null; // Specific conversation to resume
@@ -655,20 +652,12 @@ async function main(): Promise<void> {
when: shouldResume, when: shouldResume,
message: "--conversation cannot be used with --resume", message: "--conversation cannot be used with --resume",
}, },
{
when: shouldContinue,
message: "--conversation cannot be used with --continue",
},
], ],
}); });
validateFlagConflicts({ validateFlagConflicts({
guard: forceNewConversation, guard: forceNewConversation,
checks: [ checks: [
{
when: shouldContinue,
message: "--new cannot be used with --continue",
},
{ {
when: specifiedConversationId, when: specifiedConversationId,
message: "--new cannot be used with --conversation", message: "--new cannot be used with --conversation",
@@ -957,7 +946,6 @@ async function main(): Promise<void> {
const App = AppModule.default; const App = AppModule.default;
function LoadingApp({ function LoadingApp({
continueSession,
forceNew, forceNew,
initBlocks, initBlocks,
baseTools, baseTools,
@@ -969,7 +957,6 @@ async function main(): Promise<void> {
fromAfFile, fromAfFile,
isRegistryImport, isRegistryImport,
}: { }: {
continueSession: boolean;
forceNew: boolean; forceNew: boolean;
initBlocks?: string[]; initBlocks?: string[];
baseTools?: string[]; baseTools?: string[];
@@ -1273,59 +1260,6 @@ async function main(): Promise<void> {
process.exit(1); process.exit(1);
} }
// =====================================================================
// TOP-LEVEL PATH: --continue
// Resume last session directly (local → global fallback)
// =====================================================================
if (continueSession) {
const localSession = settingsManager.getLocalLastSession(
process.cwd(),
);
const localAgentId = localSession?.agentId ?? localSettings.lastAgent;
// Try local LRU first
if (localAgentId) {
try {
await client.agents.retrieve(localAgentId);
setSelectedGlobalAgentId(localAgentId);
if (localSession?.conversationId) {
setSelectedConversationId(localSession.conversationId);
}
setLoadingState("assembling");
return;
} catch {
// Local agent doesn't exist, try global
setFailedAgentMessage(
`Unable to locate agent ${localAgentId} in .letta/, checking global (~/.letta)`,
);
}
} else {
console.log("No recent agent in .letta/, using global (~/.letta)");
}
// Try global LRU
const globalSession = settingsManager.getGlobalLastSession();
const globalAgentId = globalSession?.agentId;
if (globalAgentId) {
try {
await client.agents.retrieve(globalAgentId);
setSelectedGlobalAgentId(globalAgentId);
if (globalSession?.conversationId) {
setSelectedConversationId(globalSession.conversationId);
}
setLoadingState("assembling");
return;
} catch {
// Global agent also doesn't exist
}
}
// No valid agent found anywhere
console.error("No recent session found in .letta/ or ~/.letta.");
console.error("Run 'letta' to get started.");
process.exit(1);
}
// ===================================================================== // =====================================================================
// DEFAULT PATH: No special flags // DEFAULT PATH: No special flags
// Check local LRU → global LRU → selector → create default // Check local LRU → global LRU → selector → create default
@@ -1429,7 +1363,6 @@ async function main(): Promise<void> {
forceNew, forceNew,
agentIdArg, agentIdArg,
fromAfFile, fromAfFile,
continueSession,
shouldResume, shouldResume,
specifiedConversationId, specifiedConversationId,
]); ]);
@@ -1488,18 +1421,6 @@ async function main(): Promise<void> {
return; return;
} }
} }
// Priority 4: Try global settings if --continue flag
if (!resumingAgentId && continueSession && settings.lastAgent) {
try {
await client.agents.retrieve(settings.lastAgent);
resumingAgentId = settings.lastAgent;
} catch {
// Global agent doesn't exist - show selector
setLoadingState("selecting_global");
return;
}
}
} }
// Set resuming state early so loading messages are accurate // Set resuming state early so loading messages are accurate
@@ -1670,22 +1591,6 @@ async function main(): Promise<void> {
} }
} }
// Priority 6: Try to reuse global lastAgent if --continue flag is passed
// Note: If global lastAgent retrieval failed in early validation (with --continue),
// we already showed selector and returned. This is a safety fallback.
if (!agent && continueSession && settings.lastAgent) {
try {
agent = await client.agents.retrieve(settings.lastAgent);
} catch (error) {
// Agent disappeared - show selector instead of silently creating
console.error(
`Previous agent ${settings.lastAgent} not found (error: ${JSON.stringify(error)})`,
);
setLoadingState("selecting_global");
return;
}
}
// All paths should have resolved to an agent by now // All paths should have resolved to an agent by now
// If not, it's an unexpected state - error out instead of auto-creating // If not, it's an unexpected state - error out instead of auto-creating
if (!agent) { if (!agent) {
@@ -1724,14 +1629,12 @@ async function main(): Promise<void> {
// Check if we're resuming an existing agent // Check if we're resuming an existing agent
// We're resuming if: // We're resuming if:
// 1. We specified an agent ID via --agent flag (agentIdArg) // 1. We specified an agent ID via --agent flag (agentIdArg)
// 2. We used --resume flag (continueSession) // 2. We're reusing a project agent (detected early as resumingAgentId)
// 3. We're reusing a project agent (detected early as resumingAgentId) // 3. We retrieved an agent from LRU (detected by checking if agent already existed)
// 4. We retrieved an agent from LRU (detected by checking if agent already existed)
const isResumingProject = !shouldCreateNew && !!resumingAgentId; const isResumingProject = !shouldCreateNew && !!resumingAgentId;
const isReusingExistingAgent = const isReusingExistingAgent =
!shouldCreateNew && !fromAfFile && agent && agent.id; !shouldCreateNew && !fromAfFile && agent && agent.id;
const resuming = !!( const resuming = !!(
continueSession ||
agentIdArg || agentIdArg ||
isResumingProject || isResumingProject ||
isReusingExistingAgent isReusingExistingAgent
@@ -1829,7 +1732,6 @@ async function main(): Promise<void> {
// Debug: log resume flag status // Debug: log resume flag status
if (isDebugEnabled()) { if (isDebugEnabled()) {
debugLog("startup", "shouldContinue=%o", shouldContinue);
debugLog("startup", "shouldResume=%o", shouldResume); debugLog("startup", "shouldResume=%o", shouldResume);
debugLog( debugLog(
"startup", "startup",
@@ -1865,60 +1767,6 @@ async function main(): Promise<void> {
} }
throw error; throw error;
} }
} else if (shouldContinue) {
// Try to load the last session for this agent
const lastSession =
settingsManager.getLocalLastSession(process.cwd()) ??
settingsManager.getGlobalLastSession();
if (isDebugEnabled()) {
debugLog("startup", "lastSession=%s", JSON.stringify(lastSession));
debugLog("startup", "agent.id=%s", agent.id);
}
let resumedSuccessfully = false;
if (lastSession && lastSession.agentId === agent.id) {
// Try to resume the exact last conversation
// If it no longer exists, fall back to creating new
try {
// Load message history and pending approvals from the conversation
setLoadingState("checking");
const data = await getResumeData(
client,
agent,
lastSession.conversationId,
);
// Only set state after validation succeeds
conversationIdToUse = lastSession.conversationId;
setResumedExistingConversation(true);
setResumeData(data);
resumedSuccessfully = true;
} catch (error) {
// Only treat 404/422 as "not found", rethrow other errors
if (
error instanceof APIError &&
(error.status === 404 || error.status === 422)
) {
// Conversation no longer exists, will create new below
console.warn(
`Previous conversation ${lastSession.conversationId} not found, creating new`,
);
} else {
throw error;
}
}
}
if (!resumedSuccessfully) {
// No valid session to resume - error with helpful message
console.error(
`Attempting to resume conversation ${lastSession?.conversationId ?? "(unknown)"}, but conversation was not found.`,
);
console.error(
"Resume the default conversation with 'letta', view recent conversations with 'letta --resume', or start a new conversation with 'letta --new'.",
);
process.exit(1);
}
} else if (selectedConversationId) { } else if (selectedConversationId) {
// Conversation selected from --resume selector or auto-restored from local project settings // Conversation selected from --resume selector or auto-restored from local project settings
try { try {
@@ -2049,7 +1897,6 @@ async function main(): Promise<void> {
process.exit(1); process.exit(1);
}); });
}, [ }, [
continueSession,
forceNew, forceNew,
userRequestedNewAgent, userRequestedNewAgent,
agentIdArg, agentIdArg,
@@ -2059,7 +1906,6 @@ async function main(): Promise<void> {
loadingState, loadingState,
selectedGlobalAgentId, selectedGlobalAgentId,
validatedAgent, validatedAgent,
shouldContinue,
resumeAgentId, resumeAgentId,
selectedConversationId, selectedConversationId,
]); ]);
@@ -2182,7 +2028,6 @@ async function main(): Promise<void> {
markMilestone("REACT_RENDER_START"); markMilestone("REACT_RENDER_START");
render( render(
React.createElement(LoadingApp, { React.createElement(LoadingApp, {
continueSession: shouldContinue,
forceNew: forceNew, forceNew: forceNew,
initBlocks: initBlocks, initBlocks: initBlocks,
baseTools: baseTools, baseTools: baseTools,

View File

@@ -328,27 +328,3 @@ describe("Startup Flow - Integration", () => {
{ timeout: 190000 }, { timeout: 190000 },
); );
}); });
// ============================================================================
// --continue Tests (depend on LRU state, harder to isolate)
// ============================================================================
describe("Startup Flow - Continue Flag", () => {
test(
"--continue with no LRU shows error",
async () => {
const result = await runCli(
["--continue", "-p", "Say OK", "--output-format", "json"],
{
timeoutMs: 60000,
},
);
// Either succeeds (LRU exists) or fails with specific error
if (result.exitCode !== 0) {
expect(result.stderr).toContain("No recent session found");
}
},
{ timeout: 70000 },
);
});

View File

@@ -23,7 +23,7 @@ export const releaseNotes: Record<string, string> = {
→ Read more: https://docs.letta.com/letta-code/changelog#0134`, → Read more: https://docs.letta.com/letta-code/changelog#0134`,
"0.13.0": `🎁 **Letta Code 0.13.0: Introducing Conversations!** "0.13.0": `🎁 **Letta Code 0.13.0: Introducing Conversations!**
→ Letta Code now starts a new conversation on each startup (memory is shared across all conversations) → Letta Code now starts a new conversation on each startup (memory is shared across all conversations)
→ Use **/resume** to switch conversations, or run **letta --continue** to continue an existing conversation → Use **/resume** to switch conversations, or run **letta --conv <id>** to continue a specific conversation
→ Read more: https://docs.letta.com/letta-code/changelog#0130`, → Read more: https://docs.letta.com/letta-code/changelog#0130`,
}; };

View File

@@ -52,7 +52,6 @@ describe("shared CLI arg schema", () => {
test("rendered OPTIONS help is generated from catalog metadata", () => { test("rendered OPTIONS help is generated from catalog metadata", () => {
const help = renderCliOptionsHelp(); const help = renderCliOptionsHelp();
expect(help).toContain("-h, --help"); expect(help).toContain("-h, --help");
expect(help).toContain("-c, --continue");
expect(help).toContain("--memfs-startup <m>"); expect(help).toContain("--memfs-startup <m>");
expect(help).toContain("Default: text"); expect(help).toContain("Default: text");
expect(help).not.toContain("--run"); expect(help).not.toContain("--run");
@@ -139,18 +138,9 @@ describe("shared CLI arg schema", () => {
test("supports short aliases used by headless and interactive modes", () => { test("supports short aliases used by headless and interactive modes", () => {
const parsed = parseCliArgs( const parsed = parseCliArgs(
preprocessCliArgs([ preprocessCliArgs(["node", "script", "-p", "hello", "-C", "conv-123"]),
"node",
"script",
"-p",
"hello",
"-c",
"-C",
"conv-123",
]),
true, true,
); );
expect(parsed.values.continue).toBe(true);
expect(parsed.values.conversation).toBe("conv-123"); expect(parsed.values.conversation).toBe("conv-123");
}); });
}); });

View File

@@ -95,15 +95,6 @@ describe("Startup Flow - Flag Conflicts", () => {
); );
}); });
test("--conversation conflicts with --continue", async () => {
const result = await runCli(["--conversation", "conv-123", "--continue"], {
expectExit: 1,
});
expect(result.stderr).toContain(
"--conversation cannot be used with --continue",
);
});
test("--conversation conflicts with --import", async () => { test("--conversation conflicts with --import", async () => {
const result = await runCli( const result = await runCli(
["--conversation", "conv-123", "--import", "test.af"], ["--conversation", "conv-123", "--import", "test.af"],
@@ -197,14 +188,6 @@ describe("Startup Flow - Smoke", () => {
expect(result.stderr).not.toContain("Unknown option '--memfs-startup'"); expect(result.stderr).not.toContain("Unknown option '--memfs-startup'");
}); });
test("-c alias for --continue is accepted", async () => {
const result = await runCli(["-p", "Say OK", "-c"], {
expectExit: 1,
});
expect(result.stderr).toContain("Missing LETTA_API_KEY");
expect(result.stderr).not.toContain("Unknown option '-c'");
});
test("-C alias for --conversation is accepted", async () => { test("-C alias for --conversation is accepted", async () => {
const result = await runCli(["-p", "Say OK", "-C", "conv-123"], { const result = await runCli(["-p", "Say OK", "-C", "conv-123"], {
expectExit: 1, expectExit: 1,