Skip to content

feat(tui): show the model's thinking in the main chat - #118

Merged
openjiuwen-release-bot[bot] merged 4 commits into
openJiuwen-ai:mainfrom
openjiuwenai:sync/pr-85
Oct 25, 2026
Merged

openjiuwen-release-bot[bot] merged 4 commits into
openJiuwen-ai:mainfrom
openjiuwenai:sync/pr-85

Conversation

@openjiuwen-sync-bot

@openjiuwen-sync-bot openjiuwen-sync-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown

Paired: GitHub #118 ↔ GitCode !85

Summary

The main chat shows the model's readable thinking. When the model service returns thinking as readable text (reasoning or a reasoning summary), it streams in grey under a Thinking header above what it led to. It uses the same 200 ms throttle as the answer. Encrypted thinking has no readable text and still shows nothing.

  • A completed block stays open, and a click on its header folds it. It has no copy button, and copying replies or the conversation leaves it out.
  • New setting: Show model thinking (ui.chat.show_thinking).
    • It is in General → Appearance, on by default. A project's settings can't set it.
    • It is read as each thinking segment opens, and once per replay. Thinking already shown stays.
    • With it off, live and replayed transcripts are as before.
  • Removal:
    • A wire retry's InvocationContentAbandoned removes the thinking of the response it gave up.
    • When a turn whose only output was thinking fails or is interrupted, the next prompt removes that thinking together with the prompt it followed, as history does.
  • Resumed sessions show the saved thinking where it showed live, folded.
    • Its body is built the first time it opens, then handed to the GC freeze.
    • Shown thinking before tool calls ends the tool group above it, as it does live.
    • The ✓ of a turn that ended with structured hosted output is now a replay entry of its own. It is placed after the thinking that followed that output.
  • /fold folds and unfolds thinking together with tool groups. Thinking still being written stays open.
  • Sub-agent cards and details, and workflow node transcripts, are unchanged. ACP already sends thinking.

Refactor: AgentMessage's streaming machinery moves to a base class, StreamedMarkdownMessage, which ThinkingMessage shares.

  • That machinery is the throttled body sync, the held rows, the single layout per height change, the chrome strip fast path and the collapsed state.
  • A streamed message now hands its text to markdown at most every 200 ms instead of 150 ms.

Removal hardening: app/tui/util/removal.py::remove_all_shielded removes several widgets in one shielded operation, so a cancel (an interrupt during a retry) can't leave some of them behind. It is used for abandoned thinking, for a failed turn's prompt and its thinking, and for answer withdrawal.

Docs and notes:

  • The en and zh-Hans docs are updated: getting started, sessions, the settings guide and reference, and slash commands.
  • The AGENTS.md "Model text" section covers the TUI thinking.

The four commits are one per step: refactor → live → replay → /fold. Each was reviewed on its own, then all four together.

Known limitations

  • A bare Continue drops landed reasoning from history, but the block already shown live stays on screen.
  • Thinking from a stream that was cut off isn't saved, so a resumed session doesn't show it.
  • Mounted thinking headers aren't relabelled when the display language changes.
  • Responses reasoning-summary parts are joined without a separator.

Test plan

  • New tests:

    • tests/app/tui/widgets/chat/test_live_thinking.py, test_replayed_thinking.py and tests/app/tui/screens/main/test_streamed_thinking.py;
    • additions to test_replay_tail_first.py, tests/app/tui/util/test_removal.py and the settings tests;
    • test_replay_planner.py: one existing test now expects the structured-output ✓ as its own entry.

    For each new rule, an in-memory mutant breaks it, and its test catches that mutant.

  • Setting off: a randomized planner diff over 20,000 histories and main-vs-branch transcript comparisons (live and replay) found no difference from main.

  • Setting on: live and replayed transcripts match in 44 scenarios built from the wire codecs' real content shapes.

  • Smart Test --paths on all 51 changed files: 33039 passed, 60 skipped; architecture 1241 passed. The full suite was not run locally.

  • Gates: ruff check, ruff format --check, ty check (linux, darwin, win32) and scripts/i18n.py check.

  • Review: Codex + subagent rounds for each step and for the whole branch. The final round found nothing at medium or above.

AgentMessage's streaming machinery moves to StreamedMarkdownMessage:
the throttled body sync, the held rows that keep a view following the
bottom steady, the single transcript layout per height change, the
chrome strip fast path and the collapsed state. AgentMessage keeps its
header, copy button, timestamp and intermediate/compressed styles, and
reacts through two hooks: _refresh_header and _on_final. The chat's
thinking message will be the base's second subclass, so queries for
AgentMessage keep meaning the answer.

A streamed message now hands its text to markdown at most every 200 ms
instead of 150 ms: each update parses the whole text again and
allocates a new parse.
The main chat now shows the model's readable thinking (the reasoning and
reasoning-summary channels the backend already publishes) as a muted
"Thinking" block above what it led to, streamed with the same throttle as
the answer. A completed block stays open and folds on a header click; it
has no copy button, and copying replies leaves it out. Encrypted thinking
has no readable text and still shows nothing.

