diff --git a/scripts/latency-benchmark.ts b/scripts/latency-benchmark.ts index b6aec9f..559dd34 100644 --- a/scripts/latency-benchmark.ts +++ b/scripts/latency-benchmark.ts @@ -61,7 +61,6 @@ const SCENARIOS: ScenarioConfig[] = [ args: [ "-p", "What is 3+3? Reply with just the number.", - "--continue", "--yolo", "--output-format", "json", @@ -73,7 +72,6 @@ const SCENARIOS: ScenarioConfig[] = [ args: [ "-p", "What is 5+5? Reply with just the number.", - "--continue", "--yolo", "--output-format", "json", diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 23f0776..57f913b 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -340,7 +340,7 @@ const TOOL_CALL_COMMIT_DEFER_MS = 50; const ANIMATION_RESUME_HYSTERESIS_ROWS = 2; // 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) // This saves ~2s latency per message in the common case. diff --git a/src/cli/args.ts b/src/cli/args.ts index 5a45dce..36a12f4 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -36,13 +36,6 @@ export const CLI_FLAG_CATALOG = { mode: "both", 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: { parser: { type: "boolean", short: "r" }, mode: "interactive", diff --git a/src/headless.ts b/src/headless.ts index d2d200f..14a52be 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -367,7 +367,6 @@ export async function handleHeadlessCommand( console.error( "Error: --resume is for interactive mode only (opens conversation selector).\n" + "In headless mode, use:\n" + - " --continue Resume the last session (agent + conversation)\n" + " --conversation Resume a specific conversation by ID", ); process.exit(1); @@ -382,7 +381,6 @@ export async function handleHeadlessCommand( let specifiedAgentId = values.agent; const specifiedAgentName = values.name; let specifiedConversationId = values.conversation; - const shouldContinue = values.continue; const forceNew = values["new-agent"]; const systemPromptPreset = values.system; const systemCustom = values["system-custom"]; @@ -509,10 +507,6 @@ export async function handleHeadlessCommand( ); process.exit(1); } - if (shouldContinue) { - console.error("Error: --from-agent cannot be used with --continue"); - process.exit(1); - } if (forceNew) { console.error("Error: --from-agent cannot be used with --new-agent"); process.exit(1); @@ -543,20 +537,12 @@ export async function handleHeadlessCommand( when: fromAfFile, message: "--conversation cannot be used with --import", }, - { - when: shouldContinue, - message: "--conversation cannot be used with --continue", - }, ], }); validateFlagConflicts({ guard: forceNewConversation, checks: [ - { - when: shouldContinue, - message: "--new cannot be used with --continue", - }, { when: specifiedConversationId, message: "--new cannot be used with --conversation", @@ -586,10 +572,6 @@ export async function handleHeadlessCommand( when: specifiedAgentName, message: "--import cannot be used with --name", }, - { - when: shouldContinue, - message: "--import cannot be used with --continue", - }, { when: forceNew, message: "--import cannot be used with --new-agent", @@ -860,14 +842,7 @@ export async function handleHeadlessCommand( } } - // Priority 6: --continue with no agent found → error - 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 + // Priority 6: Fresh user with no LRU - create default agent if (!agent) { const { ensureDefaultAgents } = await import("./agent/defaults"); const defaultAgent = await ensureDefaultAgents(client, { @@ -886,11 +861,7 @@ export async function handleHeadlessCommand( markMilestone("HEADLESS_AGENT_RESOLVED"); // Check if we're resuming an existing agent (not creating a new one) - const isResumingAgent = !!( - specifiedAgentId || - shouldContinue || - (!forceNew && !fromAfFile) - ); + const isResumingAgent = !!(specifiedAgentId || (!forceNew && !fromAfFile)); // If resuming, always refresh model settings from presets to keep // preset-derived fields in sync, then apply optional command-line @@ -1111,45 +1082,6 @@ export async function handleHeadlessCommand( 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) { // --new flag: create a new conversation (for concurrent sessions) const conversation = await client.conversations.create({ diff --git a/src/index.ts b/src/index.ts index b9b755a..4701b5c 100755 --- a/src/index.ts +++ b/src/index.ts @@ -68,7 +68,6 @@ USAGE # interactive TUI letta Resume last conversation for this project 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 --new-agent Create a new agent directly (skip profile selector) letta --agent Open a specific agent by ID @@ -452,8 +451,6 @@ async function main(): Promise { 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 const shouldResume = values.resume ?? false; let specifiedConversationId = values.conversation ?? null; // Specific conversation to resume @@ -655,20 +652,12 @@ async function main(): Promise { when: shouldResume, message: "--conversation cannot be used with --resume", }, - { - when: shouldContinue, - message: "--conversation cannot be used with --continue", - }, ], }); validateFlagConflicts({ guard: forceNewConversation, checks: [ - { - when: shouldContinue, - message: "--new cannot be used with --continue", - }, { when: specifiedConversationId, message: "--new cannot be used with --conversation", @@ -957,7 +946,6 @@ async function main(): Promise { const App = AppModule.default; function LoadingApp({ - continueSession, forceNew, initBlocks, baseTools, @@ -969,7 +957,6 @@ async function main(): Promise { fromAfFile, isRegistryImport, }: { - continueSession: boolean; forceNew: boolean; initBlocks?: string[]; baseTools?: string[]; @@ -1273,59 +1260,6 @@ async function main(): Promise { 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 // Check local LRU → global LRU → selector → create default @@ -1429,7 +1363,6 @@ async function main(): Promise { forceNew, agentIdArg, fromAfFile, - continueSession, shouldResume, specifiedConversationId, ]); @@ -1488,18 +1421,6 @@ async function main(): Promise { 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 @@ -1670,22 +1591,6 @@ async function main(): Promise { } } - // 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 // If not, it's an unexpected state - error out instead of auto-creating if (!agent) { @@ -1724,14 +1629,12 @@ async function main(): Promise { // Check if we're resuming an existing agent // We're resuming if: // 1. We specified an agent ID via --agent flag (agentIdArg) - // 2. We used --resume flag (continueSession) - // 3. We're reusing a project agent (detected early as resumingAgentId) - // 4. We retrieved an agent from LRU (detected by checking if agent already existed) + // 2. We're reusing a project agent (detected early as resumingAgentId) + // 3. We retrieved an agent from LRU (detected by checking if agent already existed) const isResumingProject = !shouldCreateNew && !!resumingAgentId; const isReusingExistingAgent = !shouldCreateNew && !fromAfFile && agent && agent.id; const resuming = !!( - continueSession || agentIdArg || isResumingProject || isReusingExistingAgent @@ -1829,7 +1732,6 @@ async function main(): Promise { // Debug: log resume flag status if (isDebugEnabled()) { - debugLog("startup", "shouldContinue=%o", shouldContinue); debugLog("startup", "shouldResume=%o", shouldResume); debugLog( "startup", @@ -1865,60 +1767,6 @@ async function main(): Promise { } 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) { // Conversation selected from --resume selector or auto-restored from local project settings try { @@ -2049,7 +1897,6 @@ async function main(): Promise { process.exit(1); }); }, [ - continueSession, forceNew, userRequestedNewAgent, agentIdArg, @@ -2059,7 +1906,6 @@ async function main(): Promise { loadingState, selectedGlobalAgentId, validatedAgent, - shouldContinue, resumeAgentId, selectedConversationId, ]); @@ -2182,7 +2028,6 @@ async function main(): Promise { markMilestone("REACT_RENDER_START"); render( React.createElement(LoadingApp, { - continueSession: shouldContinue, forceNew: forceNew, initBlocks: initBlocks, baseTools: baseTools, diff --git a/src/integration-tests/startup-flow.integration.test.ts b/src/integration-tests/startup-flow.integration.test.ts index 33ebbb2..ed47ef2 100644 --- a/src/integration-tests/startup-flow.integration.test.ts +++ b/src/integration-tests/startup-flow.integration.test.ts @@ -328,27 +328,3 @@ describe("Startup Flow - Integration", () => { { 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 }, - ); -}); diff --git a/src/release-notes.ts b/src/release-notes.ts index bc572a3..ed9a718 100644 --- a/src/release-notes.ts +++ b/src/release-notes.ts @@ -23,7 +23,7 @@ export const releaseNotes: Record = { → Read more: https://docs.letta.com/letta-code/changelog#0134`, "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) -→ Use **/resume** to switch conversations, or run **letta --continue** to continue an existing conversation +→ Use **/resume** to switch conversations, or run **letta --conv ** to continue a specific conversation → Read more: https://docs.letta.com/letta-code/changelog#0130`, }; diff --git a/src/tests/cli/args.test.ts b/src/tests/cli/args.test.ts index e4fd7be..b52dfc1 100644 --- a/src/tests/cli/args.test.ts +++ b/src/tests/cli/args.test.ts @@ -52,7 +52,6 @@ describe("shared CLI arg schema", () => { test("rendered OPTIONS help is generated from catalog metadata", () => { const help = renderCliOptionsHelp(); expect(help).toContain("-h, --help"); - expect(help).toContain("-c, --continue"); expect(help).toContain("--memfs-startup "); expect(help).toContain("Default: text"); expect(help).not.toContain("--run"); @@ -139,18 +138,9 @@ describe("shared CLI arg schema", () => { test("supports short aliases used by headless and interactive modes", () => { const parsed = parseCliArgs( - preprocessCliArgs([ - "node", - "script", - "-p", - "hello", - "-c", - "-C", - "conv-123", - ]), + preprocessCliArgs(["node", "script", "-p", "hello", "-C", "conv-123"]), true, ); - expect(parsed.values.continue).toBe(true); expect(parsed.values.conversation).toBe("conv-123"); }); }); diff --git a/src/tests/startup-flow.test.ts b/src/tests/startup-flow.test.ts index ed7e61f..6678600 100644 --- a/src/tests/startup-flow.test.ts +++ b/src/tests/startup-flow.test.ts @@ -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 () => { const result = await runCli( ["--conversation", "conv-123", "--import", "test.af"], @@ -197,14 +188,6 @@ describe("Startup Flow - Smoke", () => { 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 () => { const result = await runCli(["-p", "Say OK", "-C", "conv-123"], { expectExit: 1,