Skip to content

Conversation

@zerob13
Copy link
Collaborator

@zerob13 zerob13 commented Jan 13, 2026

Add StreamUpdateScheduler to batch and debounce streaming updates, reducing event frequency by batching updates over 200ms intervals for rendering and 600ms for database writes. Introduces stream_kind to distinguish init/delta/final events and filters events by conversation ID for performance.

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced streaming updates with improved message delivery and batching across tabs.
  • Bug Fixes

    • Improved variant message association and display accuracy within conversations.
  • Performance

    • Optimized message update scheduling for more efficient streaming performance.

✏️ Tip: You can customize this high-level summary in your review settings.

Add StreamUpdateScheduler to batch and debounce streaming updates, reducing event frequency by batching updates over 200ms intervals for rendering and 600ms for database writes. Introduces stream_kind to distinguish init/delta/final events and filters events by conversation ID for performance.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

This pull request introduces a new StreamUpdateScheduler class to batch and coordinate streaming updates from the LLM agent, replacing direct message editing calls. It integrates the scheduler into handlers (ContentBufferHandler, ToolCallHandler, LLMEventHandler), adds per-tab tracking via optional tabId parameters, extends event payloads with metadata fields (conversationId, parentId, is_variant, seq), and refactors the renderer's stream response handling to better support variant messages.

Changes

Cohort / File(s) Summary
StreamUpdateScheduler Infrastructure
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts, src/main/presenter/agentPresenter/streaming/types.ts
New scheduler class batches and flushes delta events with configurable render/DB flush intervals. Supports per-event state tracking (eventId, conversationId, parentId, variant, tabId) and implements init/delta/final event sequencing. Exports public API: enqueueDelta(), flushAll(), cleanup() and constants STREAM_RENDER_FLUSH_INTERVAL_MS, STREAM_DB_FLUSH_INTERVAL_MS. Updated types to include tabId?: number in GeneratingMessageState.
Handler Integration (MessageManager → Scheduler)
src/main/presenter/agentPresenter/index.ts, src/main/presenter/agentPresenter/loop/toolCallHandler.ts, src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts, src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
Replaced direct MessageManager.editMessage() calls with streamUpdateScheduler.enqueueDelta() across all handlers. Updated constructor signatures to inject scheduler instead of message manager. Extended AgentPresenter.sendMessage() to accept optional tabId parameter and pass it through to trackGeneratingMessage().
MessageManager Enhancement
src/main/presenter/sessionPresenter/managers/messageManager.ts
Extended editMessage() with optional { emit?, emitParent? } flags to control event emission. Added new editMessageSilently() helper method for updates without triggering events.
Event Type System
src/shared/types/core/agent-events.ts
Extended LLMAgentEventData with five new optional fields: conversationId, parentId, is_variant, stream_kind ('init' | 'delta' | 'final'), and seq.
Renderer Variant & Stream Handling
src/renderer/src/components/message/MessageItemAssistant.vue, src/renderer/src/stores/chat.ts
Refined variant filtering logic in MessageItemAssistant to match parentId instead of message id. Significantly refactored chat store's handleStreamResponse() to support new stream metadata, implement main/variant message routing, add skeleton creation, and handle tool_call state transitions. Updated variant attachment logic to target main messages instead of parents.

Sequence Diagram(s)

sequenceDiagram
    participant Handler as ContentBuffer/<br>ToolCall/LLMHandler
    participant Scheduler as StreamUpdateScheduler
    participant EventBus as EventBus
    participant Renderer as Renderer
    participant DB as MessageManager/<br>Backend

    Handler->>Scheduler: enqueueDelta(eventId, delta)
    activate Scheduler
    Scheduler->>Scheduler: batch delta into pendingDelta
    alt shouldFlushBeforeEnqueue
        Scheduler->>EventBus: emit delta event (seq++)
        EventBus->>Renderer: render streamed content
        Scheduler->>DB: flushDb (editMessageSilently)
        DB-->>Scheduler: ✓ persisted
    else schedule flush
        Scheduler->>Scheduler: scheduleRenderFlush (timer)
    end
    deactivate Scheduler
    
    Scheduler->>Scheduler: STREAM_RENDER_FLUSH_INTERVAL_MS expires
    Scheduler->>EventBus: flushRender (send pendingDelta)
    EventBus->>Renderer: update UI
    Scheduler->>Scheduler: scheduleDbFlush (timer)
    
    Scheduler->>Scheduler: STREAM_DB_FLUSH_INTERVAL_MS expires
    Scheduler->>DB: flushDb (persist snapshot)
    DB-->>Scheduler: ✓