A new setting, ui.chat.show_thinking (General → Appearance, on by
default), decides it for each segment when it opens; thinking already
shown stays. A wire retry's InvocationContentAbandoned removes the blocks
of the response it gave up, in arrival order through the render gate.
When a turn whose only output was thinking failed or was interrupted, the
next prompt removes it with the prompt it followed, as history does.
Sub-agent and workflow views are unchanged.
A resumed session now shows the model's saved readable thinking where it
showed live: above the answer it led to, as a folded "Thinking" block whose
body is built the first time it opens (and then handed to the GC freeze).
Whether it shows is read once per replay from ui.chat.show_thinking; with
it off, replay is as before.

Shown thinking before tool calls ends the tool group above it, as live;
blank thinking does not. An answer that thinking followed stays the answer
unless another answer follows. Thinking in a compressed part is styled as
compressed. Thinking with no readable text still shows nothing.

The ✓ of a turn that ended with structured hosted output is now a plan
entry of its own, placed after the thinking that followed that output: live
it showed when the turn ended. It reads every tool group shown thinking
split the turn's last group into, as it reads the one group otherwise.
/fold now folds and unfolds the main chat's thinking blocks together with
the tool groups: any one open folds them all, else all open. Thinking that
is still being written stays open, as a click on its header leaves it.
Replayed thinking opening this way builds its body then, as on a click,
and thinking still waiting to be prepended takes the choice as it mounts.
The command's description, its manual page and the guide say so.
@openjiuwen-collaboration-bot

Copy link
Copy Markdown
Contributor

head_sha: 4b419e229bbf238d34e1553e9101d6d4e813e0e6

变更摘要

本 PR 在主聊天区(TUI)中原样展示模型返回的可读思考内容:新增配置项 ui.chat.show_thinking(默认开启、仅用户设置可改、支持热更新),思考内容以灰色 ThinkingMessage 块流式输出在它导向的答案之上;同时把 AgentMessage 的流式渲染机制(节流同步、行高保持、chrome 快路径、折叠状态)抽取为基类 StreamedMarkdownMessage,由思考块与答案共享,并把 HistoryReplayPlanner 扩展为可按开关回放历史思考。此外新增 InvocationContentAbandoned 事件处理,使重试放弃的响应连同其思考一起从视图移除。

主要改动

  • 新增思考消息与配置项: Settings.chat_show_thinking 以 key="ui.chat.show_thinking"、Apply.LIVE、ProjectMerge.DENY 注册,设置界面在“外观”分组新增 SettingRowSpec("ui.chat.show_thinking"),并通过 SettingsCoordinator.apply_live 与 MainScreen._apply_show_thinking 即时生效。
  • 流式渲染基类抽取: 新增 StreamedMarkdownMessage,承接 stream_update/append_text 节流同步、get_content_height 的行保持、render_lines 的 chrome strip 缓存与 collapsed reactive;AgentMessage 与新增 ThinkingMessage 继承它,节流间隔由 _STREAMED_BODY_INTERVAL_SECONDS 0.15 调整为 0.2。
  • 实时思考的挂载与移除: LiveTranscriptRenderer 新增 add_thinking_delta、finish_thinking_segment、abandon_thinking,按 segment 打开时读取开关、在文本非空白时折叠上方工具组并挂载;ChatPanel/ViewAdapter/ports.py 同步暴露这些接口,subscriptions.py 订阅 InvocationContentAbandoned,事件在 DeferredTurnEvent 与 _predates_active_run 中加入该类型。
  • 历史回放支持: HistoryReplayPlanner.build_plan/_replay_assistant 增加 show_reasoning 参数,从 text_reasoning 内容经 readable_reasoning 还原思考块并默认折叠;结构化输出的 ✓ 改为独立的重放条目 ReplayStructuredCompletion,排在随后的思考之后。
  • /fold 与安全移除: ChatPanel.toggle_fold_all 同时折叠工具组与已完成思考(流式中的保持展开),ReplayMountController.pending_folds_collapsed 覆盖待挂载条目;新增 removal.py::remove_all_shielded,用于重试放弃思考、失败轮次提示与思考、答案撤回等多控件屏蔽移除。

@openjiuwen-collaboration-bot

openjiuwen-collaboration-bot Bot commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

head_sha: 4b419e229bbf238d34e1553e9101d6d4e813e0e6

代码审查

✅ 未发现问题

@openjiuwen-collaboration-bot

Copy link
Copy Markdown
Contributor

head_sha: 4b419e229bbf238d34e1553e9101d6d4e813e0e6

任务名称 结果 日志操作
静态检查 ✅SUCCESS 点此跳转
禁用词扫描 ✅SUCCESS 点此跳转
防投毒检查 ✅SUCCESS 点此跳转
开源合规检查 ✅SUCCESS 点此跳转
UT测试 ✅SUCCESS 点此跳转
ST测试 N/A N/A
build 编译包 N/A N/A
ruff codecheck ✅SUCCESS N/A
issue关联检查 N/A N/A
pr size检查 N/A N/A

@openjiuwen-release-bot
openjiuwen-release-bot Bot merged commit cf34ba8 into openJiuwen-ai:main Oct 25, 2026
18 checks passed
@openjiuwen-release-bot

Copy link
Copy Markdown
Contributor

🤖 This pull request has been merged, and the target branch is up to date.

openjiuwen-release-bot Bot pushed a commit that referenced this pull request Oct 25, 2026
feat(tui): show the model's thinking in the main chat

Created-by: Ox7c13
Commit-by: Jiaqi (0x7c13) Liu
Merged-by: Ox7c13
Description: Paired: [GitHub #118](#118) ↔ [GitCode !85](https://gitcode.com/openJiuwen/iCode/merge_requests/85)

## Summary

**The main chat shows the model's readable thinking.** When the model service returns thinking as readable text (reasoning or a reasoning summary), it streams in grey under a **Thinking** header above what it led to. It uses the same 200 ms throttle as the answer. Encrypted thinking has no readable text and still shows nothing.
- A completed block stays open, and a click on its header folds it. It has no copy button, and copying replies or the conversation leaves it out.
- **New setting: Show model thinking** (`ui.chat.show_thinking`).
  - It is in General → Appearance, on by default. A project's settings can't set it.
  - It is read as each thinking segment opens, and once per replay. Thinking already shown stays.
  - With it off, live and replayed transcripts are as before.
- **Removal:**
  - A wire retry's `InvocationContentAbandoned` removes the thinking of the response it gave up.
  - When a turn whose only output was thinking fails or is interrupted, the next prompt removes that thinking together with the prompt it followed, as history does.
- **Resumed sessions** show the saved thinking where it showed live, folded.
  - Its body is built the first time it opens, then handed to the GC freeze.
  - Shown thinking before tool calls ends the tool group above it, as it does live.
  - The ✓ of a turn that ended with structured hosted output is now a replay entry of its own. It is placed after the thinking that followed that output.
- **`/fold`** folds and unfolds thinking together with tool groups. Thinking still being written stays open.
- Sub-agent cards and details, and workflow node transcripts, are unchanged. ACP already sends thinking.

**Refactor:** `AgentMessage`'s streaming machinery moves to a base class, `StreamedMarkdownMessage`, which `ThinkingMessage` shares.
- That machinery is the throttled body sync, the held rows, the single layout per height change, the chrome strip fast path and the collapsed state.
- A streamed message now hands its text to markdown at most every 200 ms instead of 150 ms.

**Removal hardening:** `app/tui/util/removal.py::remove_all_shielded` removes several widgets in one shielded operation, so a cancel (an interrupt during a retry) can't leave some of them behind. It is used for abandoned thinking, for a failed turn's prompt and its thinking, and for answer withdrawal.

**Docs and notes:**
- The en and zh-Hans docs are updated: getting started, sessions, the settings guide and reference, and slash commands.
- The AGENTS.md "Model text" section covers the TUI thinking.

The four commits are one per step: refactor → live → replay → `/fold`. Each was reviewed on its own, then all four together.

## Known limitations

- A bare Continue drops landed reasoning from history, but the block already shown live stays on screen.
- Thinking from a stream that was cut off isn't saved, so a resumed session doesn't show it.
- Mounted thinking headers aren't relabelled when the display language changes.
- Responses reasoning-summary parts are joined without a separator.

## Test plan

- [x] **New tests:**
  - `tests/app/tui/widgets/chat/test_live_thinking.py`, `test_replayed_thinking.py` and `tests/app/tui/screens/main/test_streamed_thinking.py`;
  - additions to `test_replay_tail_first.py`, `tests/app/tui/util/test_removal.py` and the settings tests;
  - `test_replay_planner.py`: one existing test now expects the structured-output ✓ as its own entry.

  For each new rule, an in-memory mutant breaks it, and its test catches that mutant.
- [x] **Setting off:** a randomized planner diff over 20,000 histories and main-vs-branch transcript comparisons (live and replay) found no difference from main.
- [x] **Setting on:** live and replayed transcripts match in 44 scenarios built from the wire codecs' real content shapes.
- [x] **Smart Test** `--paths` on all 51 changed files: 33039 passed, 60 skipped; architecture 1241 passed. The full suite was not run locally.
- [x] **Gates:** `ruff check`, `ruff format --check`, `ty check` (linux, darwin, win32) and `scripts/i18n.py check`.
- [x] **Review:** Codex + subagent rounds for each step and for the whole branch. The final round found nothing at medium or above.

<!-- bot4-pr-sync-meta
schema_version: 1
source: gitcode
gitcode_repo: openJiuwen/iCode
gitcode_mr: 85
github_repo: openJiuwen-ai/iCode
github_pr: 118
github_branch: sync/pr-85
-->

See merge request: openJiuwen/iCode!85
@openjiuwenai
openjiuwenai deleted the sync/pr-85 branch October 25, 2026 07:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant