feat: 为 Character 增加外部记忆适配生命周期事件#82
Conversation
|
Warning Rate limit exceeded
You’ve run out of usage credits. Purchase more in the billing tab. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
📝 WalkthroughWalkthroughThis PR adds three new typed event hooks ( ChangesChat Lifecycle Event Hooks
Message Author Name Resolution Improvements
Sequence DiagramsequenceDiagram
participant Client
participant PluginChat
participant MessageCollector
participant EventBus
Client->>PluginChat: trigger chat message
PluginChat->>PluginChat: prepareMessages()
PluginChat->>PluginChat: build systemVariables, inputVariables
PluginChat->>EventBus: emit chatluna_character/before-chat
EventBus-->>PluginChat: before-chat hook complete
PluginChat->>MessageCollector: collect and process messages
MessageCollector->>MessageCollector: build completedMessages
PluginChat->>MessageCollector: service.muteAtLeast()
MessageCollector-->>PluginChat: mute complete
PluginChat->>EventBus: emit chatluna_character/after-chat
EventBus-->>PluginChat: after-chat hook complete
PluginChat-->>Client: return response
Client->>MessageCollector: clear chat history
MessageCollector->>MessageCollector: compute conversationId, isDirect
MessageCollector->>EventBus: emit chatluna_character/clear-chat-history
EventBus-->>MessageCollector: clear-chat-history hook complete
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces lifecycle events for character interactions—specifically before-chat, after-chat, and clear-chat-history—to allow external plugins to hook into the chat process. It also refactors variable handling in the message preparation phase and improves user name resolution logic across the service. The review feedback suggests expanding the locking mechanism in the history clearing method to prevent potential race conditions and recommends consolidating timestamp generation to ensure consistency and proper synchronization of variables when modified by event listeners.
| const groupIds = Object.keys(this._groupLocks).sort() | ||
| const clearedGroupIds = Array.from( | ||
| new Set([ | ||
| ...groupIds, | ||
| ...Object.keys(this._messages), | ||
| ...Object.keys(this._groupTemp) | ||
| ]) | ||
| ).sort() | ||
| const unlocks: (() => void)[] = [] | ||
| for (const gid of groupIds) { | ||
| unlocks.push(await this._lockByGroupId(gid)) |
There was a problem hiding this comment.
In the clear method (clear-all case), the code only acquires locks for sessions already present in _groupLocks. However, it attempts to clear data and notify listeners for all sessions in clearedGroupIds (which includes those in _messages or _groupTemp). This can lead to race conditions if a session is active but hasn't been locked yet. Redefining groupIds to include all relevant session IDs ensures proper locking and cleanup for all active sessions throughout the function.
| const groupIds = Object.keys(this._groupLocks).sort() | |
| const clearedGroupIds = Array.from( | |
| new Set([ | |
| ...groupIds, | |
| ...Object.keys(this._messages), | |
| ...Object.keys(this._groupTemp) | |
| ]) | |
| ).sort() | |
| const unlocks: (() => void)[] = [] | |
| for (const gid of groupIds) { | |
| unlocks.push(await this._lockByGroupId(gid)) | |
| const groupIds = Array.from( | |
| new Set([ | |
| ...Object.keys(this._groupLocks), | |
| ...Object.keys(this._messages), | |
| ...Object.keys(this._groupTemp) | |
| ]) | |
| ).sort() | |
| const clearedGroupIds = groupIds | |
| const unlocks: (() => void)[] = [] | |
| for (const gid of groupIds) { | |
| unlocks.push(await this._lockByGroupId(gid)) | |
| } |
| const inputVariables: Record<string, unknown> = { | ||
| history_new: historyNewMessages | ||
| .join('\n\n') | ||
| .replaceAll('{', '{{') | ||
| .replaceAll('}', '}}'), | ||
| history_last: historyLast, | ||
| time: formatTimestamp(new Date()), | ||
| stickers: '', | ||
| status: temp.status ?? currentPreset.status ?? '', | ||
| trigger_reason: triggerReasonText, | ||
| prompt: session.content, | ||
| built | ||
| } | ||
| const persistedInputVariables: Record<string, unknown> = { | ||
| ...inputVariables, | ||
| history_new: recentMessages | ||
| .join('\n\n') | ||
| .replaceAll('{', '{{') | ||
| .replaceAll('}', '}}'), | ||
| time: formatTimestamp(new Date()) | ||
| } |
There was a problem hiding this comment.
There are two points for improvement in this block:
- The current time is formatted twice using
new Date(). It is better to calculate it once and reuse it to ensure consistency betweeninputVariablesandpersistedInputVariables. persistedInputVariablesis created as a shallow copy ofinputVariablesbefore thechatluna_character/before-chatevent is emitted. If an external plugin injects new variables intoinputVariablesduring the event, these changes will not be reflected inpersistedInputVariables(and thus missing from the persisted history prompt) unless the listener explicitly modifies both. Consider derivingpersistedInputVariablesafter the event emission or ensuring they stay in sync.
const now = new Date()
const timestamp = formatTimestamp(now)
const inputVariables: Record<string, unknown> = {
history_new: historyNewMessages
.join('\n\n')
.replaceAll('{', '{{')
.replaceAll('}', '}}'),
history_last: historyLast,
time: timestamp,
stickers: '',
status: temp.status ?? currentPreset.status ?? '',
trigger_reason: triggerReasonText,
prompt: session.content,
built
}
const persistedInputVariables: Record<string, unknown> = {
...inputVariables,
history_new: recentMessages
.join('\n\n')
.replaceAll('{', '{{')
.replaceAll('}', '}}'),
time: timestamp
}新增 before-chat、after-chat、clear-chat-history 生命周期事件及对应类型,作为 livingmemory 等外部插件的最小接入面。 before-chat 在 prompt 渲染前暴露可注入变量表;after-chat 和 clear-chat-history 均异步发布,避免外部插件阻塞 Character 回复、锁释放或清理流程。 统一实时消息、引用消息和历史消息的发言人名称优先级,优先使用 QQ 昵称或名称,减少群名片导致的身份分裂。 处理 PR review:clear-all 对所有相关 session key 统一加锁;prompt 变量复用同一 timestamp,并让 persisted 变量继承 before-chat 后的 inputVariables。
10d7043 to
2bed5f9
Compare
概要
本 PR 为
chatluna-character增加最小生命周期事件,供 livingmemory 等外部插件在不侵入 Character 主逻辑的情况下完成记忆注入、回复后处理和缓存清理。同时统一部分消息历史中的发言人名称来源,优先使用 QQ 昵称/名称,避免同一用户因群名片差异在记忆或历史上下文中被识别成不同人。
主要改动
chatluna_character/before-chat、chatluna_character/after-chat、chatluna_character/clear-chat-history三个事件及对应 payload 类型。before-chat在 prompt 渲染前暴露systemVariables、inputVariables、persistedInputVariables,允许外部插件注入{living_memory}等变量。after-chat在 Character 成功回复并完成内部状态更新后异步发布上下文,包括已渲染的systemPrompt、消息列表、用户消息、最后回复和状态。clear-chat-history在 Character 内部清理完成后异步通知外部插件清理自身缓存。影响面
Summary by CodeRabbit
New Features
Improvements