Loading
sequenceDiagram
    participant Renderer as Renderer
    participant Store as Chat Store
    participant Cache as Message Cache

    Renderer->>Store: handleStreamResponse(msg: init)
    activate Store
    Store->>Store: findMainAssistantMessageByParentId
    Store->>Cache: create skeleton / cache main message
    deactivate Store
    
    Renderer->>Store: handleStreamResponse(msg: delta, content)
    activate Store
    Store->>Cache: update main/variant message content
    Store->>Renderer: refresh display
    deactivate Store
    
    Renderer->>Store: handleStreamResponse(msg: final)
    activate Store
    Store->>Store: attach variants to main message
    Store->>Cache: update final message + variants
    Store->>Renderer: final render
    deactivate Store
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 A scheduler hops in, batching deltas with grace,
Flushes render and DB at a measured pace,
Variants find their parents, no longer by ID,
Streaming updates now flow in perfect harmony! 🌊

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix(streaming): reduce stream events with batching scheduler' accurately and concisely describes the main change: introducing a batching scheduler to reduce stream event frequency.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
src/main/presenter/sessionPresenter/managers/messageManager.ts (1)

382-385: Non-English log message.

As per coding guidelines, all logs and comments must be in English.

Suggested fix
     } catch (error) {
-      console.error('初始化未完成消息失败:', error)
+      console.error('Failed to initialize unfinished messages:', error)
     }
src/renderer/src/components/message/MessageItemAssistant.vue (1)

281-284: Non-English log message.

As per coding guidelines, all logs and comments must be in English.

Suggested fix
   } catch (error) {
-    console.error('创建对话分支失败:', error)
+    console.error('Failed to create conversation fork:', error)
   }
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)

42-50: Consider adding proper typing for messageManager.

The messageManager field is typed as any, which bypasses TypeScript's type checking. Consider importing and using the MessageManager type for better type safety.

♻️ Suggested typing improvement
+import type { MessageManager } from '../../sessionPresenter/managers/messageManager'
+
 export class StreamUpdateScheduler {
   private readonly states: Map<string, SchedulerState> = new Map()
-  private readonly messageManager: any
+  private readonly messageManager: MessageManager
   private readonly onFlushRender?: () => void

-  constructor(options: { messageManager: any; onFlushRender?: () => void }) {
+  constructor(options: { messageManager: MessageManager; onFlushRender?: () => void }) {
     this.messageManager = options.messageManager
     this.onFlushRender = options.onFlushRender
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8fd50e4 and 288b5f9.

📒 Files selected for processing (10)
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/renderer/src/stores/chat.ts
  • src/shared/types/core/agent-events.ts
🧰 Additional context used
📓 Path-based instructions (19)
**/*.{js,ts,tsx,jsx,vue,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

All logs and comments must be in English

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/shared/types/core/agent-events.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
**/*.{js,ts,tsx,jsx,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

Use OxLint as the linter

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/shared/types/core/agent-events.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
**/*.{js,ts,tsx,jsx,vue,json,mjs,cjs}

📄 CodeRabbit inference engine (.cursor/rules/development-setup.mdc)

Use Prettier as the code formatter

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/shared/types/core/agent-events.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
src/main/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Electron main process code should reside in src/main/, with presenters organized in presenter/ subdirectory (Window, Tab, Thread, Mcp, Config, LLMProvider), and app events managed via eventbus.ts

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,vue}: Use camelCase for variable and function names; use PascalCase for types and classes; use SCREAMING_SNAKE_CASE for constants
Configure Prettier with single quotes, no semicolons, and line width of 100 characters. Run pnpm run format after completing features

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/shared/types/core/agent-events.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use OxLint for linting JavaScript and TypeScript files; ensure lint-staged hooks and typecheck pass before commits

Enable strict TypeScript type checking

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/shared/types/core/agent-events.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

Use English for all logs and comments

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/shared/types/core/agent-events.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
src/main/presenter/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

Implement all system capabilities as main-process presenters following the Presenter Pattern

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
{src/main,src/renderer,test}/**/*.{ts,tsx,js}

📄 CodeRabbit inference engine (CLAUDE.md)

Use IPC communication: Renderer calls main process via usePresenter composable, Main sends to Renderer via EventBus

Files:

  • src/main/presenter/agentPresenter/streaming/types.ts
  • src/main/presenter/agentPresenter/loop/toolCallHandler.ts
  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{vue,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)

src/renderer/src/**/*.{vue,ts,tsx}: Use vue-i18n framework for internationalization located at src/renderer/src/i18n/
All user-facing strings must use i18n keys, not hardcoded text

src/renderer/src/**/*.{vue,ts,tsx}: Use ref for primitives and references, reactive for objects in Vue 3 Composition API
Prefer computed properties over methods for derived state in Vue components
Import Shadcn Vue components from @/shadcn/components/ui/ path alias
Use the cn() utility function combining clsx and tailwind-merge for dynamic Tailwind classes
Use defineAsyncComponent() for lazy loading heavy Vue components
Use TypeScript for all Vue components and composables with explicit type annotations
Define TypeScript interfaces for Vue component props and data structures
Use usePresenter composable for main process communication instead of direct IPC calls

Files:

  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*.vue

📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)

Import useI18n from vue-i18n in Vue components to access translation functions t and locale

src/renderer/src/**/*.vue: Use <script setup> syntax for concise Vue 3 component definitions with Composition API
Define props and emits explicitly in Vue components using defineProps and defineEmits with TypeScript interfaces
Use provide/inject for dependency injection in Vue components instead of prop drilling
Use Tailwind CSS for all styling instead of writing scoped CSS files
Use mobile-first responsive design approach with Tailwind breakpoints
Use Iconify Vue with lucide icons as primary choice, following pattern lucide:{icon-name}
Use v-memo directive for memoizing expensive computations in templates
Use v-once directive for rendering static content without reactivity updates
Use virtual scrolling with RecycleScroller component for rendering long lists
Subscribe to events using rendererEvents.on() and unsubscribe in onUnmounted lifecycle hook

Files:

  • src/renderer/src/components/message/MessageItemAssistant.vue
src/renderer/src/**/*.{ts,tsx,vue}

📄 CodeRabbit inference engine (.cursor/rules/i18n.mdc)

Ensure all code comments are in English and all log messages are in English, with no non-English text in code comments or console statements

Use VueUse composables for common utilities like useLocalStorage, useClipboard, useDebounceFn

Vue 3 renderer app code should be organized in src/renderer/src with subdirectories for components/, stores/, views/, i18n/, and lib/

Files:

  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/renderer/src/stores/chat.ts
src/renderer/src/components/**/*.vue

📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)

Name Vue components using PascalCase (e.g., ChatInput.vue, MessageItemUser.vue)

Files:

  • src/renderer/src/components/message/MessageItemAssistant.vue
**/*.vue

📄 CodeRabbit inference engine (AGENTS.md)

Vue components must be named in PascalCase (e.g., ChatInput.vue) and use Vue 3 Composition API with Pinia for state management and Tailwind for styling

**/*.vue: Use Vue 3 Composition API with <script setup> syntax
Use Pinia stores for state management
Style Vue components using Tailwind CSS (v4) with shadcn/ui (reka-ui)

Files:

  • src/renderer/src/components/message/MessageItemAssistant.vue
src/renderer/**/*.{ts,tsx,js,jsx,vue}

📄 CodeRabbit inference engine (CLAUDE.md)

All user-facing strings must use i18n keys (supports 12 languages)

Files:

  • src/renderer/src/components/message/MessageItemAssistant.vue
  • src/renderer/src/stores/chat.ts
src/shared/**/*.ts

📄 CodeRabbit inference engine (AGENTS.md)

Shared TypeScript types and utilities should be placed in src/shared/

Files:

  • src/shared/types/core/agent-events.ts
src/renderer/src/stores/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)

src/renderer/src/stores/**/*.ts: Use Setup Store syntax with defineStore function pattern in Pinia stores
Use getters (computed properties) for derived state in Pinia stores
Keep Pinia store actions focused on state mutations and async operations

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)

Use class-variance-authority (CVA) for defining component variants with Tailwind classes

Files:

  • src/renderer/src/stores/chat.ts
src/renderer/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/vue-stack-guide.mdc)

src/renderer/src/**/*.{ts,tsx}: Use shallowRef and shallowReactive for optimizing reactivity with large objects
Prefer type over interface in TypeScript unless using inheritance with extends

Files:

  • src/renderer/src/stores/chat.ts
🧠 Learnings (5)
📚 Learning: 2026-01-13T09:23:07.415Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T09:23:07.415Z
Learning: Applies to src/main/presenter/llmProviderPresenter/providers/*.ts : Implement `coreStream` method following standardized event interface for LLM providers

Applied to files:

  • src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/main/presenter/agentPresenter/index.ts
  • src/main/presenter/agentPresenter/streaming/llmEventHandler.ts
📚 Learning: 2025-06-21T15:49:17.044Z
Learnt from: neoragex2002
Repo: ThinkInAIXYZ/deepchat PR: 550
File: src/renderer/src/stores/chat.ts:1011-1035
Timestamp: 2025-06-21T15:49:17.044Z
Learning: In src/renderer/src/stores/chat.ts, the user prefers to keep both `text` and `content` properties in the `handleMeetingInstruction` function's `sendMessage` call, even though they are redundant, rather than removing the `content` property.

Applied to files:

  • src/main/presenter/sessionPresenter/managers/messageManager.ts
  • src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts
  • src/renderer/src/stores/chat.ts
📚 Learning: 2026-01-13T09:23:07.415Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T09:23:07.415Z
Learning: Applies to {src/main,src/renderer,test}/**/*.{ts,tsx,js} : Use IPC communication: Renderer calls main process via `usePresenter` composable, Main sends to Renderer via EventBus

Applied to files:

  • src/main/presenter/agentPresenter/index.ts
📚 Learning: 2026-01-13T09:23:07.415Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-13T09:23:07.415Z
Learning: Applies to src/main/presenter/**/*.ts : Implement all system capabilities as main-process presenters following the Presenter Pattern

Applied to files:

  • src/main/presenter/agentPresenter/index.ts
📚 Learning: 2026-01-05T02:41:31.661Z
Learnt from: CR
Repo: ThinkInAIXYZ/deepchat PR: 0
File: .cursor/rules/vue-stack-guide.mdc:0-0
Timestamp: 2026-01-05T02:41:31.661Z
Learning: Applies to src/renderer/src/**/*.{vue,ts,tsx} : Use `usePresenter` composable for main process communication instead of direct IPC calls

Applied to files:

  • src/main/presenter/agentPresenter/index.ts
🧬 Code graph analysis (7)
src/main/presenter/agentPresenter/loop/toolCallHandler.ts (1)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
  • StreamUpdateScheduler (42-408)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (3)
src/shared/types/core/agent-events.ts (1)
  • LLMAgentEventData (4-51)
src/main/presenter/agentPresenter/streaming/types.ts (1)
  • StreamUpdateScheduler (39-39)
src/main/eventbus.ts (1)
  • eventBus (151-151)
src/main/presenter/sessionPresenter/managers/messageManager.ts (1)
src/main/eventbus.ts (1)
  • eventBus (151-151)
src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts (2)
src/main/presenter/agentPresenter/streaming/types.ts (2)
  • GeneratingMessageState (4-37)
  • StreamUpdateScheduler (39-39)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
  • StreamUpdateScheduler (42-408)
src/main/presenter/agentPresenter/index.ts (2)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
  • StreamUpdateScheduler (42-408)
src/main/presenter/agentPresenter/loop/toolCallHandler.ts (1)
  • ToolCallHandler (38-566)
src/main/presenter/agentPresenter/streaming/llmEventHandler.ts (3)
src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (1)
  • StreamUpdateScheduler (42-408)
src/main/presenter/agentPresenter/streaming/types.ts (1)
  • StreamUpdateScheduler (39-39)
src/shared/types/core/agent-events.ts (1)
  • LLMAgentEventData (4-51)
src/renderer/src/stores/chat.ts (3)
src/renderer/src/lib/messageRuntimeCache.ts (2)
  • getCachedMessage (36-41)
  • hasCachedMessage (43-45)
src/shared/chat.d.ts (2)
  • AssistantMessage (39-42)
  • Message (3-32)
src/shared/chat/messageBlocks.ts (1)
  • finalizeAssistantMessageBlocks (3-25)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build-check (x64)
🔇 Additional comments (30)
src/main/presenter/agentPresenter/streaming/types.ts (1)

36-39: LGTM!

The tabId addition to GeneratingMessageState supports the per-tab tracking feature, and the type re-export keeps the module's public API clean.

src/main/presenter/sessionPresenter/managers/messageManager.ts (1)

122-151: LGTM! Clean API extension for silent updates.

The editMessage signature extension with optional emit/emitParent flags maintains backward compatibility (defaults to emitting), and editMessageSilently provides a clear API for the scheduler's database flush path.

src/renderer/src/components/message/MessageItemAssistant.vue (1)

213-221: LGTM! Correct variant sibling matching.

The updated condition properly identifies variants as siblings sharing the same parentId rather than treating the current message as the parent. This aligns with the variant model where multiple assistant responses to the same user message share a common parent.

src/shared/types/core/agent-events.ts (1)

6-10: LGTM! Well-structured event metadata for streaming.

The new fields properly support the scheduler's batching mechanism:

  • conversationId and parentId enable filtering events by context
  • stream_kind distinguishes initialization, incremental updates, and finalization
  • seq provides ordering guarantees for delta reconstruction
src/main/presenter/agentPresenter/loop/toolCallHandler.ts (3)

48-59: LGTM! Clean dependency injection update.

The migration from MessageManager to StreamUpdateScheduler correctly updates the constructor signature and member assignment.


291-299: LGTM! Correct scheduler integration for MCP UI resources.

The enqueueDelta call with an empty delta and contentSnapshot properly schedules the database flush while allowing the render batching to proceed independently.


417-425: LGTM! Consistent scheduler integration for search results.

Same pattern as the MCP UI resource handling—empty delta with content snapshot schedules persistence without redundant render emissions.

src/main/presenter/agentPresenter/streaming/llmEventHandler.ts (5)

12-41: LGTM! Clean integration of StreamUpdateScheduler.

The constructor properly accepts and stores the streamUpdateScheduler dependency, following the existing pattern used for other handlers.


97-106: LGTM! Correct pattern for action blocks.

The empty delta with full contentSnapshot ensures the maximum tool calls action block is properly synchronized to the renderer and database.


248-274: LGTM! Well-structured delta construction.

The delta object is efficiently constructed, only including fields with values. The contentSnapshot ensures database persistence stays synchronized with the streaming state.


277-297: LGTM! Proper error handling sequence.

The error handler correctly flushes pending updates via flushAll before sending the error event, ensuring no streaming data is lost.


317-340: Scheduler state is properly cleaned up when the resumed permission flow completes.

When hasPendingPermissions is true, the method returns early without calling flushAll. However, this does not cause a state leak. When the permission is handled and the stream resumes via restartAgentLoopAfterPermission, continueAfterPermissionDenied, or resumeStreamCompletion, all three paths iterate through the resumed stream and call handleLLMAgentEnd() on the final event. On this second call to handleLLMAgentEnd(), the permission blocks will be 'granted' or 'denied' (not 'pending'), so hasPendingPermissions evaluates to false, and flushAll() executes normally (line 347), which calls this.states.delete(eventId) to clean up scheduler state. The early return when waiting for permissions is intentional design to preserve message content state during the permission flow.

Likely an incorrect or invalid review comment.

src/main/presenter/agentPresenter/streaming/contentBufferHandler.ts (3)

3-15: LGTM! Clean refactor to use StreamUpdateScheduler.

