fix(chat): show context text before a pending question

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
This commit is contained in:
Serhii Dziupin
2026-08-05 10:33:54 +03:00
parent 34c221b07f
commit 1ad81a2c72
3 changed files with 51 additions and 1 deletions
@@ -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
|| (
@@ -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');
});
});