feat(chat): answer the CLI's menus from the chat; Stop ends Thinking… (#74)
- A menu the CLI asks with (AskUserQuestion — one question or several,
multiSelect —, a permission or trust prompt, Codex's approval, a
first-start login menu) is read off the terminal under the chat
(`parsePaneMenu`/`readRunMenu`) and shown in the prompt box's place as a
card of buttons. A pick presses its digit (picks it, or ticks its box),
Claude's unnumbered Submit row is reached with the arrows and Enter, and
Cancel presses Esc. "Type something" opens a field in the card: its text
goes in by the arrows, Ctrl+U over anything already typed there, a
bracketed paste and Enter. That row presses no key — a second press of
its digit typed the digit into the CLI's field ("44444…"). Keys verified
on Claude Code 2.1.296 and Codex 0.161. AskUserQuestion fires no
Notification, so a menu on screen also counts as waiting (live line, tab
badge). Client only: no new route.
- #74: Esc before the model wrote anything puts the prompt back in Claude
Code's input box and writes no "[Request interrupted by user]" line, so
the transcript ended on the prompt and the chat read "Thinking…" for
15 min. The chat's Stop now ends the live line until a newer transcript
line arrives (matched by line, not by clock).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -156,6 +156,9 @@ interface MessageListProps {
|
||||
waiting?: boolean;
|
||||
/** Shows the pane's terminal, where that question is answered. */
|
||||
onOpenTerminal?: () => void;
|
||||
/** Bumped by the chat's Stop: the turn on screen is over, even when the
|
||||
* transcript never says so. */
|
||||
stopped?: number;
|
||||
}
|
||||
|
||||
/** Build a map from tool_use id → tool_result for matching */
|
||||
@@ -611,15 +614,24 @@ export function MessageList({
|
||||
live = false,
|
||||
waiting = false,
|
||||
onOpenTerminal,
|
||||
stopped = 0,
|
||||
}: MessageListProps) {
|
||||
const { t } = useTranslation("sessions");
|
||||
const toolResultMap = useMemo(() => buildToolResultMap(messages), [messages]);
|
||||
const keys = useMemo(() => messageKeys(messages), [messages]);
|
||||
const lastKey = keys[keys.length - 1] ?? null;
|
||||
// The newest line when Stop was pressed. Esc before the model wrote anything
|
||||
// puts the prompt back in Claude Code's input box and writes no
|
||||
// "[Request interrupted…]" line (issue #74), so the transcript still ends on
|
||||
// the prompt: no live line until a newer one arrives.
|
||||
const [stop, setStop] = useState<{ n: number; on: string | null }>({ n: stopped, on: null });
|
||||
if (stop.n !== stopped) setStop({ n: stopped, on: lastKey });
|
||||
const halted = stop.on !== null && stop.on === lastKey;
|
||||
const pending = useMemo(
|
||||
() => (live ? workingOn(messages, toolResultMap) : null),
|
||||
[live, messages, toolResultMap]
|
||||
() => (live && !halted ? workingOn(messages, toolResultMap) : null),
|
||||
[live, halted, messages, toolResultMap]
|
||||
);
|
||||
const stats = useMemo(() => (pending ? turnStats(messages) : null), [pending, messages]);
|
||||
const keys = useMemo(() => messageKeys(messages), [messages]);
|
||||
const items = useMemo(() => chatItems(messages, keys), [messages, keys]);
|
||||
|
||||
if (loading) {
|
||||
|
||||
Reference in New Issue
Block a user