Skip to content

feat: message aggregator#1985

Merged
RockChinQ merged 2 commits intomasterfrom
feat/cache
Feb 25, 2026
Merged

feat: message aggregator#1985
RockChinQ merged 2 commits intomasterfrom
feat/cache

Conversation

@wangcham
Copy link
Member

概述 / Overview

请在此部分填写你实现/解决/优化的内容:
Summary of what you implemented/solved/optimized:

添加防抖动机制

更改前后对比截图 / Screenshots

请在此部分粘贴更改前后对比截图(可以是界面截图、控制台输出、对话截图等):
Please paste the screenshots of changes before and after here (can be interface screenshots, console output, conversation screenshots, etc.):

修改前 / Before:

修改后 / After:

检查清单 / Checklist

PR 作者完成 / For PR author

请在方括号间写x以打勾 / Please tick the box with x

  • 阅读仓库贡献指引了吗? / Have you read the contribution guide?
  • 与项目所有者沟通过了吗? / Have you communicated with the project maintainer?
  • 我确定已自行测试所作的更改,确保功能符合预期。 / I have tested the changes and ensured they work as expected.

项目维护者完成 / For project maintainer

  • 相关 issues 链接了吗? / Have you linked the related issues?
  • 配置项写好了吗?迁移写好了吗?生效了吗? / Have you written the configuration items? Have you written the migration? Has it taken effect?
  • 依赖加到 pyproject.toml 和 core/bootutils/deps.py 了吗 / Have you added the dependencies to pyproject.toml and core/bootutils/deps.py?
  • 文档编写了吗? / Have you written the documentation?

@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. eh: Feature enhance: 新功能添加 / add new features m: Lifecycle 启动/关闭流程 / Bootstrap & application life cycle pd: Need testing pending: 待测试的PR / PR waiting to be tested labels Feb 17, 2026
@RockChinQ RockChinQ changed the title feat: aggregator feat: message aggregator Feb 17, 2026
@RockChinQ RockChinQ requested a review from Copilot February 23, 2026 08:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a message aggregation (debounce) mechanism in the trigger stage so that consecutive messages can be buffered briefly and merged before being sent into the pipeline for processing.

Changes:

  • Add new message-aggregation trigger stage metadata and default config (enabled, delay).
  • Route incoming friend/group messages through a new MessageAggregator instead of directly into QueryPool.
  • Initialize and attach the aggregator to the application during boot.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/langbot/templates/metadata/pipeline/trigger.yaml Adds pipeline trigger metadata/config schema for message aggregation.
src/langbot/templates/default-pipeline-config.json Provides default values for the new trigger config.
src/langbot/pkg/platform/botmgr.py Switches message ingestion from query_pool to msg_aggregator.
src/langbot/pkg/pipeline/aggregator.py Implements buffering, delayed flush, and message merging logic.
src/langbot/pkg/core/stages/build_app.py Instantiates and attaches MessageAggregator to the app.
src/langbot/pkg/core/app.py Adds msg_aggregator to the application context.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 103 to 106
delay = aggregation_config.get('delay', default_delay)

# Clamp delay to valid range
delay = max(1.0, min(10.0, float(delay)))
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

delay = ... float(delay) will raise ValueError if the pipeline config contains a non-numeric value (e.g. user-edited JSON/YAML, corrupted DB value), which would break message processing. Consider wrapping the cast in a try/except and falling back to default_delay before clamping.

Suggested change
delay = aggregation_config.get('delay', default_delay)
# Clamp delay to valid range
delay = max(1.0, min(10.0, float(delay)))
delay_raw = aggregation_config.get('delay', default_delay)
try:
delay = float(delay_raw)
except (TypeError, ValueError):
delay = default_delay
# Clamp delay to valid range
delay = max(1.0, min(10.0, delay))

Copilot uses AI. Check for mistakes.
Comment on lines 246 to 256
# Create merged message with updated chain
# We need to update the message_event's message_chain as well
merged_event = base_msg.message_event
merged_event.message_chain = merged_chain

