Skip to content

feat(acp): inline thinking display, plan dedup, and processing indicator fixes#1953

Merged
piorpua merged 16 commits intomainfrom
feature/inline-thinking-display
Mar 31, 2026
Merged

feat(acp): inline thinking display, plan dedup, and processing indicator fixes#1953
piorpua merged 16 commits intomainfrom
feature/inline-thinking-display

Conversation

@kaizhou-lab
Copy link
Copy Markdown
Collaborator

Summary

  • Display ACP thinking/reasoning content inline in conversation flow (like Claude.ai), with buffered DB persistence
  • Deduplicate plan messages per turn using stable msg_id; keep plan at end of message list
  • Show "正在处理" processing indicator only during ACP init → first content token window (not during streaming)
  • Fix auto-scroll: followOutput now only checks userScrolledRef, ignoring stale isAtBottom state
  • Intercept "Compacting..." messages as tips instead of rendering as text
  • Add turnFinished guard to prevent late messages from re-triggering loading state after finish
  • Set this.status = 'finished' in onSignalEvent finish handler to fix stuck status on conversation re-entry
  • Remove leaked dispatch imports and all debug logging

Test plan

  • bun run lint — 0 errors
  • bunx tsc --noEmit — pass
  • bunx vitest run — pass (previewFileWatch failure is pre-existing on main)
  • Send message in ACP conversation, verify "正在处理" shows only until first content token
  • Verify thinking content appears inline in conversation and persists in history
  • Verify plan messages stay at bottom of list without duplicates
  • Scroll up during streaming, verify auto-scroll resumes on new content
  • Stop conversation mid-stream, verify loading state resets

Closes #1952

zk added 10 commits March 30, 2026 19:25
…tor fixes (#1952)

- Display thinking content inline in conversation flow via MessageThinking
- Buffer thinking content and persist to DB with timer-based flush
- Deduplicate plan messages per turn with stable msg_id, keep at end of list
- Intercept "Compacting..." messages as tips instead of text
- Show processing indicator only from init to first content token
- Fix auto-scroll followOutput to only check userScrolledRef
- Add turnFinished guard to prevent auto-recover after finish signal
- Set status to finished in AcpAgentManager onSignalEvent finish handler
- Remove leaked dispatch imports and debug logging
- Add thinking/plan merge support in composeMessageWithIndex

Closes #1952
@kaizhou-lab kaizhou-lab force-pushed the feature/inline-thinking-display branch from 12654a8 to 5cf7194 Compare March 31, 2026 02:03
Show meaningful tool details (file paths, commands) instead of generic
kind labels like "read". Add extractKeyParam helper and update display
layout to separate operation name from parameters.
@kaizhou-lab kaizhou-lab force-pushed the feature/inline-thinking-display branch from 5cf7194 to 0c4fb90 Compare March 31, 2026 02:05
zk added 4 commits March 31, 2026 10:23
Replace blacklist (shouldIgnoreStreamMessage) with whitelist
(isGeneratingStreamMessage) for sidebar generating detection.
Only content/start/thought/thinking/tool_group/acp_tool_call/
acp_permission/plan messages trigger the spinner. Internal signals
like slash_commands_updated no longer cause false positives.
…y layout

- Increase Virtuoso increaseViewportBy from 200 to 800 to pre-render
  more items offscreen, preventing scroll position corrections when
  variable-height items are measured on-the-fly during upward scroll
- Replace ThoughtDisplay translateY(36px) + pb-40px hack with proper
  layout: top-only border-radius (rd-t-20px), negative margin (mb--20px)
  and z-index to seamlessly merge with the input box below
…bility

- Change Shadow DOM code span font-size from 14px to 13px with 20px line-height
- Add text-13px to ToolItemDetail name and description in MessageToolGroupSummary
Add common.viewMoreLines key to all 6 locales (en-US, zh-CN, zh-TW,
ja-JP, ko-KR, tr-TR) and use t() in CodeBlock component.
@piorpua piorpua added bot:reviewing Review in progress (mutex) and removed bot:reviewing Review in progress (mutex) labels Mar 31, 2026
@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 31, 2026

Code Review:feat(acp): inline thinking display, plan dedup, and processing indicator fixes (#1953)

变更概述

本 PR 将 ACP thinking 内容从隐藏的 ThoughtDisplay 改为内联到对话流中显示(类似 Claude.ai),包含流式累积和 DB 持久化。同时修复了 plan 消息重复问题(通过 turn 级别的 stable msg_id)、处理指示器的时机问题(仅在 init→首个 content token 期间显示)、auto-scroll 的 followOutput 逻辑(不再依赖 stale isAtBottom)、finish 后 late message 误触发 loading 的问题。此外重构了 CodeBlock 的折叠逻辑和 MarkdownView 的 components memoization。


方案评估

结论:✅ 方案合理

方案正确解决了 PR 描述的多个问题:thinking 内联显示通过新的 IMessageThinking 消息类型和流式累积实现,与现有消息架构保持一致;plan dedup 通过 turn 级 currentPlanMsgId 实现,简洁有效;turnFinishedRef 守卫解决了 finish 后 auto-recover 的竞态问题。MarkdownView components memoization 是解决流式更新时组件状态丢失的正确方案。


问题清单

🟠 HIGH — AcpConnection.ts 死代码块

文件src/process/agent/acp/AcpConnection.ts,第 738–740 行

问题代码

{
  const updateType = (message.params?.update as Record<string, unknown>)?.sessionUpdate;
}

问题说明:这是一个裸块(bare block),内部声明了 updateType 但从未使用。变量作用域被限制在块内,对程序逻辑没有任何影响。这明显是调试残留代码,应当移除。

修复建议

删除第 738–740 行整个块。


🟡 MEDIUM — useAutoScroll.ts 残留 console.debug 调试日志

文件src/renderer/pages/conversation/Messages/useAutoScroll.ts,第 73–79、83、107、113 行

问题代码

console.debug(
  '[AutoScroll] followOutput:',
  result,
  'isAtBottom:',
  _isAtBottom,
  'userScrolled:',
  userScrolledRef.current
);
console.debug('[AutoScroll] atBottomStateChange:', atBottom, 'userScrolled:', userScrolledRef.current);
console.debug('[AutoScroll] guard blocked upward scroll, delta:', delta, 'guardAge:', timeSinceGuard, 'ms');
console.debug('[AutoScroll] upward scroll detected, delta:', delta, 'setting userScrolled=true');

问题说明:生产代码中残留了 4 处 console.debug 调试日志。followOutputhandleScroll 是高频调用函数(每次滚动都会触发),这些日志会在控制台产生大量输出,影响开发体验和性能。项目规范要求不保留 console.log/debug 语句。

修复建议

移除所有 4 处 console.debug 调用。


🟡 MEDIUM — CodeBlock expandedStates 模块级 Map 内存泄漏

文件src/renderer/components/Markdown/CodeBlock.tsx,第 18–23 行

问题代码

const expandedStates = new Map<string, boolean>();

function getBlockFingerprint(language: string, firstLine: string): string {
  return `${language}:${firstLine}`;
}

问题说明:模块级 Map 用于在流式更新期间持久化代码块的展开/折叠状态,这个思路是对的。但该 Map 永远不会清理条目——每个查看过的唯一代码块都会留下一条记录。虽然在单次会话中不太可能出现问题,但如果用户长时间使用(Electron 应用不会经常重启),Map 会缓慢增长。此外,fingerprint 仅基于 language + firstLine,不同代码块如果语言和首行相同会共享展开状态(碰撞)。

修复建议

可以考虑使用 WeakRef 或在对话切换时清理 Map,或者使用 LRU 策略限制大小。这不是阻塞性问题,但值得后续优化。


🔵 LOW — extractAndStripThinkTags 当前未被调用

文件src/process/task/ThinkTagDetector.ts,第 66–108 行

问题说明:新增的 extractAndStripThinkTags 函数已导出并编写了测试,但在本 PR 的实际业务代码中未被任何文件引用。可能是为后续功能预留,但当前属于未使用代码。


🔵 LOW — MessageThinking 中 formatDuration 和 formatElapsedTime 逻辑重复

文件src/renderer/pages/conversation/Messages/components/MessageThinking.tsx,第 13–26 行

问题代码

const formatDuration = (ms: number): string => {
  const seconds = Math.floor(ms / 1000);
  if (seconds < 60) return `${seconds}s`;
  const minutes = Math.floor(seconds / 60);
  const remaining = seconds % 60;
  return `${minutes}m ${remaining}s`;
};

const formatElapsedTime = (seconds: number): string => {
  if (seconds < 60) return `${seconds}s`;
  const minutes = Math.floor(seconds / 60);
  const remaining = seconds % 60;
  return `${minutes}m ${remaining}s`;
};

问题说明:两个函数逻辑完全相同,唯一区别是输入单位(ms vs seconds)。可以统一为一个函数。

修复建议

const formatSeconds = (seconds: number): string => {
  if (seconds < 60) return `${seconds}s`;
  const minutes = Math.floor(seconds / 60);
  const remaining = seconds % 60;
  return `${minutes}m ${remaining}s`;
};

// 使用时:formatSeconds(Math.floor(ms / 1000)) 和 formatSeconds(elapsedTime)

汇总

# 严重级别 文件 问题
1 🟠 HIGH AcpConnection.ts:738 死代码块(调试残留)
2 🟡 MEDIUM useAutoScroll.ts:73,83,107,113 残留 console.debug 调试日志
3 🟡 MEDIUM CodeBlock.tsx:18 expandedStates Map 无清理机制
4 🔵 LOW ThinkTagDetector.ts:66 extractAndStripThinkTags 未被调用
5 🔵 LOW MessageThinking.tsx:13 两个格式化函数逻辑重复

结论

⚠️ 有条件批准 — 存在死代码和调试日志残留,处理后可合并。


本报告由本地 pr-review skill 生成,包含完整项目上下文,无截断限制。

CONCLUSION: CONDITIONAL
IS_CRITICAL_PATH: false
PR_NUMBER: 1953

@piorpua piorpua added bot:ready-to-fix CONDITIONAL review done, waiting for bot fix bot:reviewing Review in progress (mutex) bot:fixing Fix in progress (mutex) and removed bot:reviewing Review in progress (mutex) bot:ready-to-fix CONDITIONAL review done, waiting for bot fix labels Mar 31, 2026
@piorpua piorpua removed the bot:ready-to-fix CONDITIONAL review done, waiting for bot fix label Mar 31, 2026
- Remove dead code block in AcpConnection.ts (unused updateType variable)
- Remove all console.debug statements from useAutoScroll.ts
- Add LRU-style size cap to CodeBlock expandedStates Map

Review follow-up for #1953
@piorpua
Copy link
Copy Markdown
Contributor

piorpua commented Mar 31, 2026

PR Fix 验证报告

原始 PR: #1953
修复方式: 直接推送到 feature/inline-thinking-display

# 严重级别 文件 问题 修复方式 状态
1 🟠 HIGH AcpConnection.ts:738 死代码块(调试残留) 删除未使用的裸块和 updateType 变量 ✅ 已修复
2 🟡 MEDIUM useAutoScroll.ts:73,83,107,113 残留 console.debug 调试日志 移除全部 4 处 console.debug 调用 ✅ 已修复
3 🟡 MEDIUM CodeBlock.tsx:18 expandedStates Map 无清理机制 添加 LRU 式大小上限(200 条) ✅ 已修复

总结: ✅ 已修复 3 个 | ❌ 未能修复 0 个

🔵 LOW 级别问题已跳过(不阻塞合并,修复优先级低)。

@piorpua piorpua enabled auto-merge (squash) March 31, 2026 04:58
@piorpua piorpua added bot:done Auto-merged by bot and removed bot:fixing Fix in progress (mutex) labels Mar 31, 2026
@piorpua piorpua merged commit 99c1b00 into main Mar 31, 2026
15 of 17 checks passed
@piorpua piorpua deleted the feature/inline-thinking-display branch March 31, 2026 05:03
kaizhou-lab pushed a commit that referenced this pull request Apr 1, 2026
Convert Gemini thought events to thinking messages that are displayed
inline in the conversation and persisted to the database, matching the
ACP implementation from PR #1953.

- GeminiAgentManager: add emitThinkingMessage/flushThinkingToDb, convert
  thought events to thinking messages, extract inline <think> tags from
  content, finalize thinking on finish signal
- useGeminiMessage: handle thinking message type, track hasThinkingMessage
- GeminiSendBox: hide ThoughtDisplay when thinking messages are shown inline
piorpua pushed a commit that referenced this pull request Apr 1, 2026
* feat(gemini): inline thinking display in conversation flow

Convert Gemini thought events to thinking messages that are displayed
inline in the conversation and persisted to the database, matching the
ACP implementation from PR #1953.

- GeminiAgentManager: add emitThinkingMessage/flushThinkingToDb, convert
  thought events to thinking messages, extract inline <think> tags from
  content, finalize thinking on finish signal
- useGeminiMessage: handle thinking message type, track hasThinkingMessage
- GeminiSendBox: hide ThoughtDisplay when thinking messages are shown inline

* test(gemini): add unit tests for emitThinkingMessage and flushThinkingToDb

- Cover content accumulation across multiple thought chunks
- Verify thinkingMsgId reuse within a single turn
- Assert DB flush timer is created on first chunk and cleared on done
- Confirm flushThinkingToDb returns early when thinkingMsgId is null
- Validate addOrUpdateMessage receives correct TMessage shape

Review follow-up for #2013

---------

Co-authored-by: zk <[email protected]>
Co-authored-by: zynx <>
zhuqingyv pushed a commit that referenced this pull request Apr 2, 2026
* feat(gemini): inline thinking display in conversation flow

Convert Gemini thought events to thinking messages that are displayed
inline in the conversation and persisted to the database, matching the
ACP implementation from PR #1953.

- GeminiAgentManager: add emitThinkingMessage/flushThinkingToDb, convert
  thought events to thinking messages, extract inline <think> tags from
  content, finalize thinking on finish signal
- useGeminiMessage: handle thinking message type, track hasThinkingMessage
- GeminiSendBox: hide ThoughtDisplay when thinking messages are shown inline

* test(gemini): add unit tests for emitThinkingMessage and flushThinkingToDb

- Cover content accumulation across multiple thought chunks
- Verify thinkingMsgId reuse within a single turn
- Assert DB flush timer is created on first chunk and cleared on done
- Confirm flushThinkingToDb returns early when thinkingMsgId is null
- Validate addOrUpdateMessage receives correct TMessage shape

Review follow-up for #2013

---------

Co-authored-by: zk <[email protected]>
Co-authored-by: zynx <>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:done Auto-merged by bot

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ACP thinking display: inline thinking, plan dedup, processing indicator, scroll fixes

2 participants