Skip to content

refactor(agent-plan): use session-scoped live float - #1909

Merged
zerob13 merged 5 commits into
devfrom
refactor/agent-plan-live-float
Jul 9, 2026
Merged

refactor(agent-plan): use session-scoped live float#1909
zerob13 merged 5 commits into
devfrom
refactor/agent-plan-live-float

Conversation

@yyhhyyyyyy

@yyhhyyyyyy yyhhyyyyyy commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator
  • Move agent plans to a live float-only lifecycle
  • Stop producing and rendering inline assistant plan blocks
  • Keep live plan snapshots scoped by session across session switches
  • Preserve early plan snapshots until session status catches up
  • Hide only internal update_plan tool calls
  • Clear ACP per-session plan and tool state after stream cleanup
  • Update docs and regression tests

Summary by CodeRabbit

  • New Features
    • Agent progress is now a live, session-scoped overlay using float-only plan snapshots with revision tracking.
  • Bug Fixes
    • “Plan” blocks and internal update-plan tool calls are no longer rendered in chat messages.
    • Switching sessions, reload, and stop/abort/max-steps flows no longer persist or rehydrate old plan state; terminal plan states are handled correctly.
    • Deleting messages now clears only the related live plan snapshot.
  • Documentation
    • Updated architecture/flow specs for float-only live plan behavior.
  • Tests
    • Expanded coverage for plan snapshot lifecycle, revisions, session scoping, and rendering/filtering behavior.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3016ec01-0ef3-41f0-a65e-3fa1d6a572fc

📥 Commits

Reviewing files that changed from the base of the PR and between 1a74447 and ab3dac2.

📒 Files selected for processing (4)
  • src/main/presenter/agentRuntimePresenter/dispatch.ts
  • src/main/presenter/agentRuntimePresenter/types.ts
  • src/renderer/src/components/chat/messageListItems.ts
  • src/renderer/src/pages/ChatPage.vue
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/presenter/agentRuntimePresenter/types.ts
  • src/renderer/src/components/chat/messageListItems.ts
  • src/main/presenter/agentRuntimePresenter/dispatch.ts
  • src/renderer/src/pages/ChatPage.vue

📝 Walkthrough

Walkthrough

This PR changes agent plan handling from persisted assistant plan blocks to session-scoped live plan snapshots. Runtime, ACP mapping, renderer display, ChatPage session restore, docs, and tests are updated to match the float-only flow.

Changes

Agent plan float refactor

Layer / File(s) Summary
Docs and specs
docs/ARCHITECTURE.md, docs/FLOWS.md, docs/architecture/agent-plan-task-refactor/spec.md, docs/features/acp-v1-reliability/spec.md, docs/features/message-delete-confirmation/spec.md
Docs and specs describe plan updates as live session-scoped snapshots and switch restore guidance to loadMessagesForSession().
Shared plan normalization
src/shared/types/agent-plan.ts, test/main/contracts/zodV4Migration.test.ts
Adds normalization helpers for plan status, entries, and terminal reasons, with tests for entry normalization.
Runtime snapshot pipeline
src/main/presenter/agentRuntimePresenter/accumulator.ts, dispatch.ts, process.ts, types.ts, test/main/presenter/agentRuntimePresenter/*
Tracks latestAgentPlanSnapshot in stream state, publishes plan updates without plan blocks, stamps terminal snapshots, and updates runtime tests.
ACP plan revisions
src/main/presenter/llmProviderPresenter/acp/acpContentMapper.ts, providers/acpProvider.ts, test/main/presenter/llmProviderPresenter/acpContentMapper.test.ts
Tracks per-session plan revisions, clears session state on stream teardown, and emits plan events without creating plan blocks.
Debug mock plan removal
src/main/routes/debug/createMockChatSession.ts
Removes the mock assistant plan block type and generation path.
Renderer filtering and plan component removal
src/renderer/src/components/chat/messageListItems.ts, src/renderer/src/components/message/MessageItemAssistant.vue, src/renderer/src/composables/message/useMessageWindow.ts, src/renderer/src/composables/useAgentPlanStatus.ts, test/renderer/components/message/*
Adds renderability helpers, removes plan-block rendering, and updates message tests and height estimation.
ChatPage plan linger lifecycle
src/renderer/src/pages/ChatPage.vue, test/renderer/components/ChatPage.test.ts
Replaces plan rehydration with session loading, adds per-session linger/timer handling for plan snapshots, and updates session-switch tests.

Estimated code review effort: 4 (Complex) | ~75 minutes

Possibly related PRs

  • ThinkInAIXYZ/deepchat#1156: Introduces ACP plan support and dedicated plan message blocks that this PR removes in favor of live snapshots.
  • ThinkInAIXYZ/deepchat#1635: Covers the same update_plan / chat.plan.updated live-plan path and internal-tool handling.
  • ThinkInAIXYZ/deepchat#1817: Touches the same runtime and ChatPage plan flow but keeps persisted plan blocks instead of float-only snapshots.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.53% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately captures the main change: agent plans now use a session-scoped live float lifecycle.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/agent-plan-live-float

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/presenter/agentRuntimePresenter/accumulator.ts`:
- Around line 110-121: The plan branch in accumulator.ts updates trailing
narrative block status but does not mark the state as dirty. In the `plan` case,
after calling `finalizeTrailingPendingNarrativeBlocks(state.blocks)`, set
`state.dirty = true` before updating `state.latestAgentPlanSnapshot`, matching
the behavior in `tool_call_start`, `image_data`, and `error` so the renderer
flushes the finalized block state.

In `@src/shared/types/agent-plan.ts`:
- Around line 62-65: The step extraction in the agent plan parser currently
treats a blank value.step as final and returns null even when value.content has
usable text. Update the logic in the step normalization path to prefer
value.step when it contains non-whitespace text, but fall back to value.content
when step is empty or only spaces, so entries with valid plan text are
preserved. Keep the trimming and null check behavior in the same parsing flow so
the final step value reflects whichever field actually has content.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 74a8cfab-8707-4763-acfc-c70b2f5e0e1a

📥 Commits

Reviewing files that changed from the base of the PR and between a13a735 and 2d7994d.

📒 Files selected for processing (29)
  • docs/ARCHITECTURE.md
  • docs/FLOWS.md
  • docs/architecture/agent-plan-task-refactor/spec.md
  • docs/features/acp-v1-reliability/spec.md
  • docs/features/message-delete-confirmation/spec.md
  • src/main/presenter/agentRuntimePresenter/accumulator.ts
  • src/main/presenter/agentRuntimePresenter/dispatch.ts
  • src/main/presenter/agentRuntimePresenter/process.ts
  • src/main/presenter/agentRuntimePresenter/types.ts
  • src/main/presenter/llmProviderPresenter/acp/acpContentMapper.ts
  • src/main/presenter/llmProviderPresenter/providers/acpProvider.ts
  • src/main/routes/debug/createMockChatSession.ts
  • src/renderer/src/components/chat/messageListItems.ts
  • src/renderer/src/components/message/MessageBlockPlan.vue
  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/renderer/src/composables/message/useMessageWindow.ts
  • src/renderer/src/composables/useAgentPlanStatus.ts
  • src/renderer/src/pages/ChatPage.vue
  • src/shared/chat/agentPlanBlock.ts
  • src/shared/types/agent-plan-block.ts
  • src/shared/types/agent-plan.ts
  • test/main/presenter/agentRuntimePresenter/accumulator.test.ts
  • test/main/presenter/agentRuntimePresenter/dispatch.test.ts
  • test/main/presenter/agentRuntimePresenter/process.test.ts
  • test/main/presenter/llmProviderPresenter/acpContentMapper.test.ts
  • test/main/shared/agentPlanBlock.test.ts
  • test/renderer/components/ChatPage.test.ts
  • test/renderer/components/message/MessageBlockBasics.test.ts
  • test/renderer/components/message/MessageItemAssistant.test.ts
💤 Files with no reviewable changes (7)
  • test/main/shared/agentPlanBlock.test.ts
  • src/renderer/src/composables/message/useMessageWindow.ts
  • src/shared/types/agent-plan-block.ts
  • src/renderer/src/components/message/MessageBlockPlan.vue
  • src/shared/chat/agentPlanBlock.ts
  • test/renderer/components/message/MessageBlockBasics.test.ts
  • src/main/routes/debug/createMockChatSession.ts

Comment thread src/main/presenter/agentRuntimePresenter/accumulator.ts
Comment thread src/shared/types/agent-plan.ts Outdated

@zerob13 zerob13 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

这个 PR 将 agent plan 从「实时 float + 持久化正文 block」双轨模式简化为「仅实时 float」单轨模式,参考 Codex 的瞬态进度 UI 设计。整体架构决策合理,实现质量高,但有几个关键细节需要确认。

✅ 优点

  1. 架构简化彻底:删除了 MessageBlockPlan.vueagentPlanBlock.tsagent-plan-block.ts 等冗余组件和类型
  2. 生命周期清晰:session-scoped live plan snapshot,切换会话时保留内存状态但不持久化
  3. 终态处理完善terminalReason 覆盖 complete/stop/error/max_steps/aborted 五种终态
  4. 测试覆盖全面:主进程和渲染进程测试都更新了,覆盖新的 float-only 行为

⚠️ 主要问题

1. Legacy Plan Block 兼容性处理不完整 🔄

export function isRenderableAssistantBlock(block: DisplayAssistantMessageBlock): boolean {
  if (block.type === 'plan') {
    return false  // ✅ 过滤掉旧 plan block
  }
  // ...
}

问题:虽然正确过滤了 type:'plan' block,但 MessageItemAssistant.vue 可能渲染空消息(只有 plan block 的旧消息)。

const filteredBlocks = computed(() => filterRenderableAssistantBlocks(message.content))
// ⚠️ 如果 filteredBlocks 为空,会渲染空的 message card 还是完全隐藏?

建议:在 MessageList.vue 或 display message 转换层添加 hasRenderableAssistantBlocks 检查,完全跳过渲染纯 plan 消息。

2. Plan Float Linger 清理时序依赖 ⏱️

const PLAN_FLOAT_CLEAR_DELAY_MS = 1200  // ⚠️ 硬编码延迟

function schedulePlanSnapshotClear(sessionId: string) {
  const existingTimer = planSnapshotClearTimers.get(sessionId)
  if (existingTimer !== undefined) {
    window.clearTimeout(existingTimer)
  }
  const timer = window.setTimeout(() => {
    agentPlanStore.clearPlanSnapshot(sessionId)
    planSnapshotClearTimers.delete(sessionId)
  }, PLAN_FLOAT_CLEAR_DELAY_MS)
  planSnapshotClearTimers.set(sessionId, timer)
}

风险

  1. 如果用户在 1.2 秒内快速切换回原会话,可能看到 plan 闪现后消失
  2. 多次快速切换可能累积大量未清理的 timer

建议

  • 切换会话时立即取消所有旧会话的 linger timer
  • 或使用 onUnmounted 统一清理

3. ACP Content Mapper 的 Plan Event 处理 🎯

case 'plan':
  if (streamEvent.plan) {
    this.emitPlanUpdate(streamEvent.plan, event.index)
  }
  break  // ✅ 只发 event,不产生 block

问题:代码中只有 case 'plan' 分支,但没看到 emitPlanUpdate 实现。需要确认:

  • 是否正确映射到 chat.plan.updated IPC 事件?
  • terminalReason 是否正确传递到前端?

建议:补充 emitPlanUpdate 的实现细节到 PR 描述,或添加单测覆盖。

4. Session Switch 不清除 Live Plan 的副作用 💾

// docs/architecture/agent-plan-task-refactor/spec.md
5. session switch 只加载消息历史,不从旧 type:'plan' block 恢复;
   但不清除当前 app 运行内已有的 session-scoped live snapshot。

场景问题

  • Session A 正在运行,显示 plan float
  • 切换到 Session B(无 plan)
  • 再切回 Session A
  • 预期:恢复 A 的 live plan(如果仍在内存中)
  • 实际:可能因为 beginTurn 已清除而丢失
watch(
  () => props.sessionId,
  async (newSessionId, oldSessionId) => {
    // ⚠️ 切换时调用 beginTurn,会清除 snapshot
    agentPlanStore.beginTurn(newSessionId)
  }
)

建议:明确 beginTurn 的语义:

  • 如果是「清除旧 plan」,则不符合 spec 的「保留 live snapshot」承诺
  • 如果是「准备新轮次」,则应该只在实际发送消息时调用,而不是切换会话时

5. Internal Tool Call 标记不一致 🏷️

export function isInternalAssistantToolCallBlock(block: DisplayAssistantMessageBlock): boolean {
  return (
    block.type === 'tool_call' &&
    block.tool_call?.name === UPDATE_PLAN_TOOL_NAME &&
    block.extra?.internalTool === true  // ⚠️ 三重检查
  )
}

问题extra.internalTool 标记依赖 runtime 正确设置,如果某个代码路径遗漏,update_plan 会泄露到正文。

建议

  • dispatch.ts 中集中标记所有 UPDATE_PLAN_TOOL_NAME 为 internal
  • 添加防御性检查:即使缺少 internalTool 标记,也过滤掉 UPDATE_PLAN_TOOL_NAME

🐛 潜在 Bug

6. Pending Interaction 与 Plan Float 同时显示的布局冲突

<div v-if="activePendingInteraction && latestPlanSnapshot" class="agent-question-panel">
  <AgentProgressFloat :snapshot="latestPlanSnapshot" />
  <div class="agent-question-divider" />
  <ChatToolInteractionOverlay :interaction="activePendingInteraction" />
</div>

风险:如果 plan entries 很长 + question 很复杂,面板可能超出视口高度,导致无法滚动查看完整内容。

建议:为 .agent-question-panel 添加 max-heightoverflow-y: auto

7. Message Delete 后的状态同步

// docs/features/message-delete-confirmation/spec.md 更新为:
- Message delete and related recovery flows now consistently reload the current session.

但代码中没看到删除消息后清除对应 session 的 live plan 的逻辑。如果删除的是触发 plan 的那条消息,plan float 可能残留。

建议:在 onMessageDelete 成功回调中调用 agentPlanStore.clearPlanSnapshot(sessionId)

📝 代码质量建议

  1. Magic Number 提取PLAN_FLOAT_CLEAR_DELAY_MS = 1200 可以作为用户可配置项(如 settings)
  2. 类型安全增强planFloatLingerBySessionRecord<string, boolean> 应该改为 Map<string, boolean>,避免原型链污染
  3. 测试覆盖补充
    • Session A/B 快速切换时 live plan 的保留行为
    • 只有 plan block 的旧消息是否正确隐藏
    • beginTurn 调用时机的准确性

🎨 文档质量

docs/architecture/agent-plan-task-refactor/spec.md 非常清晰,但有两点建议:

  1. Migration Guide 缺失:没有说明旧数据中的 type:'plan' block 如何处理(保留但隐藏 vs 清理)
  2. 终态流程图:建议补充 plan 终态转换的状态机图,明确 5 种 terminalReason 的触发条件

✨ 总体评价

这是一个高质量的架构重构 PR,方向正确,执行彻底。主要风险点在于:

  1. Session 切换与 beginTurn 的时序逻辑(可能违反 spec 承诺)
  2. Legacy plan block 的空消息渲染(用户体验问题)
  3. Plan float linger timer 的生命周期管理(内存泄漏风险)

建议优先修复问题 1、2、4,其他可以作为后续迭代。代码实现稳健,测试覆盖充分,通过后可以安全合并。 🚀


重点确认项

  • 确认 beginTurn 不会在 session switch 时清除 live plan
  • 验证纯 plan 消息是否正确隐藏(不渲染空 card)
  • 测试快速切换 session 时 plan float 的行为
  • 确认 message delete 后清除对应 plan snapshot

@yyhhyyyyyy

Copy link
Copy Markdown
Collaborator Author

@zerob13 Thanks. Fixed the two actionable issues: the combined plan/interaction panel is now scroll-bounded, and deleting the associated assistant message clears only its matching live plan snapshot.I checked the remaining items as well. The current behavior is intentional and covered by existing tests, so I left those unchanged.

@zerob13
zerob13 merged commit b247998 into dev Jul 9, 2026
3 checks passed
@zhangmo8
zhangmo8 deleted the refactor/agent-plan-live-float branch July 10, 2026 03:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants