From 1ad81a2c7223076b327b783dc62857fafcbc62c6 Mon Sep 17 00:00:00 2001 From: Serhii Dziupin Date: Wed, 5 Aug 2026 10:33:54 +0300 Subject: [PATCH 1/2] fix(chat): show context text before a pending question MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A turn blocked on a question never reaches finish 'stop', so in sorted render mode the model's text was classified as justification and the inline-text deferral rule hid it inside the collapsible Activity group until the turn completed — with a pending question that never happens, leaving the context produced before the question invisible (OpenCode shows it inline). Keep text inline for messages that contain a question tool part: exclude them from justification classification and from the sorted-mode text deferral. Refs OPE-199 --- .../chat/lib/turns/projectTurnActivity.ts | 11 +++++++ .../chat/lib/turns/projectTurnRecords.test.ts | 30 +++++++++++++++++++ .../components/chat/message/MessageBody.tsx | 11 ++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts b/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts index 86921e31..dd43fc86 100644 --- a/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts +++ b/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts @@ -97,6 +97,16 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit input.assistantMessages.forEach((message) => { const finish = getMessageFinish(message); const messageHasTool = message.parts.some((part) => part.type === 'tool'); + // A turn blocked on a question never reaches finish === 'stop' (the + // user must answer first). Treating the text the model produced + // before the question as 'justification' would bury it inside the + // collapsible Activity group — the context stays invisible until the + // turn completes (OPE-199). Keep it inline like OpenCode. + const messageHasQuestion = message.parts.some((part) => ( + part.type === 'tool' + && typeof part.tool === 'string' + && part.tool.toLowerCase() === 'question' + )); const messageIsCompactionSummary = isCompactionSummaryMessage(message); message.parts.forEach((part, partIndex) => { @@ -137,6 +147,7 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit input.showTextJustificationActivity && part.type === 'text' && text + && !messageHasQuestion && ( messageIsCompactionSummary || ( diff --git a/packages/ui/src/components/chat/lib/turns/projectTurnRecords.test.ts b/packages/ui/src/components/chat/lib/turns/projectTurnRecords.test.ts index f7d9f22b..f5831138 100644 --- a/packages/ui/src/components/chat/lib/turns/projectTurnRecords.test.ts +++ b/packages/ui/src/components/chat/lib/turns/projectTurnRecords.test.ts @@ -221,4 +221,34 @@ describe('projectTurnRecords', () => { const finalActivity = turn?.activityParts.find((activity) => activity.messageId === 'a2'); expect(finalActivity).toBe(undefined); }); + + test('keeps text inline (not justification) when a message is blocked on a pending question', () => { + const user = createMessageEntry({ id: 'u1', role: 'user', createdAt: 1 }); + user.parts = [{ id: 'p1', type: 'text', text: 'prompt' } as Part]; + const assistant = createMessageEntry({ id: 'a1', role: 'assistant', parentID: 'u1', createdAt: 2 }); + // The turn is blocked waiting for the user's answer: no finish and a + // pending question tool part, with context text before the question. + assistant.parts = [ + { id: 'ap1', type: 'text', text: 'context before the question' } as Part, + { + id: 'ap2', + type: 'tool', + callID: 'c1', + tool: 'question', + state: { status: 'pending' }, + } as Part, + ]; + + const projection = projectTurnRecords([user, assistant], { + showTextJustificationActivity: true, + }); + + const turn = projection.turns[0]; + expect(turn).toBeDefined(); + const textActivity = turn?.activityParts.find((activity) => activity.partIndex === 0); + expect(textActivity?.kind).not.toBe('justification'); + // The question tool itself still participates in the activity group. + const questionActivity = turn?.activityParts.find((activity) => activity.partIndex === 1); + expect(questionActivity?.kind).toBe('tool'); + }); }); diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx index ae701111..7a3c96ad 100644 --- a/packages/ui/src/components/chat/message/MessageBody.tsx +++ b/packages/ui/src/components/chat/message/MessageBody.tsx @@ -1690,7 +1690,16 @@ const AssistantMessageBody = React.memo(({ && hasAnchoredActivitySegments && Boolean(toggleActivityGroup); - const shouldDeferSortedInlineText = isSortedRenderMode && !hasStopFinish; + // A message that asked a question is blocked until the user answers — it + // never reaches finish === 'stop', so the normal "defer text until final + // output" rule would hide the context the model produced before the + // question indefinitely (OPE-199). Render such messages' text inline, + // matching OpenCode's display. + const hasQuestionTool = React.useMemo(() => { + return toolParts.some((toolPart) => toolPart.tool === 'question'); + }, [toolParts]); + + const shouldDeferSortedInlineText = isSortedRenderMode && !hasStopFinish && !hasQuestionTool; const showErrorMessage = Boolean(errorMessage); const errorIconName = errorVariant === 'info' ? 'information' : 'error-warning'; const shouldShowMessageActions = hasCopyableText; From 5fd86a420de80ee86dc8c0a06ae1f44dc1e1ea3f Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Sat, 29 Aug 2026 00:27:06 +0300 Subject: [PATCH 2/2] fix(chat): match question tool name exactly Use an exact-match comparison for the question tool name instead of toLowerCase(), matching the convention used elsewhere in this file. --- .../ui/src/components/chat/lib/turns/projectTurnActivity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts b/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts index dd43fc86..e88bbd7b 100644 --- a/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts +++ b/packages/ui/src/components/chat/lib/turns/projectTurnActivity.ts @@ -105,7 +105,7 @@ export const projectTurnActivity = (input: ProjectActivityInput): ProjectActivit const messageHasQuestion = message.parts.some((part) => ( part.type === 'tool' && typeof part.tool === 'string' - && part.tool.toLowerCase() === 'question' + && part.tool === 'question' )); const messageIsCompactionSummary = isCompactionSummaryMessage(message);