Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion packages/squad-cli/src/cli/shell/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ export const App: React.FC<AppProps> = ({ registry, renderer, teamRoot, version,
return undefined;
}, [messages, processing]);

// True when there is prior conversation history (at least one agent response).
const hasConversation = useMemo(
() => messages.some(m => m.role === 'agent'),
[messages],
);

// Only archived (overflow) messages go to Static scrollback.
// Current messages stay in the live region so the user can always see
// the recent conversation without scrolling. This prevents the
Expand Down Expand Up @@ -467,7 +473,7 @@ export const App: React.FC<AppProps> = ({ registry, renderer, teamRoot, version,
count to prevent overflow into the InputPrompt area. */}
<Box flexDirection="column" flexGrow={1}>
<AgentPanel agents={agents} streamingContent={streamingContent} />
<MessageStream messages={messages} agents={agents} streamingContent={streamingContent} processing={processing} activityHint={activityHint || mentionHint} agentActivities={agentActivities} thinkingPhase={thinkingPhase} maxVisible={maxVisible} />
<MessageStream messages={messages} agents={agents} streamingContent={streamingContent} processing={processing} activityHint={activityHint || mentionHint} agentActivities={agentActivities} thinkingPhase={thinkingPhase} maxVisible={maxVisible} hasConversation={hasConversation} />
</Box>
{/* Fixed input box at bottom — Copilot/Claude style */}
<Box marginTop={1} borderStyle={noColor ? undefined : 'round'} borderColor={noColor ? undefined : 'cyan'} paddingX={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ interface MessageStreamProps {
agentActivities?: Map<string, string>;
thinkingPhase?: ThinkingPhase;
maxVisible?: number;
/** When true, thinking indicator shows conversation-aware phrases. */
hasConversation?: boolean;
}

/** Format elapsed seconds for response timestamps. */
Expand Down Expand Up @@ -199,6 +201,7 @@ export const MessageStream: React.FC<MessageStreamProps> = ({
agentActivities,
thinkingPhase,
maxVisible = 50,
hasConversation = false,
}) => {
const visible = messages.slice(-maxVisible);
const visibleOffset = Math.max(0, messages.length - maxVisible);
Expand Down Expand Up @@ -327,6 +330,7 @@ export const MessageStream: React.FC<MessageStreamProps> = ({
elapsedMs={elapsedMs}
activityHint={activityHint}
phase={thinkingPhase}
hasConversation={hasConversation}
/>
)}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ export interface ThinkingIndicatorProps {
elapsedMs: number;
activityHint?: string;
phase?: ThinkingPhase;
/** When true, cycles conversation-aware phrases instead of generic ones. */
hasConversation?: boolean;
}

/** Rotating thinking phrases — cycled every few seconds to keep the UI alive. */
Expand All @@ -40,6 +42,25 @@ export const THINKING_PHRASES = [
'Exploring possibilities',
];

/** Context-aware phrases shown when conversation history exists. */
export const CONVERSATION_PHRASES = [
'Reviewing conversation context',
'Connecting to previous work',
'Analyzing how this relates',
'Checking conversation thread',
'Considering prior context',
'Building on earlier discussion',
'Mapping to your session',
'Evaluating options',
'Consulting the team',
'Synthesizing a response',
'Weighing trade-offs',
'Gathering context',
'Crafting a plan',
'Connecting the dots',
'Reading the codebase',
];

/** Map phase to its default label. */
function phaseLabel(phase: ThinkingPhase): string {
switch (phase) {
Expand Down Expand Up @@ -72,6 +93,7 @@ export const ThinkingIndicator: React.FC<ThinkingIndicatorProps> = ({
elapsedMs,
activityHint,
phase = 'routing',
hasConversation = false,
}) => {
const noColor = isNoColor();
const [frame, setFrame] = useState(0);
Expand All @@ -86,14 +108,16 @@ export const ThinkingIndicator: React.FC<ThinkingIndicatorProps> = ({
return () => clearInterval(timer);
}, [isThinking, noColor]);

const phrases = hasConversation ? CONVERSATION_PHRASES : THINKING_PHRASES;

// Rotate thinking phrases every 3 seconds
useEffect(() => {
if (!isThinking) { setPhraseIndex(0); return; }
const timer = setInterval(() => {
setPhraseIndex(i => (i + 1) % THINKING_PHRASES.length);
setPhraseIndex(i => (i + 1) % phrases.length);
}, 3000);
return () => clearInterval(timer);
}, [isThinking]);
}, [isThinking, phrases]);

// Reset frame when thinking starts
useEffect(() => {
Expand All @@ -108,7 +132,7 @@ export const ThinkingIndicator: React.FC<ThinkingIndicatorProps> = ({

// Resolve the display label: activity hint > rotating phrase > phase label
const displayLabel = activityHint ?? (
phase === 'connecting' ? phaseLabel(phase) : `${THINKING_PHRASES[phraseIndex]}...`
phase === 'connecting' ? phaseLabel(phase) : `${phrases[phraseIndex]}...`
);

// NO_COLOR: no color props, use text labels
Expand Down
Loading