diff --git a/src/cli/components/WelcomeScreen.tsx b/src/cli/components/WelcomeScreen.tsx index 9225930..23db33c 100644 --- a/src/cli/components/WelcomeScreen.tsx +++ b/src/cli/components/WelcomeScreen.tsx @@ -51,6 +51,7 @@ type LoadingState = | "importing" | "initializing" | "checking" + | "selecting_global" | "ready"; /** diff --git a/src/cli/profile-selection.tsx b/src/cli/profile-selection.tsx index 5378161..fe96008 100644 --- a/src/cli/profile-selection.tsx +++ b/src/cli/profile-selection.tsx @@ -53,21 +53,23 @@ function formatModel(agent: AgentState): string { return agent.llm_config?.model || "unknown"; } -function getLabel(option: ProfileOption): string { +function getLabel(option: ProfileOption, freshRepoMode?: boolean): string { const parts: string[] = []; if (option.isLru) parts.push("last used"); if (option.isLocal) parts.push("pinned"); - else if (!option.isLru) parts.push("global"); // Pinned globally but not locally + else if (!option.isLru && !freshRepoMode) parts.push("global"); // Pinned globally but not locally return parts.length > 0 ? ` (${parts.join(", ")})` : ""; } function ProfileSelectionUI({ lruAgentId, externalLoading, + externalFreshRepoMode, onComplete, }: { lruAgentId: string | null; externalLoading?: boolean; + externalFreshRepoMode?: boolean; onComplete: (result: ProfileSelectionResult) => void; }) { const [options, setOptions] = useState([]); @@ -175,9 +177,11 @@ function ProfileSelectionUI({ }); const hasLocalDir = settingsManager.hasLocalLettaDir(); - const contextMessage = hasLocalDir - ? "Existing `.letta` folder detected." - : `${options.length} agent profile${options.length !== 1 ? "s" : ""} detected.`; + const contextMessage = externalFreshRepoMode + ? `${options.length} pinned agent${options.length !== 1 ? "s" : ""} available.` + : hasLocalDir + ? "Existing `.letta` folder detected." + : `${options.length} agent profile${options.length !== 1 ? "s" : ""} detected.`; return ( @@ -202,7 +206,7 @@ function ProfileSelectionUI({ const isSelected = index === selectedIndex; const displayName = option.agent?.name || option.agentId.slice(0, 20); - const label = getLabel(option); + const label = getLabel(option, externalFreshRepoMode); return ( @@ -290,12 +294,14 @@ function ProfileSelectionUI({ export function ProfileSelectionInline({ lruAgentId, loading: externalLoading, + freshRepoMode, onSelect, onCreateNew, onExit, }: { lruAgentId: string | null; loading?: boolean; + freshRepoMode?: boolean; onSelect: (agentId: string) => void; onCreateNew: () => void; onExit: () => void; @@ -313,6 +319,7 @@ export function ProfileSelectionInline({ return React.createElement(ProfileSelectionUI, { lruAgentId, externalLoading, + externalFreshRepoMode: freshRepoMode, onComplete: handleComplete, }); } diff --git a/src/index.ts b/src/index.ts index 9fcf695..a8184ff 100755 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { getClient } from "./agent/client"; import { initializeLoadedSkillsFlag, setAgentContext } from "./agent/context"; import type { AgentProvenance } from "./agent/create"; import { LETTA_CLOUD_API_URL } from "./auth/oauth"; +import { ProfileSelectionInline } from "./cli/profile-selection"; import { permissionMode } from "./permissions/mode"; import { settingsManager } from "./settings-manager"; import { telemetry } from "./telemetry"; @@ -615,6 +616,7 @@ async function main(): Promise { }) { const [loadingState, setLoadingState] = useState< | "selecting" + | "selecting_global" | "assembling" | "upserting" | "updating_tools" @@ -629,16 +631,39 @@ async function main(): Promise { const [isResumingSession, setIsResumingSession] = useState(false); const [agentProvenance, setAgentProvenance] = useState(null); + const [selectedGlobalAgentId, setSelectedGlobalAgentId] = useState< + string | null + >(null); - // Initialize on mount - no selector, just start immediately + // Initialize on mount - check if we should show global agent selector useEffect(() => { async function checkAndStart() { // Load settings await settingsManager.loadLocalProjectSettings(); + const localSettings = settingsManager.getLocalProjectSettings(); + const globalPinned = settingsManager.getGlobalPinnedAgents(); + + // Show selector if: + // 1. No lastAgent in this project (fresh directory) + // 2. No explicit flags that bypass selection (--new, --agent, --from-af, --continue) + // 3. Has global pinned agents available + const shouldShowSelector = + !localSettings.lastAgent && + !forceNew && + !agentIdArg && + !fromAfFile && + !continueSession && + globalPinned.length > 0; + + if (shouldShowSelector) { + setLoadingState("selecting_global"); + return; + } + setLoadingState("assembling"); } checkAndStart(); - }, []); + }, [forceNew, agentIdArg, fromAfFile, continueSession]); // Main initialization effect - runs after profile selection useEffect(() => { @@ -682,6 +707,16 @@ async function main(): Promise { // Agent no longer exists } } + + // Priority 4: Use agent selected from global selector + if (!resumingAgentId && selectedGlobalAgentId) { + try { + await client.agents.retrieve(selectedGlobalAgentId); + resumingAgentId = selectedGlobalAgentId; + } catch { + // Agent doesn't exist, will create new + } + } } // Set resuming state early so loading messages are accurate @@ -1057,16 +1092,40 @@ async function main(): Promise { systemPromptId, fromAfFile, loadingState, + selectedGlobalAgentId, ]); - // Profile selector is no longer shown at startup - // Users can access it via /pinned or /agents commands + // Don't render anything during initial "selecting" phase - wait for checkAndStart + if (loadingState === "selecting") { + return null; + } + + // Show global agent selector in fresh repos with global pinned agents + if (loadingState === "selecting_global") { + return React.createElement(ProfileSelectionInline, { + lruAgentId: null, // No LRU in fresh repo + loading: false, + freshRepoMode: true, // Hides "(global)" labels and simplifies context message + onSelect: (agentId: string) => { + // Auto-pin the selected global agent to this project + settingsManager.pinLocal(agentId); + + setSelectedGlobalAgentId(agentId); + setLoadingState("assembling"); + }, + onCreateNew: () => { + setLoadingState("assembling"); + }, + onExit: () => { + process.exit(0); + }, + }); + } if (!agentId) { return React.createElement(App, { agentId: "loading", - loadingState: - loadingState === "selecting" ? "assembling" : loadingState, + loadingState, continueSession: isResumingSession, startupApproval: resumeData?.pendingApproval ?? null, startupApprovals: resumeData?.pendingApprovals ?? [], @@ -1079,7 +1138,7 @@ async function main(): Promise { return React.createElement(App, { agentId, agentState, - loadingState: loadingState === "selecting" ? "assembling" : loadingState, + loadingState, continueSession: isResumingSession, startupApproval: resumeData?.pendingApproval ?? null, startupApprovals: resumeData?.pendingApprovals ?? [],