The constructor properly accepts streamUpdateScheduler instead of messageManager, aligning with the new streaming architecture.


107-115: LGTM! Proper batched content streaming.

The large content handler correctly batches content updates and passes the full content snapshot for database persistence.


150-158: LGTM! Standard content streaming pattern.

The normal content handler properly updates local state first, then enqueues the delta for batched delivery.

src/main/presenter/agentPresenter/index.ts (4)

74-76: LGTM! Centralized scheduler instantiation.

The StreamUpdateScheduler is constructed once and shared across all streaming handlers, ensuring consistent batching behavior.


86-106: LGTM! Consistent handler wiring.

All streaming handlers receive the shared streamUpdateScheduler instance, ensuring coordinated batching across different event types.


146-169: LGTM! Proper tabId propagation.

The tabId parameter is now properly captured and passed to trackGeneratingMessage, enabling per-tab filtering in the renderer.


321-337: LGTM! GeneratingMessageState correctly includes tabId.

The tabId is stored in the state object, making it available for all downstream streaming operations.

src/main/presenter/agentPresenter/streaming/streamUpdateScheduler.ts (4)

78-113: LGTM! Smart handling of mixed content and reasoning.

The recursive split for deltas containing both content and reasoning_content ensures these different content types are processed separately, preventing rendering confusion.


286-321: LGTM! Well-designed flush heuristics.

The shouldFlushBeforeEnqueue logic correctly identifies when pending deltas must be flushed to preserve event ordering and prevent inappropriate batching of incompatible delta types.


323-340: LGTM! Resilient DB flush implementation.

The flushDb method correctly handles the case when contentSnapshot is undefined, and error handling prevents DB write failures from disrupting the streaming flow.


342-391: LGTM! Complete finalization handling.

The flushAll method properly clears timers, sends the final delta, persists to database, and cleans up state in the correct order.

src/renderer/src/stores/chat.ts (7)

191-206: LGTM! Efficient main message lookup.

The findMainAssistantMessageByParentId helper efficiently finds the main (non-variant) assistant message by iterating through cached messages.


718-734: LGTM! Proper variant attachment during retry.

The retry logic correctly finds or creates the main message's variants array, attaches the new variant, and updates the cache and selection state.


918-958: LGTM! Proper skeleton message initialization.

The skeleton message created on init events provides immediate visual feedback while streaming begins. Non-variant messages are correctly added to the view, while variants are only cached.


910-915: LGTM! Performance optimization via conversation filtering.

Early filtering of events from non-active conversations prevents unnecessary processing and cache updates.


997-1097: LGTM! Comprehensive tool call lifecycle handling.

The tool call handling covers all states (start, update, running, end, error) with proper block matching and status transitions.


1164-1186: LGTM! Proper variant synchronization during streaming.

The variant handling correctly finds the main message, updates its variants array, and refreshes the cache to ensure UI consistency during streaming.


1279-1294: LGTM! Consistent error handling for variants.

Error state is properly propagated to variant messages and their parent main messages using the same pattern as successful streaming.

Comment on lines +131 to +134
const shouldFlushImmediately = this.shouldFlushBeforeEnqueue(state, delta)
if (shouldFlushImmediately) {
this.flushRender(state)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Double flush when shouldFlushImmediately is true.

When shouldFlushImmediately is true, flushRender is called twice:

  1. Line 133: Before accumulating the new delta (correct - flushes pending incompatible delta)
  2. Line 186: After accumulating the new delta (redundant - sends new delta immediately)

The second flush at line 186 defeats batching for these scenarios. While not functionally incorrect (it just sends more events), it undermines the batching optimization for events like tool_call transitions.

🔧 Suggested fix
     const now = Date.now()

-    if (shouldFlushImmediately) {
-      this.flushRender(state)
-    } else {
+    if (!shouldFlushImmediately) {
       const renderDelay = Math.max(
         0,
         STREAM_RENDER_FLUSH_INTERVAL_MS - (now - state.lastRenderFlushAt)
       )
       this.scheduleRenderFlush(state, renderDelay)
     }

Also applies to: 185-193

@zerob13 zerob13 merged commit d4c6a49 into dev Jan 14, 2026
6 checks passed
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