feat: unify /pinned and /agents into tabbed agent browser (#412)

Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
Charles Packer
2025-12-29 00:25:16 -08:00
committed by GitHub
parent 0104fe4b3b
commit a5b0c9f1c9
3 changed files with 643 additions and 263 deletions

View File

@@ -72,7 +72,6 @@ import { NewAgentDialog } from "./components/NewAgentDialog";
import { OAuthCodeDialog } from "./components/OAuthCodeDialog";
import { PinDialog, validateAgentName } from "./components/PinDialog";
import { PlanModeDialog } from "./components/PlanModeDialog";
import { ProfileSelector } from "./components/ProfileSelector";
import { QuestionDialog } from "./components/QuestionDialog";
import { ReasoningMessage } from "./components/ReasoningMessageRich";
import { ResumeSelector } from "./components/ResumeSelector";
@@ -478,7 +477,6 @@ export default function App({
| "system"
| "agent"
| "resume"
| "profile"
| "search"
| "subagent"
| "feedback"
@@ -2945,8 +2943,14 @@ export default function App({
return { submitted: true };
}
// Special handling for /agents command - show agent selector (/resume is hidden alias)
if (msg.trim() === "/agents" || msg.trim() === "/resume") {
// Special handling for /agents command - show agent browser
// /resume, /pinned, /profiles are hidden aliases
if (
msg.trim() === "/agents" ||
msg.trim() === "/resume" ||
msg.trim() === "/pinned" ||
msg.trim() === "/profiles"
) {
setActiveOverlay("resume");
return { submitted: true };
}
@@ -2972,9 +2976,9 @@ export default function App({
setAgentName,
};
// /profile - open profile selector
// /profile - open agent browser (now points to /agents)
if (!subcommand) {
setActiveOverlay("profile");
setActiveOverlay("resume");
return { submitted: true };
}
@@ -3033,12 +3037,6 @@ export default function App({
return { submitted: true };
}
// Special handling for /profiles and /pinned commands - open pinned agents selector
if (msg.trim() === "/profiles" || msg.trim() === "/pinned") {
setActiveOverlay("profile");
return { submitted: true };
}
// Special handling for /new command - create new agent dialog
if (msg.trim() === "/new") {
setActiveOverlay("new");
@@ -5508,33 +5506,6 @@ Plan file path: ${planFilePath}`;
/>
)}
{/* Profile Selector - conditionally mounted as overlay */}
{activeOverlay === "profile" && (
<ProfileSelector
currentAgentId={agentId}
onSelect={async (id) => {
closeOverlay();
await handleAgentSelect(id);
}}
onUnpin={(unpinAgentId) => {
closeOverlay();
settingsManager.unpinBoth(unpinAgentId);
const cmdId = uid("cmd");
buffersRef.current.byId.set(cmdId, {
kind: "command",
id: cmdId,
input: "/pinned",
output: `Unpinned agent ${unpinAgentId.slice(0, 12)}`,
phase: "finished",
success: true,
});
buffersRef.current.order.push(cmdId);
refreshDerived();
}}
onCancel={closeOverlay}
/>
)}
{/* Message Search - conditionally mounted as overlay */}
{activeOverlay === "search" && (
<MessageSearch onClose={closeOverlay} />

View File

@@ -12,12 +12,12 @@ interface Command {
export const commands: Record<string, Command> = {
// === Page 1: Most commonly used (order 10-19) ===
"/pinned": {
desc: "Browse pinned agents",
"/agents": {
desc: "Browse agents (pinned, Letta Code, all)",
order: 10,
handler: () => {
// Handled specially in App.tsx to open pinned agents selector
return "Opening pinned agents...";
// Handled specially in App.tsx to open agent browser
return "Opening agent browser...";
},
},
"/model": {
@@ -85,14 +85,6 @@ export const commands: Record<string, Command> = {
return "Creating new agent...";
},
},
"/agents": {
desc: "Browse all agents",
order: 21,
handler: () => {
// Handled specially in App.tsx to show agent selector
return "Opening agent selector...";
},
},
"/pin": {
desc: "Pin current agent globally, or use -l for local only",
order: 22,
@@ -336,6 +328,20 @@ export const commands: Record<string, Command> = {
return "Opening agent selector...";
},
},
"/pinned": {
desc: "Browse pinned agents",
hidden: true, // Alias for /agents (opens to Pinned tab)
handler: () => {
return "Opening agent browser...";
},
},
"/profiles": {
desc: "Browse pinned agents",
hidden: true, // Alias for /agents (opens to Pinned tab)
handler: () => {
return "Opening agent browser...";
},
},
};
/**

View File

@@ -3,6 +3,8 @@ import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"
import { Box, Text, useInput } from "ink";
import { useCallback, useEffect, useRef, useState } from "react";
import { getClient } from "../../agent/client";
import { getModelDisplayName } from "../../agent/model";
import { settingsManager } from "../../settings-manager";
import { useTerminalWidth } from "../hooks/useTerminalWidth";
import { colors } from "./colors";
@@ -12,8 +14,35 @@ interface ResumeSelectorProps {
onCancel: () => void;
}
const DISPLAY_PAGE_SIZE = 5; // How many agents to show per page
const FETCH_PAGE_SIZE = 20; // How many agents to fetch from server at once
type TabId = "pinned" | "letta-code" | "all";
interface PinnedAgentData {
agentId: string;
agent: AgentState | null;
error: string | null;
isLocal: boolean;
}
const TABS: { id: TabId; label: string }[] = [
{ id: "pinned", label: "Pinned" },
{ id: "letta-code", label: "Letta Code" },
{ id: "all", label: "All" },
];
const TAB_DESCRIPTIONS: Record<TabId, string> = {
pinned: "Save agents for easy access by pinning them with /pin",
"letta-code": "Displaying agents created inside of Letta Code",
all: "Displaying all available agents",
};
const TAB_EMPTY_STATES: Record<TabId, string> = {
pinned: "No pinned agents, use /pin to save",
"letta-code": "No agents with tag 'origin:letta-code'",
all: "No agents found",
};
const DISPLAY_PAGE_SIZE = 5;
const FETCH_PAGE_SIZE = 20;
/**
* Format a relative time string from a date
@@ -40,28 +69,34 @@ function formatRelativeTime(dateStr: string | null | undefined): string {
/**
* Truncate agent ID with middle ellipsis if it exceeds available width
* e.g., "agent-6b383e6f-f2df-43ed-ad88-8c832f1129d0" -> "agent-6b3...9d0"
*/
function truncateAgentId(id: string, availableWidth: number): string {
if (id.length <= availableWidth) return id;
if (availableWidth < 15) return id.slice(0, availableWidth); // Too narrow for ellipsis
const prefixLen = Math.floor((availableWidth - 3) / 2); // -3 for "..."
if (availableWidth < 15) return id.slice(0, availableWidth);
const prefixLen = Math.floor((availableWidth - 3) / 2);
const suffixLen = availableWidth - 3 - prefixLen;
return `${id.slice(0, prefixLen)}...${id.slice(-suffixLen)}`;
}
/**
* Format model string to show provider/model-name
* Format model string to show friendly display name (e.g., "Sonnet 4.5")
*/
function formatModel(agent: AgentState): string {
// Prefer the new model field
// Build handle from agent config
let handle: string | null = null;
if (agent.model) {
return agent.model;
}
// Fall back to llm_config
if (agent.llm_config?.model) {
handle = agent.model;
} else if (agent.llm_config?.model) {
const provider = agent.llm_config.model_endpoint_type || "unknown";
return `${provider}/${agent.llm_config.model}`;
handle = `${provider}/${agent.llm_config.model}`;
}
if (handle) {
// Try to get friendly display name
const displayName = getModelDisplayName(handle);
if (displayName) return displayName;
// Fallback to handle
return handle;
}
return "unknown";
}
@@ -72,22 +107,88 @@ export function ResumeSelector({
onCancel,
}: ResumeSelectorProps) {
const terminalWidth = useTerminalWidth();
const [allAgents, setAllAgents] = useState<AgentState[]>([]); // All fetched agents
const [nextCursor, setNextCursor] = useState<string | null>(null);
const [currentPage, setCurrentPage] = useState(0);
const [loading, setLoading] = useState(true);
const [loadingMore, setLoadingMore] = useState(false);
const [error, setError] = useState<string | null>(null);
const [selectedIndex, setSelectedIndex] = useState(0);
const [searchInput, setSearchInput] = useState(""); // What user is typing
const [activeQuery, setActiveQuery] = useState(""); // Submitted search query
const [hasMore, setHasMore] = useState(true);
const [filterLettaCode, setFilterLettaCode] = useState(true); // Filter to only letta-code agents
const clientRef = useRef<Letta | null>(null);
// Fetch agents from the server
const fetchAgents = useCallback(
async (afterCursor?: string | null, query?: string) => {
// Tab state
const [activeTab, setActiveTab] = useState<TabId>("pinned");
// Pinned tab state
const [pinnedAgents, setPinnedAgents] = useState<PinnedAgentData[]>([]);
const [pinnedLoading, setPinnedLoading] = useState(true);
const [pinnedSelectedIndex, setPinnedSelectedIndex] = useState(0);
const [pinnedPage, setPinnedPage] = useState(0);
// Letta Code tab state (cached separately)
const [lettaCodeAgents, setLettaCodeAgents] = useState<AgentState[]>([]);
const [lettaCodeCursor, setLettaCodeCursor] = useState<string | null>(null);
const [lettaCodeLoading, setLettaCodeLoading] = useState(false);
const [lettaCodeLoadingMore, setLettaCodeLoadingMore] = useState(false);
const [lettaCodeHasMore, setLettaCodeHasMore] = useState(true);
const [lettaCodeSelectedIndex, setLettaCodeSelectedIndex] = useState(0);
const [lettaCodePage, setLettaCodePage] = useState(0);
const [lettaCodeError, setLettaCodeError] = useState<string | null>(null);
const [lettaCodeLoaded, setLettaCodeLoaded] = useState(false);
const [lettaCodeQuery, setLettaCodeQuery] = useState<string>(""); // Query used to load current data
// All tab state (cached separately)
const [allAgents, setAllAgents] = useState<AgentState[]>([]);
const [allCursor, setAllCursor] = useState<string | null>(null);
const [allLoading, setAllLoading] = useState(false);
const [allLoadingMore, setAllLoadingMore] = useState(false);
const [allHasMore, setAllHasMore] = useState(true);
const [allSelectedIndex, setAllSelectedIndex] = useState(0);
const [allPage, setAllPage] = useState(0);
const [allError, setAllError] = useState<string | null>(null);
const [allLoaded, setAllLoaded] = useState(false);
const [allQuery, setAllQuery] = useState<string>(""); // Query used to load current data
// Search state (shared across list tabs)
const [searchInput, setSearchInput] = useState("");
const [activeQuery, setActiveQuery] = useState("");
// Load pinned agents
const loadPinnedAgents = useCallback(async () => {
setPinnedLoading(true);
try {
const mergedPinned = settingsManager.getMergedPinnedAgents();
if (mergedPinned.length === 0) {
setPinnedAgents([]);
setPinnedLoading(false);
return;
}
const client = clientRef.current || (await getClient());
clientRef.current = client;
const pinnedData = await Promise.all(
mergedPinned.map(async ({ agentId, isLocal }) => {
try {
const agent = await client.agents.retrieve(agentId, {
include: ["agent.blocks"],
});
return { agentId, agent, error: null, isLocal };
} catch {
return { agentId, agent: null, error: "Agent not found", isLocal };
}
}),
);
setPinnedAgents(pinnedData);
} catch {
setPinnedAgents([]);
} finally {
setPinnedLoading(false);
}
}, []);
// Fetch agents for list tabs (Letta Code / All)
const fetchListAgents = useCallback(
async (
filterLettaCode: boolean,
afterCursor?: string | null,
query?: string,
) => {
const client = clientRef.current || (await getClient());
clientRef.current = client;
@@ -101,48 +202,211 @@ export function ResumeSelector({
...(query && { query_text: query }),
});
// Get cursor for next fetch (last item's ID if there are more)
const cursor =
agentList.items.length === FETCH_PAGE_SIZE
? (agentList.items[agentList.items.length - 1]?.id ?? null)
: null;
return {
agents: agentList.items,
nextCursor: cursor,
};
return { agents: agentList.items, nextCursor: cursor };
},
[filterLettaCode],
[],
);
// Fetch agents when activeQuery changes (initial load or search submitted)
useEffect(() => {
const doFetch = async () => {
setLoading(true);
// Load Letta Code agents
const loadLettaCodeAgents = useCallback(
async (query?: string) => {
setLettaCodeLoading(true);
setLettaCodeError(null);
try {
const result = await fetchAgents(null, activeQuery || undefined);
setAllAgents(result.agents);
setNextCursor(result.nextCursor);
setHasMore(result.nextCursor !== null);
setCurrentPage(0);
setSelectedIndex(0);
const result = await fetchListAgents(true, null, query);
setLettaCodeAgents(result.agents);
setLettaCodeCursor(result.nextCursor);
setLettaCodeHasMore(result.nextCursor !== null);
setLettaCodePage(0);
setLettaCodeSelectedIndex(0);
setLettaCodeLoaded(true);
setLettaCodeQuery(query || ""); // Track query used for this load
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
setLettaCodeError(err instanceof Error ? err.message : String(err));
} finally {
setLoading(false);
setLettaCodeLoading(false);
}
};
doFetch();
}, [fetchAgents, activeQuery]);
},
[fetchListAgents],
);
// Submit search (called when Enter is pressed while typing search)
// Load All agents
const loadAllAgents = useCallback(
async (query?: string) => {
setAllLoading(true);
setAllError(null);
try {
const result = await fetchListAgents(false, null, query);
setAllAgents(result.agents);
setAllCursor(result.nextCursor);
setAllHasMore(result.nextCursor !== null);
setAllPage(0);
setAllSelectedIndex(0);
setAllLoaded(true);
setAllQuery(query || ""); // Track query used for this load
} catch (err) {
setAllError(err instanceof Error ? err.message : String(err));
} finally {
setAllLoading(false);
}
},
[fetchListAgents],
);
// Load pinned agents on mount
useEffect(() => {
loadPinnedAgents();
}, [loadPinnedAgents]);
// Load tab data when switching tabs (only if not already loaded)
useEffect(() => {
if (activeTab === "letta-code" && !lettaCodeLoaded && !lettaCodeLoading) {
loadLettaCodeAgents();
} else if (activeTab === "all" && !allLoaded && !allLoading) {
loadAllAgents();
}
}, [
activeTab,
lettaCodeLoaded,
lettaCodeLoading,
loadLettaCodeAgents,
allLoaded,
allLoading,
loadAllAgents,
]);
// Reload current tab when search query changes (only if query differs from cached)
useEffect(() => {
if (activeTab === "letta-code" && activeQuery !== lettaCodeQuery) {
loadLettaCodeAgents(activeQuery || undefined);
} else if (activeTab === "all" && activeQuery !== allQuery) {
loadAllAgents(activeQuery || undefined);
}
}, [
activeQuery,
activeTab,
lettaCodeQuery,
allQuery,
loadLettaCodeAgents,
loadAllAgents,
]);
// Fetch more Letta Code agents
const fetchMoreLettaCodeAgents = useCallback(async () => {
if (lettaCodeLoadingMore || !lettaCodeHasMore || !lettaCodeCursor) return;
setLettaCodeLoadingMore(true);
try {
const result = await fetchListAgents(
true,
lettaCodeCursor,
activeQuery || undefined,
);
setLettaCodeAgents((prev) => [...prev, ...result.agents]);
setLettaCodeCursor(result.nextCursor);
setLettaCodeHasMore(result.nextCursor !== null);
} catch {
// Silently fail on pagination errors
} finally {
setLettaCodeLoadingMore(false);
}
}, [
lettaCodeLoadingMore,
lettaCodeHasMore,
lettaCodeCursor,
fetchListAgents,
activeQuery,
]);
// Fetch more All agents
const fetchMoreAllAgents = useCallback(async () => {
if (allLoadingMore || !allHasMore || !allCursor) return;
setAllLoadingMore(true);
try {
const result = await fetchListAgents(
false,
allCursor,
activeQuery || undefined,
);
setAllAgents((prev) => [...prev, ...result.agents]);
setAllCursor(result.nextCursor);
setAllHasMore(result.nextCursor !== null);
} catch {
// Silently fail on pagination errors
} finally {
setAllLoadingMore(false);
}
}, [allLoadingMore, allHasMore, allCursor, fetchListAgents, activeQuery]);
// Pagination calculations - Pinned
const pinnedTotalPages = Math.ceil(pinnedAgents.length / DISPLAY_PAGE_SIZE);
const pinnedStartIndex = pinnedPage * DISPLAY_PAGE_SIZE;
const pinnedPageAgents = pinnedAgents.slice(
pinnedStartIndex,
pinnedStartIndex + DISPLAY_PAGE_SIZE,
);
// Pagination calculations - Letta Code
const lettaCodeTotalPages = Math.ceil(
lettaCodeAgents.length / DISPLAY_PAGE_SIZE,
);
const lettaCodeStartIndex = lettaCodePage * DISPLAY_PAGE_SIZE;
const lettaCodePageAgents = lettaCodeAgents.slice(
lettaCodeStartIndex,
lettaCodeStartIndex + DISPLAY_PAGE_SIZE,
);
const lettaCodeCanGoNext =
lettaCodePage < lettaCodeTotalPages - 1 || lettaCodeHasMore;
// Pagination calculations - All
const allTotalPages = Math.ceil(allAgents.length / DISPLAY_PAGE_SIZE);
const allStartIndex = allPage * DISPLAY_PAGE_SIZE;
const allPageAgents = allAgents.slice(
allStartIndex,
allStartIndex + DISPLAY_PAGE_SIZE,
);
const allCanGoNext = allPage < allTotalPages - 1 || allHasMore;
// Current tab's state (computed)
const currentLoading =
activeTab === "pinned"
? pinnedLoading
: activeTab === "letta-code"
? lettaCodeLoading
: allLoading;
const currentError =
activeTab === "letta-code"
? lettaCodeError
: activeTab === "all"
? allError
: null;
const currentAgents =
activeTab === "pinned"
? pinnedPageAgents.map((p) => p.agent).filter(Boolean)
: activeTab === "letta-code"
? lettaCodePageAgents
: allPageAgents;
const setCurrentSelectedIndex =
activeTab === "pinned"
? setPinnedSelectedIndex
: activeTab === "letta-code"
? setLettaCodeSelectedIndex
: setAllSelectedIndex;
// Submit search
const submitSearch = useCallback(() => {
if (searchInput !== activeQuery) {
setActiveQuery(searchInput);
}
}, [searchInput, activeQuery]);
// Clear search
// Clear search (effect will handle reload when query changes)
const clearSearch = useCallback(() => {
setSearchInput("");
if (activeQuery) {
@@ -150,109 +414,267 @@ export function ResumeSelector({
}
}, [activeQuery]);
// Fetch more agents when needed
const fetchMoreAgents = useCallback(async () => {
if (loadingMore || !hasMore || !nextCursor) return;
setLoadingMore(true);
try {
const result = await fetchAgents(nextCursor, activeQuery || undefined);
setAllAgents((prev) => [...prev, ...result.agents]);
setNextCursor(result.nextCursor);
setHasMore(result.nextCursor !== null);
} catch (_err) {
// Silently fail on pagination errors
} finally {
setLoadingMore(false);
}
}, [loadingMore, hasMore, nextCursor, fetchAgents, activeQuery]);
// Calculate display pages from all fetched agents
const totalDisplayPages = Math.ceil(allAgents.length / DISPLAY_PAGE_SIZE);
const startIndex = currentPage * DISPLAY_PAGE_SIZE;
const pageAgents = allAgents.slice(
startIndex,
startIndex + DISPLAY_PAGE_SIZE,
);
const canGoNext = currentPage < totalDisplayPages - 1 || hasMore;
useInput((input, key) => {
if (loading || error) return;
// Tab key cycles through tabs
if (key.tab) {
const currentIndex = TABS.findIndex((t) => t.id === activeTab);
const nextIndex = (currentIndex + 1) % TABS.length;
setActiveTab(TABS[nextIndex]?.id ?? "pinned");
return;
}
if (currentLoading) return;
if (key.upArrow) {
setSelectedIndex((prev) => Math.max(0, prev - 1));
setCurrentSelectedIndex((prev: number) => Math.max(0, prev - 1));
} else if (key.downArrow) {
setSelectedIndex((prev) => Math.min(pageAgents.length - 1, prev + 1));
setCurrentSelectedIndex((prev: number) =>
Math.min((currentAgents as AgentState[]).length - 1, prev + 1),
);
} else if (key.return) {
// If typing a search query, submit it; otherwise select agent
if (searchInput && searchInput !== activeQuery) {
// If typing a search query (list tabs only), submit it
if (
activeTab !== "pinned" &&
searchInput &&
searchInput !== activeQuery
) {
submitSearch();
return;
}
// Select agent
if (activeTab === "pinned") {
const selected = pinnedPageAgents[pinnedSelectedIndex];
if (selected?.agent) {
onSelect(selected.agentId);
}
} else if (activeTab === "letta-code") {
const selected = lettaCodePageAgents[lettaCodeSelectedIndex];
if (selected?.id) {
onSelect(selected.id);
}
} else {
const selectedAgent = pageAgents[selectedIndex];
if (selectedAgent?.id) {
onSelect(selectedAgent.id);
const selected = allPageAgents[allSelectedIndex];
if (selected?.id) {
onSelect(selected.id);
}
}
} else if (key.escape) {
// If typing search, clear it first; otherwise cancel
if (searchInput) {
// If typing search (list tabs), clear it first
if (activeTab !== "pinned" && searchInput) {
clearSearch();
} else {
onCancel();
return;
}
onCancel();
} else if (key.backspace || key.delete) {
setSearchInput((prev) => prev.slice(0, -1));
if (activeTab !== "pinned") {
setSearchInput((prev) => prev.slice(0, -1));
}
} else if (input === "j" || input === "J") {
// Previous page (j = up/back)
if (currentPage > 0) {
setCurrentPage((prev) => prev - 1);
setSelectedIndex(0);
// Previous page
if (activeTab === "pinned") {
if (pinnedPage > 0) {
setPinnedPage((prev) => prev - 1);
setPinnedSelectedIndex(0);
}
} else if (activeTab === "letta-code") {
if (lettaCodePage > 0) {
setLettaCodePage((prev) => prev - 1);
setLettaCodeSelectedIndex(0);
}
} else {
if (allPage > 0) {
setAllPage((prev) => prev - 1);
setAllSelectedIndex(0);
}
}
} else if (input === "k" || input === "K") {
// Next page (k = down/forward)
if (canGoNext) {
const nextPageIndex = currentPage + 1;
// Next page
if (activeTab === "pinned") {
if (pinnedPage < pinnedTotalPages - 1) {
setPinnedPage((prev) => prev + 1);
setPinnedSelectedIndex(0);
}
} else if (activeTab === "letta-code" && lettaCodeCanGoNext) {
const nextPageIndex = lettaCodePage + 1;
const nextStartIndex = nextPageIndex * DISPLAY_PAGE_SIZE;
// Fetch more if we need data for the next page
if (nextStartIndex >= allAgents.length && hasMore) {
fetchMoreAgents();
if (nextStartIndex >= lettaCodeAgents.length && lettaCodeHasMore) {
fetchMoreLettaCodeAgents();
}
if (nextStartIndex < lettaCodeAgents.length) {
setLettaCodePage(nextPageIndex);
setLettaCodeSelectedIndex(0);
}
} else if (activeTab === "all" && allCanGoNext) {
const nextPageIndex = allPage + 1;
const nextStartIndex = nextPageIndex * DISPLAY_PAGE_SIZE;
if (nextStartIndex >= allAgents.length && allHasMore) {
fetchMoreAllAgents();
}
// Navigate if we have the data
if (nextStartIndex < allAgents.length) {
setCurrentPage(nextPageIndex);
setSelectedIndex(0);
setAllPage(nextPageIndex);
setAllSelectedIndex(0);
}
}
} else if (input === "/") {
// Ignore "/" - just starts typing search
} else if (input === "a" || input === "A") {
// Toggle filter between letta-code agents and all agents
setFilterLettaCode((prev) => !prev);
} else if (input && !key.ctrl && !key.meta) {
// Add regular characters to search input
} else if (activeTab === "pinned" && (input === "d" || input === "D")) {
// Unpin from all (pinned tab only)
const selected = pinnedPageAgents[pinnedSelectedIndex];
if (selected) {
settingsManager.unpinBoth(selected.agentId);
loadPinnedAgents();
}
} else if (activeTab === "pinned" && (input === "p" || input === "P")) {
// Unpin from current scope (pinned tab only)
const selected = pinnedPageAgents[pinnedSelectedIndex];
if (selected) {
if (selected.isLocal) {
settingsManager.unpinLocal(selected.agentId);
} else {
settingsManager.unpinGlobal(selected.agentId);
}
loadPinnedAgents();
}
} else if (activeTab !== "pinned" && input && !key.ctrl && !key.meta) {
// Type to search (list tabs only)
setSearchInput((prev) => prev + input);
}
});
// Always show the header, with contextual content below
// Render tab bar
const renderTabBar = () => (
<Box flexDirection="row" gap={1}>
{TABS.map((tab) => {
const isActive = tab.id === activeTab;
return (
<Text
key={tab.id}
color={isActive ? colors.selector.itemHighlighted : undefined}
bold={isActive}
>
[{tab.label}]
</Text>
);
})}
</Box>
);
// Render agent item (shared between tabs)
const renderAgentItem = (
agent: AgentState,
_index: number,
isSelected: boolean,
extra?: { isLocal?: boolean },
) => {
const isCurrent = agent.id === currentAgentId;
const relativeTime = formatRelativeTime(agent.last_run_completion);
const blockCount = agent.blocks?.length ?? 0;
const modelStr = formatModel(agent);
const nameLen = (agent.name || "Unnamed").length;
const fixedChars = 2 + 3 + (isCurrent ? 10 : 0);
const availableForId = Math.max(15, terminalWidth - nameLen - fixedChars);
const displayId = truncateAgentId(agent.id, availableForId);
return (
<Box key={agent.id} flexDirection="column" marginBottom={1}>
<Box flexDirection="row">
<Text
color={isSelected ? colors.selector.itemHighlighted : undefined}
>
{isSelected ? ">" : " "}
</Text>
<Text> </Text>
<Text
bold={isSelected}
color={isSelected ? colors.selector.itemHighlighted : undefined}
>
{agent.name || "Unnamed"}
</Text>
<Text dimColor>
{" · "}
{extra?.isLocal !== undefined
? `${extra.isLocal ? "project" : "global"} · `
: ""}
{displayId}
</Text>
{isCurrent && (
<Text color={colors.selector.itemCurrent}> (current)</Text>
)}
</Box>
<Box flexDirection="row" marginLeft={2}>
<Text dimColor italic>
{agent.description || "No description"}
</Text>
</Box>
<Box flexDirection="row" marginLeft={2}>
<Text dimColor>
{relativeTime} · {blockCount} memory block
{blockCount === 1 ? "" : "s"} · {modelStr}
</Text>
</Box>
</Box>
);
};
// Render pinned agent item (may have error)
const renderPinnedItem = (
data: PinnedAgentData,
index: number,
isSelected: boolean,
) => {
if (data.agent) {
return renderAgentItem(data.agent, index, isSelected, {
isLocal: data.isLocal,
});
}
// Error state for missing agent
return (
<Box key={data.agentId} flexDirection="column" marginBottom={1}>
<Box flexDirection="row">
<Text
color={isSelected ? colors.selector.itemHighlighted : undefined}
>
{isSelected ? ">" : " "}
</Text>
<Text> </Text>
<Text
bold={isSelected}
color={isSelected ? colors.selector.itemHighlighted : undefined}
>
{data.agentId.slice(0, 12)}
</Text>
<Text dimColor> · {data.isLocal ? "project" : "global"}</Text>
</Box>
<Box flexDirection="row" marginLeft={2}>
<Text color="red" italic>
{data.error}
</Text>
</Box>
</Box>
);
};
return (
<Box flexDirection="column" gap={1}>
<Box flexDirection="column">
<Box flexDirection="column">
{/* Header */}
<Box flexDirection="column" gap={1} marginBottom={1}>
<Text bold color={colors.selector.title}>
Browsing Agents (sorting by last run)
</Text>
<Text dimColor>
{filterLettaCode
? "Displaying agents created in Letta Code (press A to show all)"
: "Displaying all agents (press A to filter to Letta Code)"}
Browsing Agents
</Text>
<Box flexDirection="column">
{renderTabBar()}
<Text dimColor>{TAB_DESCRIPTIONS[activeTab]}</Text>
</Box>
</Box>
{/* Search input - show when typing or when there's an active search */}
{(searchInput || activeQuery) && (
<Box>
{/* Search input - list tabs only */}
{activeTab !== "pinned" && (searchInput || activeQuery) && (
<Box marginBottom={1}>
<Text dimColor>Search: </Text>
<Text>{searchInput}</Text>
{searchInput && searchInput !== activeQuery && (
@@ -264,113 +686,94 @@ export function ResumeSelector({
</Box>
)}
{/* Error state */}
{error && (
{/* Error state - list tabs */}
{activeTab !== "pinned" && currentError && (
<Box flexDirection="column">
<Text color="red">Error: {error}</Text>
<Text color="red">Error: {currentError}</Text>
<Text dimColor>Press ESC to cancel</Text>
</Box>
)}
{/* Loading state */}
{loading && !error && (
{currentLoading && (
<Box>
<Text dimColor>Loading agents...</Text>
</Box>
)}
{/* Empty state */}
{!loading && !error && allAgents.length === 0 && (
<Box flexDirection="column">
<Text dimColor>
{activeQuery ? "No matching agents found" : "No agents found"}
</Text>
<Text dimColor>Press ESC to cancel</Text>
</Box>
)}
{/* Agent list - only show when loaded and have agents */}
{!loading && !error && allAgents.length > 0 && (
<Box flexDirection="column">
{pageAgents.map((agent, index) => {
const isSelected = index === selectedIndex;
const isCurrent = agent.id === currentAgentId;
const relativeTime = formatRelativeTime(agent.last_run_completion);
const blockCount = agent.blocks?.length ?? 0;
const modelStr = formatModel(agent);
// Calculate available width for agent ID
// Row format: "> Name · agent-id (current)"
const nameLen = (agent.name || "Unnamed").length;
const fixedChars = 2 + 3 + (isCurrent ? 10 : 0); // "> " + " · " + " (current)"
const availableForId = Math.max(
15,
terminalWidth - nameLen - fixedChars,
);
const displayId = truncateAgentId(agent.id, availableForId);
return (
<Box key={agent.id} flexDirection="column" marginBottom={1}>
{/* Row 1: Selection indicator, agent name, and ID */}
<Box flexDirection="row">
<Text
color={
isSelected ? colors.selector.itemHighlighted : undefined
}
>
{isSelected ? ">" : " "}
</Text>
<Text> </Text>
<Text
bold={isSelected}
color={
isSelected ? colors.selector.itemHighlighted : undefined
}
>
{agent.name || "Unnamed"}
</Text>
<Text dimColor> · {displayId}</Text>
{isCurrent && (
<Text color={colors.selector.itemCurrent}> (current)</Text>
)}
</Box>
{/* Row 2: Description */}
<Box flexDirection="row" marginLeft={2}>
<Text dimColor italic>
{agent.description || "No description"}
</Text>
</Box>
{/* Row 3: Metadata (dimmed) */}
<Box flexDirection="row" marginLeft={2}>
<Text dimColor>
{relativeTime} · {blockCount} memory block
{blockCount === 1 ? "" : "s"} · {modelStr}
</Text>
</Box>
</Box>
);
})}
</Box>
)}
{/* Footer with pagination and controls - only show when loaded with agents */}
{!loading && !error && allAgents.length > 0 && (
<Box flexDirection="column" marginTop={1}>
<Box>
<Text dimColor>
Page {currentPage + 1}
{hasMore ? "+" : `/${totalDisplayPages || 1}`}
{loadingMore && " (loading...)"}
</Text>
</Box>
<Box>
<Text dimColor>
navigate · Enter select · J/K page · Type to search
</Text>
{!currentLoading &&
((activeTab === "pinned" && pinnedAgents.length === 0) ||
(activeTab === "letta-code" &&
!lettaCodeError &&
lettaCodeAgents.length === 0) ||
(activeTab === "all" && !allError && allAgents.length === 0)) && (
<Box flexDirection="column">
<Text dimColor>{TAB_EMPTY_STATES[activeTab]}</Text>
<Text dimColor>Press ESC to cancel</Text>
</Box>
)}
{/* Pinned tab content */}
{activeTab === "pinned" && !pinnedLoading && pinnedAgents.length > 0 && (
<Box flexDirection="column">
{pinnedPageAgents.map((data, index) =>
renderPinnedItem(data, index, index === pinnedSelectedIndex),
)}
</Box>
)}
{/* Letta Code tab content */}
{activeTab === "letta-code" &&
!lettaCodeLoading &&
!lettaCodeError &&
lettaCodeAgents.length > 0 && (
<Box flexDirection="column">
{lettaCodePageAgents.map((agent, index) =>
renderAgentItem(agent, index, index === lettaCodeSelectedIndex),
)}
</Box>
)}
{/* All tab content */}
{activeTab === "all" &&
!allLoading &&
!allError &&
allAgents.length > 0 && (
<Box flexDirection="column">
{allPageAgents.map((agent, index) =>
renderAgentItem(agent, index, index === allSelectedIndex),
)}
</Box>
)}
{/* Footer */}
{!currentLoading &&
((activeTab === "pinned" && pinnedAgents.length > 0) ||
(activeTab === "letta-code" &&
!lettaCodeError &&
lettaCodeAgents.length > 0) ||
(activeTab === "all" && !allError && allAgents.length > 0)) && (
<Box flexDirection="column">
<Box>
<Text dimColor>
{activeTab === "pinned"
? `Page ${pinnedPage + 1}/${pinnedTotalPages || 1}`
: activeTab === "letta-code"
? `Page ${lettaCodePage + 1}${lettaCodeHasMore ? "+" : `/${lettaCodeTotalPages || 1}`}${lettaCodeLoadingMore ? " (loading...)" : ""}`
: `Page ${allPage + 1}${allHasMore ? "+" : `/${allTotalPages || 1}`}${allLoadingMore ? " (loading...)" : ""}`}
</Text>
</Box>
<Box>
<Text dimColor>
Tab switch · navigate · Enter select · J/K page
{activeTab === "pinned"
? " · P unpin · D unpin all"
: " · Type to search"}
</Text>
</Box>
</Box>
)}
</Box>
);
}