From be41f9f5c7cfe114edc3a2549cacc723d5b48ece Mon Sep 17 00:00:00 2001 From: Devansh Jain <31609257+devanshrj@users.noreply.github.com> Date: Mon, 16 Mar 2026 16:04:00 -0700 Subject: [PATCH] feat(cli): add thinking indicator tip bar with memory tips (#1410) Co-authored-by: Letta Code --- src/cli/components/InputRich.tsx | 66 ++++++++++++++++++-------- src/cli/helpers/thinkingMessages.ts | 17 +++++++ src/tests/cli/thinkingMessages.test.ts | 13 ++++- 3 files changed, 76 insertions(+), 20 deletions(-) diff --git a/src/cli/components/InputRich.tsx b/src/cli/components/InputRich.tsx index 7fa7074..f5a802e 100644 --- a/src/cli/components/InputRich.tsx +++ b/src/cli/components/InputRich.tsx @@ -37,6 +37,7 @@ import { getSnapshot as getSubagentSnapshot, subscribe as subscribeToSubagents, } from "../helpers/subagentState.js"; +import { getRandomThinkingTip } from "../helpers/thinkingMessages"; import { BlinkingSpinner } from "./BlinkingSpinner.js"; import { colors } from "./colors"; import { InputAssist } from "./InputAssist"; @@ -527,6 +528,8 @@ const StreamingStatus = memo(function StreamingStatus({ const [shimmerOffset, setShimmerOffset] = useState(-3); const [elapsedMs, setElapsedMs] = useState(0); + const [tipMessage, setTipMessage] = useState(""); + const tipInitializedRef = useRef(false); const streamStartRef = useRef(null); useEffect(() => { @@ -578,6 +581,18 @@ const StreamingStatus = memo(function StreamingStatus({ setElapsedMs(0); }, [streaming, visible]); + useEffect(() => { + if (streaming && visible) { + if (!tipInitializedRef.current) { + setTipMessage(getRandomThinkingTip()); + tipInitializedRef.current = true; + } + return; + } + + tipInitializedRef.current = false; + }, [streaming, visible]); + const estimatedTokens = charsToTokens(tokenCount); const totalElapsedMs = elapsedBaseMs + elapsedMs; const shouldShowTokenCount = @@ -650,33 +665,46 @@ const StreamingStatus = memo(function StreamingStatus({ hintColor(" (") + hintBold("esc") + hintColor(` to interrupt${suffix}`) ); }, [interruptRequested, statusHintSuffix]); + const tipLineText = useMemo(() => { + return truncateEnd(`⎿ Tip: ${tipMessage}`, statusContentWidth); + }, [tipMessage, statusContentWidth]); if (!streaming || !visible) { return null; } return ( - - - - {animate ? : "●"} - - - - - + + + + + {animate ? : "●"} + - {hintColumnWidth > 0 && ( - - {statusHintText} + + + - )} - + {hintColumnWidth > 0 && ( + + {statusHintText} + + )} + + + + + + + + {tipLineText} + + ); diff --git a/src/cli/helpers/thinkingMessages.ts b/src/cli/helpers/thinkingMessages.ts index 5017c5f..af8072e 100644 --- a/src/cli/helpers/thinkingMessages.ts +++ b/src/cli/helpers/thinkingMessages.ts @@ -43,6 +43,14 @@ const THINKING_VERBS = [ "internalizing", ] as const; +export const THINKING_TIPS = [ + "Use /remember [instructions] to remember something from the conversation.", + "Use /palace to inspect your agent's memory palace.", + "Use /reflect to launch a background reflection agent to update memory.", + "Use /search [query] to search messages across all agents.", + "Use /init to initialize (or re-init) your agent's memory.", +] as const; + type ThinkingVerb = (typeof THINKING_VERBS)[number]; const PAST_TENSE_VERBS: Record = { @@ -95,6 +103,11 @@ function getRandomVerb(): string { return THINKING_VERBS[index] ?? "thinking"; } +function getRandomTip(): string { + const index = Math.floor(Math.random() * THINKING_TIPS.length); + return THINKING_TIPS[index] ?? ""; +} + // Get a random thinking verb phrase (e.g., "is thinking", "is processing") export function getRandomThinkingVerb(): string { return `is ${getRandomVerb()}`; @@ -117,3 +130,7 @@ export function getRandomThinkingMessage(agentName?: string | null): string { // Fallback to capitalized verb if no agent name return verb.charAt(0).toUpperCase() + verb.slice(1); } + +export function getRandomThinkingTip(): string { + return getRandomTip(); +} diff --git a/src/tests/cli/thinkingMessages.test.ts b/src/tests/cli/thinkingMessages.test.ts index 76ac67a..853322e 100644 --- a/src/tests/cli/thinkingMessages.test.ts +++ b/src/tests/cli/thinkingMessages.test.ts @@ -1,5 +1,9 @@ import { describe, expect, test } from "bun:test"; -import { getRandomThinkingMessage } from "../../cli/helpers/thinkingMessages"; +import { + getRandomThinkingMessage, + getRandomThinkingTip, + THINKING_TIPS, +} from "../../cli/helpers/thinkingMessages"; describe("Thinking messages", () => { test("returns formatted message with agent name", () => { @@ -43,4 +47,11 @@ describe("Thinking messages", () => { // Should have more than 1 unique message (with high probability) expect(messages.size).toBeGreaterThan(1); }); + + test("returns a tip from the configured tip list", () => { + const tip = getRandomThinkingTip(); + + expect(tip.length).toBeGreaterThan(0); + expect((THINKING_TIPS as readonly string[]).includes(tip)).toBe(true); + }); });