From 78fdb23d0048fe9dc4af1d3ffbb1a86a097dd600 Mon Sep 17 00:00:00 2001 From: Devansh Jain <31609257+devanshrj@users.noreply.github.com> Date: Fri, 5 Dec 2025 12:36:26 -0800 Subject: [PATCH] fix: Fixed status line and updated welcome screen (#156) --- src/cli/App.tsx | 111 ++----------- src/cli/components/WelcomeScreen.tsx | 226 ++++++++++++++++----------- 2 files changed, 145 insertions(+), 192 deletions(-) diff --git a/src/cli/App.tsx b/src/cli/App.tsx index 7966155..d5b64a2 100644 --- a/src/cli/App.tsx +++ b/src/cli/App.tsx @@ -201,74 +201,6 @@ function getSkillUnloadReminder(): string { return ""; } -// Generate status lines based on agent provenance -function generateStatusLines( - continueSession: boolean, - agentProvenance: AgentProvenance | null, - agentState?: AgentState | null, -): string[] { - const lines: string[] = []; - - // For resumed agents - if (continueSession) { - lines.push(`Resumed existing agent (${agentState?.id})`); - - // Show attached blocks if available - if (agentState?.memory?.blocks) { - const labels = agentState.memory.blocks - .map((b) => b.label) - .filter(Boolean) - .join(", "); - if (labels) { - lines.push(` → Memory blocks: ${labels}`); - } - } - - lines.push(" → To create a new agent, use --new"); - return lines; - } - - // For new agents with provenance - if (agentProvenance) { - if (agentProvenance.freshBlocks) { - lines.push(`Created new agent (${agentState?.id})`); - const allLabels = agentProvenance.blocks.map((b) => b.label).join(", "); - if (allLabels) { - lines.push(` → Created new memory blocks: ${allLabels}`); - } - } else { - lines.push(`Created new agent (${agentState?.id})`); - - // Group blocks by source - const globalBlocks = agentProvenance.blocks - .filter((b) => b.source === "global") - .map((b) => b.label); - const projectBlocks = agentProvenance.blocks - .filter((b) => b.source === "project") - .map((b) => b.label); - const newBlocks = agentProvenance.blocks - .filter((b) => b.source === "new") - .map((b) => b.label); - - if (globalBlocks.length > 0) { - lines.push( - ` → Reusing from global (~/.letta/): ${globalBlocks.join(", ")}`, - ); - } - if (projectBlocks.length > 0) { - lines.push( - ` → Reusing from project (.letta/): ${projectBlocks.join(", ")}`, - ); - } - if (newBlocks.length > 0) { - lines.push(` → Created new blocks: ${newBlocks.join(", ")}`); - } - } - } - - return lines; -} - // Items that have finished rendering and no longer change type StaticItem = | { @@ -277,6 +209,7 @@ type StaticItem = snapshot: { continueSession: boolean; agentState?: AgentState | null; + agentProvenance?: AgentProvenance | null; terminalWidth: number; }; } @@ -576,6 +509,7 @@ export default function App({ snapshot: { continueSession, agentState, + agentProvenance, terminalWidth: columns, }, }, @@ -604,22 +538,6 @@ export default function App({ // Insert at the beginning of the order array buffersRef.current.order.unshift(backfillStatusId); - // Inject provenance status line at the END of the backfilled history - const statusLines = generateStatusLines( - continueSession, - agentProvenance, - agentState, - ); - if (statusLines.length > 0) { - const statusId = `status-${Date.now().toString(36)}`; - buffersRef.current.byId.set(statusId, { - kind: "status", - id: statusId, - lines: statusLines, - }); - buffersRef.current.order.push(statusId); - } - refreshDerived(); commitEligibleLines(buffersRef.current); } @@ -2834,12 +2752,18 @@ Plan file path: ${planFilePath}`; }, [lines, tokenStreamingEnabled]); // Commit welcome snapshot once when ready for fresh sessions (no history) + // Wait for agentProvenance to be available for new agents (continueSession=false) useEffect(() => { if ( loadingState === "ready" && !welcomeCommittedRef.current && messageHistory.length === 0 ) { + // For new agents, wait until provenance is available + // For resumed agents, provenance stays null (that's expected) + if (!continueSession && !agentProvenance) { + return; // Wait for provenance to be set + } welcomeCommittedRef.current = true; setStaticItems((prev) => [ ...prev, @@ -2849,27 +2773,11 @@ Plan file path: ${planFilePath}`; snapshot: { continueSession, agentState, + agentProvenance, terminalWidth: columns, }, }, ]); - - // Inject status line for fresh sessions - const statusLines = generateStatusLines( - continueSession, - agentProvenance, - agentState, - ); - if (statusLines.length > 0) { - const statusId = `status-${Date.now().toString(36)}`; - buffersRef.current.byId.set(statusId, { - kind: "status", - id: statusId, - lines: statusLines, - }); - buffersRef.current.order.push(statusId); - refreshDerived(); - } } }, [ loadingState, @@ -2878,7 +2786,6 @@ Plan file path: ${planFilePath}`; columns, agentProvenance, agentState, - refreshDerived, ]); return ( diff --git a/src/cli/components/WelcomeScreen.tsx b/src/cli/components/WelcomeScreen.tsx index fc7e50b..48af605 100644 --- a/src/cli/components/WelcomeScreen.tsx +++ b/src/cli/components/WelcomeScreen.tsx @@ -1,6 +1,7 @@ import type { Letta } from "@letta-ai/letta-client"; import { Box, Text } from "ink"; import Link from "ink-link"; +import type { AgentProvenance } from "../../agent/create"; import { getVersion } from "../../version"; import { useTerminalWidth } from "../hooks/useTerminalWidth"; import { asciiLogo } from "./AsciiArt"; @@ -15,15 +16,92 @@ type LoadingState = | "checking" | "ready"; +/** + * Generate status hints based on session type and block provenance. + * Pure function - no React dependencies. + */ +export function getAgentStatusHints( + continueSession: boolean, + agentState?: Letta.AgentState | null, + agentProvenance?: AgentProvenance | null, +): string[] { + const hints: string[] = []; + + // For resumed agents, show memory blocks and --new hint + if (continueSession) { + if (agentState?.memory?.blocks) { + const blocks = agentState.memory.blocks; + const count = blocks.length; + const labels = blocks + .map((b) => b.label) + .filter(Boolean) + .join(", "); + if (labels) { + hints.push( + `→ Attached ${count} memory block${count !== 1 ? "s" : ""}: ${labels}`, + ); + } + } + hints.push("→ To create a new agent, use --new"); + return hints; + } + + // For new agents with provenance, show block sources + if (agentProvenance) { + // Blocks reused from existing storage + const reusedGlobalBlocks = agentProvenance.blocks + .filter((b) => b.source === "global") + .map((b) => b.label); + const reusedProjectBlocks = agentProvenance.blocks + .filter((b) => b.source === "project") + .map((b) => b.label); + + // New blocks - categorize by where they'll be stored + // (project/skills → .letta/, others → ~/.letta/) + const newBlocks = agentProvenance.blocks.filter((b) => b.source === "new"); + const newGlobalBlocks = newBlocks + .filter((b) => b.label !== "project" && b.label !== "skills") + .map((b) => b.label); + const newProjectBlocks = newBlocks + .filter((b) => b.label === "project" || b.label === "skills") + .map((b) => b.label); + + if (reusedGlobalBlocks.length > 0) { + hints.push( + `→ Reusing from global (~/.letta/): ${reusedGlobalBlocks.join(", ")}`, + ); + } + if (newGlobalBlocks.length > 0) { + hints.push( + `→ Created in global (~/.letta/): ${newGlobalBlocks.join(", ")}`, + ); + } + if (reusedProjectBlocks.length > 0) { + hints.push( + `→ Reusing from project (.letta/): ${reusedProjectBlocks.join(", ")}`, + ); + } + if (newProjectBlocks.length > 0) { + hints.push( + `→ Created in project (.letta/): ${newProjectBlocks.join(", ")}`, + ); + } + } + + return hints; +} + export function WelcomeScreen({ loadingState, continueSession, agentState, + agentProvenance, terminalWidth: frozenWidth, }: { loadingState: LoadingState; continueSession?: boolean; agentState?: Letta.AgentState | null; + agentProvenance?: AgentProvenance | null; terminalWidth?: number; }) { const currentWidth = useTerminalWidth(); @@ -32,94 +110,20 @@ export function WelcomeScreen({ const version = getVersion(); const agentId = agentState?.id; - // Split logo into lines for side-by-side rendering const logoLines = asciiLogo.trim().split("\n"); - - // Determine verbosity level based on terminal width - const isWide = terminalWidth >= 120; const isMedium = terminalWidth >= 80; - const getMemoryBlocksText = () => { - if (!agentState?.memory?.blocks) { - return null; - } - - const blocks = agentState.memory.blocks; - const count = blocks.length; - const labels = blocks - .map((b) => b.label) - .filter(Boolean) - .join(", "); - - if (isWide && labels) { - return `attached ${count} memory block${count !== 1 ? "s" : ""} (${labels})`; - } - if (isMedium) { - return `attached ${count} memory block${count !== 1 ? "s" : ""}`; - } - return null; - }; - - const getAgentMessage = () => { - if (loadingState === "ready") { - const memoryText = getMemoryBlocksText(); - const baseText = - continueSession && agentId - ? "Resumed agent" - : agentId - ? "Created a new agent" - : "Ready to go!"; - - if (memoryText) { - return `${baseText}, ${memoryText}`; - } - return baseText; - } - if (loadingState === "initializing") { - return continueSession ? "Resuming agent..." : "Creating agent..."; - } - if (loadingState === "assembling") { - return "Assembling tools..."; - } - if (loadingState === "upserting") { - return "Upserting tools..."; - } - if (loadingState === "linking") { - return "Attaching Letta Code tools..."; - } - if (loadingState === "unlinking") { - return "Removing Letta Code tools..."; - } - if (loadingState === "checking") { - return "Checking for pending approvals..."; - } - return ""; - }; - - const getPathLine = () => { - if (isMedium) { - return `Running in ${cwd}`; - } - return cwd; - }; - - const getAgentLink = () => { - if (loadingState === "ready" && agentId) { - const url = `https://app.letta.com/agents/${agentId}`; - if (isWide) { - return { text: url, url }; - } - if (isMedium) { - return { text: agentId, url }; - } - return { text: "Click to view in ADE", url }; - } - return null; - }; - - const agentMessage = getAgentMessage(); - const pathLine = getPathLine(); - const agentLink = getAgentLink(); + const statusMessage = getStatusMessage( + loadingState, + !!continueSession, + agentId, + ); + const pathLine = isMedium ? `Running in ${cwd}` : cwd; + const agentUrl = agentId ? `https://app.letta.com/agents/${agentId}` : null; + const hints = + loadingState === "ready" + ? getAgentStatusHints(!!continueSession, agentState, agentProvenance) + : []; return ( @@ -139,13 +143,55 @@ export function WelcomeScreen({ Letta Code v{version} {pathLine} - {agentMessage && {agentMessage}} - {agentLink && ( - - {agentLink.text} - + {statusMessage && ( + + {statusMessage} + {loadingState === "ready" && agentUrl && ( + <> + {": "} + + {agentUrl} + + + )} + )} + {hints.map((hint, idx) => ( + // biome-ignore lint/suspicious/noArrayIndexKey: Hint lines are static and never reorder + + {hint} + + ))} ); } + +function getStatusMessage( + loadingState: LoadingState, + continueSession: boolean, + agentId?: string, +): string { + switch (loadingState) { + case "ready": + return continueSession && agentId + ? "Resumed agent" + : agentId + ? "Created a new agent" + : "Ready to go!"; + case "initializing": + return continueSession ? "Resuming agent..." : "Creating agent..."; + case "assembling": + return "Assembling tools..."; + case "upserting": + return "Upserting tools..."; + case "linking": + return "Attaching Letta Code tools..."; + case "unlinking": + return "Removing Letta Code tools..."; + case "checking": + return "Checking for pending approvals..."; + default: + return ""; + } +}