From 6861bf4a9fc36fc9bfcee744dc38355daf6f085c Mon Sep 17 00:00:00 2001 From: Merge Sim Date: Thu, 10 Sep 2026 20:01:41 -0700 Subject: [PATCH] codex: mark streamed reasoning as reasoning too, and pin the provider markers The settled reasoning item carried the marker but the streaming one did not, so a live Codex turn - the only time the indicator is on screen - never read as reasoning. Both paths now stamp it; a plan document keeps its own presentation and must never read as reasoning. --- .../claude-structured-journal-translation.test.ts | 15 +++++++++------ .../codex/codex-notice-item-translation.test.ts | 10 ++++++++-- .../codex-structured-item-translation.test.ts | 14 ++++++++++++-- .../codex/codex-structured-item-translation.ts | 11 ++++++++++- ...uctured-journal-translation-settlement.test.ts | 5 ++++- 5 files changed, 43 insertions(+), 12 deletions(-) diff --git a/src/main/claude/claude-structured-journal-translation.test.ts b/src/main/claude/claude-structured-journal-translation.test.ts index 050fccc42b6..671707d207c 100644 --- a/src/main/claude/claude-structured-journal-translation.test.ts +++ b/src/main/claude/claude-structured-journal-translation.test.ts @@ -2,11 +2,12 @@ import { mkdtemp, rm } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { - AgentJournalItemBody, - AgentJournalItemIdentity, - AgentJournalRenderItem, - AgentSessionJournalIdentity +import { + AGENT_JOURNAL_THINKING_PRESENTATION, + type AgentJournalItemBody, + type AgentJournalItemIdentity, + type AgentJournalRenderItem, + type AgentSessionJournalIdentity } from '../../shared/agent-session-journal-types' import { agentJournalItemKey } from '../../shared/agent-session-journal-item-key' import { activeStructuredAgentSessionTurnId } from '../../shared/structured-agent-session-projection' @@ -568,7 +569,9 @@ describe('Claude structured journal translation', () => { expect(state.items.at(-1)?.body).toEqual({ kind: 'status', - text: boundInlineText(thinking, DEFAULT_JOURNAL_PAYLOAD_LIMITS).text + text: boundInlineText(thinking, DEFAULT_JOURNAL_PAYLOAD_LIMITS).text, + // Marks the row as the model reasoning, so a client can label the live turn. + presentation: AGENT_JOURNAL_THINKING_PRESENTATION }) }) diff --git a/src/main/codex/codex-notice-item-translation.test.ts b/src/main/codex/codex-notice-item-translation.test.ts index 15f638c4015..cb6838ae1a4 100644 --- a/src/main/codex/codex-notice-item-translation.test.ts +++ b/src/main/codex/codex-notice-item-translation.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from 'vitest' import { codexItemBody, codexStreamingJournalItem } from './codex-structured-item-translation' import { AgentJournalItemBodySchema } from '../../shared/agent-session-journal-schemas' +import { AGENT_JOURNAL_THINKING_PRESENTATION } from '../../shared/agent-session-journal-types' import { projectStructuredItemsToNativeChat } from '../../shared/structured-agent-session-projection' describe('plan document translation', () => { @@ -23,10 +24,15 @@ describe('plan document translation', () => { codexItemBody({ id: 'r', type: 'reasoning', summary: ['Thinking through the problem.'] }) ).toEqual({ kind: 'status', - text: 'Thinking through the problem.' + text: 'Thinking through the problem.', + presentation: AGENT_JOURNAL_THINKING_PRESENTATION }) expect(codexStreamingJournalItem({ id: 'r', type: 'reasoning' }, 'Thinking…')).toEqual({ - body: { kind: 'status', text: 'Thinking…' }, + body: { + kind: 'status', + text: 'Thinking…', + presentation: AGENT_JOURNAL_THINKING_PRESENTATION + }, handled: true }) }) diff --git a/src/main/codex/codex-structured-item-translation.test.ts b/src/main/codex/codex-structured-item-translation.test.ts index 201df58bc90..09eace30e2e 100644 --- a/src/main/codex/codex-structured-item-translation.test.ts +++ b/src/main/codex/codex-structured-item-translation.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from 'vitest' import { agentJournalItemKey } from '../../shared/agent-session-journal-item-key' +import { AGENT_JOURNAL_THINKING_PRESENTATION } from '../../shared/agent-session-journal-types' import { briefToolArg, createToolInputDisplay, @@ -592,12 +593,17 @@ describe('codex item bodies', () => { body: { kind: 'status', text, presentation: 'plan-document' }, handled: true }) + // A plan is a durable artifact, so it must never read as the model reasoning now. + expect(codexItemBody({ type: 'plan', id: 'plan-document', text })).not.toMatchObject({ + presentation: AGENT_JOURNAL_THINKING_PRESENTATION + }) }) it('renders reasoning as status and exposes an unknown item as a provider frame', () => { expect(codexItemBody({ type: 'reasoning', id: 'r', text: 'thinking' })).toEqual({ kind: 'status', - text: 'thinking' + text: 'thinking', + presentation: AGENT_JOURNAL_THINKING_PRESENTATION }) expect(codexItemBody({ type: 'reasoning', id: 'r' })).toBeNull() expect(codexItemBody({ type: 'agentMessage', id: 'm', text: '' })).toBeNull() @@ -830,7 +836,11 @@ describe('codex item bodies', () => { summary: ['first', 'second'], content: [{ text: 'fallback' }] }) - ).toEqual({ kind: 'status', text: 'first\nsecond' }) + ).toEqual({ + kind: 'status', + text: 'first\nsecond', + presentation: AGENT_JOURNAL_THINKING_PRESENTATION + }) }) it('refuses a value that is not a thread item at all', () => { diff --git a/src/main/codex/codex-structured-item-translation.ts b/src/main/codex/codex-structured-item-translation.ts index e88a1efbb2f..2177276da8c 100644 --- a/src/main/codex/codex-structured-item-translation.ts +++ b/src/main/codex/codex-structured-item-translation.ts @@ -337,5 +337,14 @@ export function codexStreamingJournalItem(item: CodexThreadItem, text: string): } } const bounded = boundInlineText(text, DEFAULT_JOURNAL_PAYLOAD_LIMITS) - return { body: { kind: 'status', text: bounded.text }, handled: true } + return { + body: { + kind: 'status', + text: bounded.text, + // Streaming reasoning is the live turn's reasoning; the settled item says so + // too, and the indicator reads the tail of the journal while it is still open. + ...(item.type === 'reasoning' ? { presentation: AGENT_JOURNAL_THINKING_PRESENTATION } : {}) + }, + handled: true + } } diff --git a/src/main/codex/codex-structured-journal-translation-settlement.test.ts b/src/main/codex/codex-structured-journal-translation-settlement.test.ts index f8a5c7a1671..734bd0c2a67 100644 --- a/src/main/codex/codex-structured-journal-translation-settlement.test.ts +++ b/src/main/codex/codex-structured-journal-translation-settlement.test.ts @@ -25,6 +25,7 @@ import { CODEX_USER_INPUT_METHOD } from './codex-structured-prompt-replies' import type { CodexStructuredSessionEvent } from './codex-structured-session-adapter' +import { AGENT_JOURNAL_THINKING_PRESENTATION } from '../../shared/agent-session-journal-types' const SESSION_ID = 'session-1' const THREAD_ID = 'thread-abc' @@ -800,7 +801,9 @@ describe('codex journal translation', () => { const reduced = new Map(tap.rows.map((row) => [row.key, row.body])) expect(reduced.get('orca:codex-item%3Athread-abc%3Ar-1')).toEqual({ kind: 'status', - text: 'thinking' + text: 'thinking', + // A streamed reasoning row is still reasoning, so the live indicator can read it. + presentation: AGENT_JOURNAL_THINKING_PRESENTATION }) expect(reduced.get('orca:codex-item%3Athread-abc%3Apatch-1')).toMatchObject({ kind: 'diff',