fix: Fixed status line and updated welcome screen (#156)

This commit is contained in:
Devansh Jain
2025-12-05 12:36:26 -08:00
committed by GitHub
parent fde6fc0ab1
commit 78fdb23d00
2 changed files with 145 additions and 192 deletions

View File

@@ -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 (

View File

@@ -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 (
<Box flexDirection="row" marginTop={1}>
@@ -139,13 +143,55 @@ export function WelcomeScreen({
Letta Code v{version}
</Text>
<Text dimColor>{pathLine}</Text>
{agentMessage && <Text dimColor>{agentMessage}</Text>}
{agentLink && (
<Link url={agentLink.url}>
<Text dimColor>{agentLink.text}</Text>
</Link>
{statusMessage && (
<Text dimColor>
{statusMessage}
{loadingState === "ready" && agentUrl && (
<>
{": "}
<Link url={agentUrl}>
<Text dimColor>{agentUrl}</Text>
</Link>
</>
)}
</Text>
)}
{hints.map((hint, idx) => (
// biome-ignore lint/suspicious/noArrayIndexKey: Hint lines are static and never reorder
<Text key={idx} dimColor>
{hint}
</Text>
))}
</Box>
</Box>
);
}
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 "";
}
}