diff --git a/openhands-cli/openhands_cli/listeners/__init__.py b/openhands-cli/openhands_cli/listeners/__init__.py
index a2f1d8606a..76725db747 100644
--- a/openhands-cli/openhands_cli/listeners/__init__.py
+++ b/openhands-cli/openhands_cli/listeners/__init__.py
@@ -1,4 +1,3 @@
-from openhands_cli.listeners.loading_listener import LoadingContext
from openhands_cli.listeners.pause_listener import PauseListener
-__all__ = ['PauseListener', 'LoadingContext']
+__all__ = ['PauseListener']
diff --git a/openhands-cli/openhands_cli/listeners/loading_listener.py b/openhands-cli/openhands_cli/listeners/loading_listener.py
deleted file mode 100644
index 8742e68ee4..0000000000
--- a/openhands-cli/openhands_cli/listeners/loading_listener.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""
-Loading animation utilities for OpenHands CLI.
-Provides animated loading screens during agent initialization.
-"""
-
-import sys
-import threading
-import time
-
-
-def display_initialization_animation(text: str, is_loaded: threading.Event) -> None:
- """Display a spinning animation while agent is being initialized.
-
- Args:
- text: The text to display alongside the animation
- is_loaded: Threading event that signals when loading is complete
- """
- ANIMATION_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏']
-
- i = 0
- while not is_loaded.is_set():
- sys.stdout.write('\n')
- sys.stdout.write(
- f'\033[s\033[J\033[38;2;255;215;0m[{ANIMATION_FRAMES[i % len(ANIMATION_FRAMES)]}] {text}\033[0m\033[u\033[1A'
- )
- sys.stdout.flush()
- time.sleep(0.1)
- i += 1
-
- sys.stdout.write('\r' + ' ' * (len(text) + 10) + '\r')
- sys.stdout.flush()
-
-
-class LoadingContext:
- """Context manager for displaying loading animations in a separate thread."""
-
- def __init__(self, text: str):
- """Initialize the loading context.
-
- Args:
- text: The text to display during loading
- """
- self.text = text
- self.is_loaded = threading.Event()
- self.loading_thread: threading.Thread | None = None
-
- def __enter__(self) -> 'LoadingContext':
- """Start the loading animation in a separate thread."""
- self.loading_thread = threading.Thread(
- target=display_initialization_animation,
- args=(self.text, self.is_loaded),
- daemon=True,
- )
- self.loading_thread.start()
- return self
-
- def __exit__(self, exc_type, exc_val, exc_tb) -> None:
- """Stop the loading animation and clean up the thread."""
- self.is_loaded.set()
- if self.loading_thread:
- self.loading_thread.join(
- timeout=1.0
- ) # Wait up to 1 second for thread to finish
diff --git a/openhands-cli/openhands_cli/setup.py b/openhands-cli/openhands_cli/setup.py
index f6fb6cdb37..c8fc07aa83 100644
--- a/openhands-cli/openhands_cli/setup.py
+++ b/openhands-cli/openhands_cli/setup.py
@@ -6,7 +6,6 @@ from openhands.sdk import Agent, BaseConversation, Conversation, Workspace, regi
from openhands.tools.execute_bash import BashTool
from openhands.tools.file_editor import FileEditorTool
from openhands.tools.task_tracker import TaskTrackerTool
-from openhands_cli.listeners import LoadingContext
from openhands_cli.locations import CONVERSATIONS_DIR, WORK_DIR
from openhands_cli.tui.settings.store import AgentStore
from openhands.sdk.security.confirmation_policy import (
@@ -70,26 +69,29 @@ def setup_conversation(
MissingAgentSpec: If agent specification is not found or invalid.
"""
- with LoadingContext('Initializing OpenHands agent...'):
- agent = load_agent_specs(str(conversation_id))
+ print_formatted_text(
+ HTML(f'Initializing agent...')
+ )
- if not include_security_analyzer:
- # Remove security analyzer from agent spec
- agent = agent.model_copy(
- update={"security_analyzer": None}
- )
+ agent = load_agent_specs(str(conversation_id))
- # Create conversation - agent context is now set in AgentStore.load()
- conversation: BaseConversation = Conversation(
- agent=agent,
- workspace=Workspace(working_dir=WORK_DIR),
- # Conversation will add / to this path
- persistence_dir=CONVERSATIONS_DIR,
- conversation_id=conversation_id,
+ if not include_security_analyzer:
+ # Remove security analyzer from agent spec
+ agent = agent.model_copy(
+ update={"security_analyzer": None}
)
- if include_security_analyzer:
- conversation.set_confirmation_policy(AlwaysConfirm())
+ # Create conversation - agent context is now set in AgentStore.load()
+ conversation: BaseConversation = Conversation(
+ agent=agent,
+ workspace=Workspace(working_dir=WORK_DIR),
+ # Conversation will add / to this path
+ persistence_dir=CONVERSATIONS_DIR,
+ conversation_id=conversation_id,
+ )
+
+ if include_security_analyzer:
+ conversation.set_confirmation_policy(AlwaysConfirm())
print_formatted_text(
HTML(f'✓ Agent initialized with model: {agent.llm.model}')
diff --git a/openhands-cli/tests/test_confirmation_mode.py b/openhands-cli/tests/test_confirmation_mode.py
index fc8fa10c95..ff9e03ed1d 100644
--- a/openhands-cli/tests/test_confirmation_mode.py
+++ b/openhands-cli/tests/test_confirmation_mode.py
@@ -73,8 +73,6 @@ class TestConfirmationMode:
persistence_dir=ANY,
conversation_id=mock_conversation_id,
)
- # Verify print_formatted_text was called
- mock_print.assert_called_once()
def test_setup_conversation_raises_missing_agent_spec(self) -> None:
"""Test that setup_conversation raises MissingAgentSpec when agent is not found."""
diff --git a/openhands-cli/tests/test_loading.py b/openhands-cli/tests/test_loading.py
deleted file mode 100644
index 4d0d4ead54..0000000000
--- a/openhands-cli/tests/test_loading.py
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/usr/bin/env python3
-"""
-Unit tests for the loading animation functionality.
-"""
-
-import threading
-import time
-import unittest
-from unittest.mock import patch
-
-from openhands_cli.listeners.loading_listener import (
- LoadingContext,
- display_initialization_animation,
-)
-
-
-class TestLoadingAnimation(unittest.TestCase):
- """Test cases for loading animation functionality."""
-
- def test_loading_context_manager(self):
- """Test that LoadingContext works as a context manager."""
- with LoadingContext('Test loading...') as ctx:
- self.assertIsInstance(ctx, LoadingContext)
- self.assertEqual(ctx.text, 'Test loading...')
- self.assertIsInstance(ctx.is_loaded, threading.Event)
- self.assertIsNotNone(ctx.loading_thread)
- # Give the thread a moment to start
- time.sleep(0.1)
- self.assertTrue(ctx.loading_thread.is_alive())
-
- # After exiting context, thread should be stopped
- time.sleep(0.1)
- self.assertFalse(ctx.loading_thread.is_alive())
-
- @patch('sys.stdout')
- def test_animation_writes_while_running_and_stops_after(self, mock_stdout):
- """Ensure stdout is written while animation runs and stops after it ends."""
- is_loaded = threading.Event()
-
- animation_thread = threading.Thread(
- target=display_initialization_animation,
- args=('Test output', is_loaded),
- daemon=True,
- )
- animation_thread.start()
-
- # Let it run a bit and check calls
- time.sleep(0.2)
- calls_while_running = mock_stdout.write.call_count
- self.assertGreater(calls_while_running, 0, 'Expected writes while spinner runs')
-
- # Stop animation
- is_loaded.set()
- time.sleep(0.2)
-
- animation_thread.join(timeout=1.0)
- calls_after_stop = mock_stdout.write.call_count
-
- # Wait a moment to detect any stray writes after thread finished
- time.sleep(0.2)
- self.assertEqual(
- calls_after_stop,
- mock_stdout.write.call_count,
- 'No extra writes should occur after animation stops',
- )
-
-
-if __name__ == '__main__':
- unittest.main()