return PendingMessage(
bot_uuid=base_msg.bot_uuid,
launcher_type=base_msg.launcher_type,
launcher_id=base_msg.launcher_id,
sender_id=base_msg.sender_id,
message_event=merged_event,
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

_merge_messages() mutates message_event.message_chain to the merged chain. Several adapters rely on message_source.message_chain.message_id when replying/quoting; replacing the chain risks losing the original message_id and other event metadata. Since QueryPool.add_query() already takes message_chain separately, keep message_event unmodified (or choose a specific event to keep for reply reference) and only pass the merged chain via the message_chain field.

Suggested change
# Create merged message with updated chain
# We need to update the message_event's message_chain as well
merged_event = base_msg.message_event
merged_event.message_chain = merged_chain
return PendingMessage(
bot_uuid=base_msg.bot_uuid,
launcher_type=base_msg.launcher_type,
launcher_id=base_msg.launcher_id,
sender_id=base_msg.sender_id,
message_event=merged_event,
# Create merged message using the original event (do not mutate its chain)
return PendingMessage(
bot_uuid=base_msg.bot_uuid,
launcher_type=base_msg.launcher_type,
launcher_id=base_msg.launcher_id,
sender_id=base_msg.sender_id,
message_event=base_msg.message_event,

Copilot uses AI. Check for mistakes.

return enabled, delay

async def add_message(
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

The new aggregation/debounce behavior is non-trivial (buffering, keying, merge semantics). Add unit tests to cover: (1) messages within the delay window are merged, (2) different senders in the same group are not merged, and (3) reply-critical message_event/message_id stays intact when merging.

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +78
) -> str:
"""Generate a unique session ID"""
Copy link

Copilot AI Feb 23, 2026

Choose a reason for hiding this comment

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

_get_session_id() currently keys buffers only by bot_uuid, launcher_type, and launcher_id. For group messages this means messages from different senders in the same group can be merged together, and the merged query will also keep sender_id from the first buffered message (misattributing content). Include sender_id in the buffer/session key (at least for group chats) so aggregation matches the “same user” behavior described in the metadata and avoids cross-user merges.

Suggested change
) -> str:
"""Generate a unique session ID"""
sender_id: typing.Union[int, str, None] = None,
) -> str:
"""Generate a unique session ID
For group or multi-user contexts, callers SHOULD supply ``sender_id``
so that aggregation is scoped per sender and avoids cross-user merges.
If ``sender_id`` is omitted, the session ID falls back to the
historical behavior and does not distinguish between senders.
"""
# Include sender_id in the session key when provided to avoid
# aggregating messages from different users in the same launcher
if sender_id is not None:
return f'{bot_uuid}:{launcher_type.value}:{launcher_id}:{sender_id}'

Copilot uses AI. Check for mistakes.
- Fix deadlock: don't await cancelled timer tasks inside the lock;
  _flush_buffer acquires the same lock, causing a deadlock cycle
- Fix message_event mutation: keep original message_event unmodified
  to preserve message_id/metadata for reply/quote; only pass merged
  message_chain separately
- Fix Plain positional arg: Plain('\n') → Plain(text='\n')
- Fix float() ValueError: wrap delay cast in try/except
- Add MAX_BUFFER_MESSAGES (10) cap to prevent unbounded buffer growth
- Default enabled to false to avoid surprising latency on upgrade
- Fix flush_all: cancel all timers under one lock acquisition, then
  flush outside the lock to avoid deadlock
@RockChinQ
Copy link
Member

image

@RockChinQ RockChinQ merged commit b8df0db into master Feb 25, 2026
3 checks passed
@RockChinQ RockChinQ deleted the feat/cache branch February 25, 2026 06:20
RockChinQ added a commit that referenced this pull request Feb 25, 2026
* fix a bag updata

* Update page.tsx

* Update page.tsx

* Append text area to body for selection

* Update page.tsx

* Update mcp.py

* fix(web): Handle null/undefined starCount and installCount (#1970)

* Initial plan

* fix(web): Handle null/undefined values for starCount and installCount

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* fix(web): Hide star count badge when API fails instead of showing '0'

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* Add files via upload

* Update README.md

* Update README_EN.md

* Update README_TW.md

* Add Satori to communication tools list

* Add Satori to supported platforms list

* Add Satori to the supported LLMs list

* Add Satori to the supported platforms list

* Add Satori to supported platforms list

* Add Satori to the supported platforms list

* Add files via upload

* Update README_TW.md

* Add files via upload

* Add files via upload

* chore(deps): bump pillow from 12.1.0 to 12.1.1 (#1977)

Bumps [pillow](https://github.com/python-pillow/Pillow) from 12.1.0 to 12.1.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst)
- [Commits](python-pillow/Pillow@12.1.0...12.1.1)

---
updated-dependencies:
- dependency-name: pillow
  dependency-version: 12.1.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump cryptography from 46.0.4 to 46.0.5 (#1978)

Bumps [cryptography](https://github.com/pyca/cryptography) from 46.0.4 to 46.0.5.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](pyca/cryptography@46.0.4...46.0.5)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-version: 46.0.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump axios from 1.13.4 to 1.13.5 in /web (#1979)

Bumps [axios](https://github.com/axios/axios) from 1.13.4 to 1.13.5.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](axios/axios@v1.13.4...v1.13.5)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.13.5
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Revise bug report instructions for clarity

Updated bug report template to request export files for external platforms.

* Update bug-report_en.yml

* Replace English README with Chinese version and update language links across all README files

* Update README files across multiple languages to reflect new platform capabilities and improve clarity. Enhanced descriptions for AI bot development and deployment, and added links for further documentation.

* Update src/langbot/pkg/platform/sources/satori.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update src/langbot/pkg/platform/sources/satori.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add files via upload

* Delete README_EN.md

* Update README.md

* Add Satori support to README_TW.md

* Update README_VI.md

* Add Satori support to the README_KO.md

* Update README_RU.md

* Update fmt.Println message from 'Hello' to 'Goodbye'

* Update print statement from 'Hello' to 'Goodbye'

* ruff

* Add files via upload

* Change type from int to integer in satori.yaml

* fix: correct license declaration in OpenAPI spec from AGPL-3.0 to Apache-2.0 (#1988)

* Initial plan

* fix: update license from AGPL-3.0 to Apache-2.0 in service-api-openapi.json

Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: RockChinQ <45992437+RockChinQ@users.noreply.github.com>

* Update satori.py

* Update satori.py

* Update satori.py

* feat: Implement extension and bot limitations across services and UI (#1991)

- Added checks for maximum allowed extensions, bots, and pipelines in the backend services (PluginsRouterGroup, BotService, MCPService, PipelineService).
- Updated system configuration to include limitation settings for max_bots, max_pipelines, and max_extensions.
- Enhanced frontend components to handle limitations, providing user feedback when limits are reached.
- Added internationalization support for limitation messages in English, Japanese, Simplified Chinese, and Traditional Chinese.

* feat: Add unsaved changes tracking to PipelineFormComponent

* chore: Update logo in README files to new resource location

* chore: Standardize section headers in multiple language README files

* chore: Bump version to 4.8.4 and update langbot-plugin dependency to 0.2.6

* feat: add plugin recommendation lists to market page (#2001)

* fix: Add the file upload function and optimize the media message proc… (#2002)

* fix: Add the file upload function and optimize the media message processing

* fix: Optimize the message processing logic, improve the concatenation of text elements and the sending of media messages

* fix: Simplify the file request construction and message processing logic to enhance code readability

* fix(web): emit initial form values on mount to prevent saving empty config (#2004)

DynamicFormComponent uses form.watch(callback) to notify parent of form
values, but react-hook-form's watch callback only fires on subsequent
changes, not on mount. This causes PluginForm's currentFormValues to
remain as {} if the user saves without modifying any field, overwriting
the existing plugin config with an empty object in the database.

* feat(platform): add Forward message support for aiocqhttp adapter (#2003)

* feat(platform): add Forward message support for aiocqhttp adapter

- Add _send_forward_message method to send merged forward cards via OneBot API
- Support NapCat's send_forward_msg API with fallback to send_group_forward_msg
- Fix MessageChain deserialization for Forward messages in handler.py
- Properly deserialize nested ForwardMessageNode.message_chain to preserve data

This enables plugins to send QQ merged forward cards through the standard
LangBot send_message API using the Forward message component.

* style: fix ruff lint and format issues

- Remove f-string prefix from log message without placeholders
- Apply ruff format to aiocqhttp.py and handler.py

* refactor: remove custom deserializer, rely on SDK for Forward deserialization

- Remove _deserialize_message_chain from handler.py; use standard
  MessageChain.model_validate() (Forward handling fixed in SDK via
  langbot-app/langbot-plugin-sdk#38)
- Fix group_id type: use int instead of str for OneBot compatibility
- Add warning log when Forward message is used with non-group target

* chore: bump langbot-plugin to 0.2.7 (Forward deserialization fix)

---------

Co-authored-by: RockChinQ <rockchinq@gmail.com>

* feat: message aggregator (#1985)

* feat: aggregator

* fix: resolve deadlock, mutation, and safety issues in message aggregator

- Fix deadlock: don't await cancelled timer tasks inside the lock;
  _flush_buffer acquires the same lock, causing a deadlock cycle
- Fix message_event mutation: keep original message_event unmodified
  to preserve message_id/metadata for reply/quote; only pass merged
  message_chain separately
- Fix Plain positional arg: Plain('\n') → Plain(text='\n')
- Fix float() ValueError: wrap delay cast in try/except
- Add MAX_BUFFER_MESSAGES (10) cap to prevent unbounded buffer growth
- Default enabled to false to avoid surprising latency on upgrade
- Fix flush_all: cancel all timers under one lock acquisition, then
  flush outside the lock to avoid deadlock

---------

Co-authored-by: RockChinQ <rockchinq@gmail.com>

* feat: add session message monitoring tab to bot detail dialog

Add a new "Sessions" tab in the bot detail dialog that displays
sent & received messages grouped by sessions. Users can select
any session to view its messages in a chat-bubble style layout.

Backend changes:
- Add sessionId filter to monitoring messages endpoint
- Add role column to MonitoringMessage (user/assistant)
- Record bot responses in monitoring via record_query_response()
- Add DB migration (dbm019) for the new role column

Frontend changes:
- New BotSessionMonitor component with session list + message viewer
- Add Sessions sidebar tab to BotDetailDialog
- Add getBotSessions/getSessionMessages API methods to BackendClient
- Add i18n translations (en-US, zh-Hans, zh-Hant, ja-JP)

Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>

* refactor: remove outdated version comment from PipelineManager class

* fix: bump required_database_version to 19 to trigger monitoring_messages.role migration

* fix: prevent session message auto-scroll from pushing dialog content out of view

Replace scrollIntoView (which scrolls all ancestor containers) with
direct scrollTop manipulation on the ScrollArea viewport. This keeps
the scroll contained within the messages panel only.

* ui: redesign BotSessionMonitor with polished chat UI

- Wider session list (w-72) with avatar circles and cleaner layout
- Richer chat header with avatar, platform info, and active indicator
- User messages now use blue-500 (solid) instead of blue-100 for
  clear visual distinction
- Metadata (time, runner) shown on hover below bubbles, not inside
- Proper empty state illustrations for both panels
- Better spacing, rounded corners, and shadow treatment
- Consistent dark mode styling

* fix: infinite re-render loop in DynamicFormComponent

The useEffect depended on onSubmit which was a new closure every
parent render. Calling onSubmit inside the effect triggered parent
state update → re-render → new onSubmit ref → effect re-runs → loop.

Fix: use useRef to hold a stable reference to onSubmit, removing it
from the useEffect dependency array.

Also add DialogDescription to BotDetailDialog to suppress Radix
aria-describedby warning.

* fix: remove .html suffix from docs.langbot.app links (Mintlify migration)

* style: fix prettier and ruff formatting

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Typer_Body <mcjiekejiemi@163.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Typer_Body <marcelacelani74@gmail.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Guanchao Wang <wangcham233@gmail.com>
Co-authored-by: fdc310 <82008029+fdc310@users.noreply.github.com>
Co-authored-by: Dongze Yang <50231148+ydzat@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Happy <yesreply@happy.engineering>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

eh: Feature enhance: 新功能添加 / add new features m: Lifecycle 启动/关闭流程 / Bootstrap & application life cycle pd: Need testing pending: 待测试的PR / PR waiting to be tested size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants