fix(webui): disable dashboard auto update checks

feat(plugins): add plugin marketplace route
This commit is contained in:
LoveLosita
2026-05-12 11:20:28 +08:00
parent f75d98900f
commit 587aa0a531
6 changed files with 555 additions and 497 deletions

804
计划.md
View File

@@ -1,372 +1,530 @@
# 私聊定时跟进 V1 计划
# 私聊拟人性增强分阶段计划
## 当前结论
- 本方案只支持私聊,不支持群聊
- 旧草案中的“到点直接发送预先写死的 `message_text`”不再作为 V1 主方案
- V1 改为“定时器负责唤醒当前私聊会话的正常思考流程”,由 LLM 基于到点时的当前上下文决定是否主动发言
- 为了支持“主动发言但不强行回复某条旧消息”,需要新增一个与 `reply` 平行的主动私聊发送工具。
- 平台出口、消息存储、Maisaka 上下文写回、Napcat 适配等链路应尽量复用现有实现,不新造一条发送通道。
## 总体目标
- 在尽量保留当前 maibot 主干能力与最近更新的前提下,增强私聊中的连续感、活人感与主动性
- 优先修复“私聊对象信息容易遗忘”“聊天断开后像失忆”“上下文窗口过于保守”这三类核心问题
- 再逐步增加“定时跟进”“主动私聊”“关系阶段推进”等能力
- 落地时坚持“平行新增、契约尽量不变”:
- 不破坏现有 `reply` 语义
- 新能力优先通过新工具、新配置、新任务表、新 metadata 增量扩展
- 尽量不改现有 WebUI 聊天消息结构与事件结构
## 目标
- 让 LLM 可以在当前私聊里创建一条未来触发的“定时跟进任务”。
- 到点后不直接发固定文本,而是重新进入该私聊的正常 planner 思考流程。
- 到点思考时直接复用现有上下文窗口、原有 planner prompt、原有工具机制。
- 如果 planner 判断现在适合主动发言,则调用新的主动私聊发送工具发出消息。
- 如果 planner 判断现在不适合打扰,则可以不发言并直接结束本轮。
## 借鉴来源
## 明确不做
- 不做群聊版定时跟进。
- 不做跨会话发送;当前私聊中创建的任务,只能唤醒当前私聊,也只能向当前私聊主动发言。
- 不做“任意指定目标用户/目标 session 的消息发送器”。
- 不做复杂多意图分类,不引入冗余的 `intent_key`
- 不伪造一条新的用户入站消息来驱动到点逻辑
-把主动 follow-up 强行伪装成 `reply(msg_id=...)`
### 1. `MoFox-Core`
- 主要借鉴:
- 私聊对象关系信息常态注入
- 主动前护栏判断
- `WAITING / IDLE` 状态机思路
- 发完后等待回复、超时再判断是否继续发言的活人感逻辑
-直接照搬:
- AFC / KFC 全套 runtime
- 强耦合插件系统
- 其整套数据库结构
## 核心方案
### 2. `Muice-Chatbot`
- 主要借鉴:
- 轻量主动开场/问候思路
- 随机主动话题池思路
- 不直接照搬:
- 到点直接发送固定文本
- 过于依赖固定 prompt 的主动对话实现
### 1. 定时器从“文本发送器”改为“流程进入器”
- 当前用户在私聊中表达未来某个时间点需要 bot 主动跟进
- Planner 调用新的定时跟进工具,写入一条未来任务
- 到点后,后台调度器不直接发消息,而是唤醒对应私聊会话的 Maisaka planner
- planner 结合当前上下文重新判断:
- 是否应该主动发言
- 现在该说什么
- 是否应该放弃本次跟进
- 是否应该重新约下一个时间点
## 当前问题
- 私聊对话者信息经常会被忘记,应该被常态化注入
- 当前上下文管理偏保守,没充分发挥大上下文模型的能力
- 私聊 runtime 重建后不会自动回灌最近历史,聊天断开一阵子后容易出现“失忆”
- 人物信息、关系信息、共同经历主要依赖临时检索,缺少稳定默认上下文。
- planner 查到的信息不能稳定传递到 replyer导致回复阶段再次遗忘。
### 2. 新增主动私聊发送工具
- 保留现有 `reply`
- 语义仍然是“针对某条已有消息进行回复”
- 仍然依赖 `msg_id`
- 新增 `send_private_message`
- 语义是“在当前私聊里主动发出一条可见消息”
- 不依赖 `msg_id`
- 不默认引用旧消息
- 这样可以避免 QQ/Napcat 侧展示成“永远在回复某条旧消息”,更符合真实私聊中的自然主动聊天。
## 当前准备增加的能力
- 定时跟进机制:允许 LLM 在当前私聊中创建未来跟进任务,到点后重新唤醒 planner 判断要不要主动开口。
- 主动私聊发送工具:新增不依赖 `msg_id``send_private_message`,作为 `replyer` 的新发送出口,让主动聊天不再伪装成回复旧消息。
- 到点前护栏机制:加入静默时段、最小主动间隔、可选每日上限,避免技术上能发但体验上打扰。
- 阶段以及主线机制:允许你在私聊中像玩 galgame 一样,和小麦进行逐步推进的互动。
- 为后续私聊状态机预留扩展:后面再补“发完等回复、超时再判断”的活人感机制。
### 3. 发送出口继续复用现有通道
- `send_private_message` 不新造平台出口。
- 它和 `reply` 一样,最终复用现有:
- `send_service`
- `Platform IO`
- 现有 driver / adapter
- Napcat 出口
- 这样发送成功后,仍可复用现有:
- 消息写库
- memory automation
## 兼容性原则
### 前后端契约
- 现有 `reply` 工具保持原语义、原参数、原发送形态不变。
- 新增 `send_private_message``schedule_private_followup` 时,采用与现有 builtin tool 平行的注册、执行、记录方式。
- 现有聊天消息 `WsMessage / ChatMessage / MessageSegment` 契约尽量不变:
- 主动私聊发出的消息仍然是普通 bot 可见消息
- 不新增专用 `MessageSegment.type` 作为前提
- 现有工具记录结构尽量不变:
- 继续复用现有 tool record / tool result 链路
- 新增内容尽量放在 `tool_name``tool_data`、metadata 中
### 配置与 WebUI
- 配置面优先平行新增独立配置块,默认值完整,保证旧配置不改也能运行。
- WebUI V1 不应成为主阻塞项:
- 新功能优先复用现有聊天渲染与工具渲染
- 后续再考虑做任务管理页、状态展示页等增强 UI
---
## 阶段 0开发期稳定化
### 本阶段要解决的问题
- 当前准备进入连续多阶段改造,如果开发过程中误触自动更新,容易导致本体与 WebUI 版本漂移。
- 一旦前后端版本不一致,后续上下文、工具、调度器相关改造会很难排障。
- private-main 开发期间需要一个尽量稳定、可复现的运行基线。
### 本阶段要增加/修改的点
- `☐` 断开 WebUI 自动更新检测
- `☐` 断开本体自动更新检测
- `☐` 避免开发期自动拉取或自动提示更新
- `☐` 保留显式手动恢复/更新路径
### 如何做
- 找到当前 WebUI 与本体的更新检查入口:
- 启动时自动检查
- 定时轮询检查
- 页面内自动提示
- 开发期优先做“禁用自动检查”,而不是修改版本号或伪造远端结果。
- 尽量采用可逆方案:
- 配置开关
- 环境变量
- 显式开发模式判断
- 如果本体和 WebUI 的更新逻辑是分开的,也应分别断开,避免只关一边。
### 借鉴来源
- 不依赖 `MoFox-Core``Muice-Chatbot`
- 这是当前 private-main 连续改造前的工程防护阶段。
### 验收标准
- 启动本体时,不再主动发起自动更新检查。
- 启动 WebUI 时,不再自动弹更新提示或自动轮询更新状态。
- 本体日志和 WebUI 调试输出中,不再出现自动更新任务被触发的痕迹。
- 若后续需要更新,仍存在显式、可控、可手动触发的更新路径。
---
## 阶段 1修复私聊遗忘问题
### 本阶段要解决的问题
- 私聊对象信息经常被忘记。
- 聊天断开后再回来,像从空白状态重新开始。
- planner 查到的信息不稳定replyer 容易再次遗忘。
### 本阶段要增加/修改的点
- `☐` 私聊对象信息常态注入
- `☐` runtime 重建时自动回灌最近历史
- `☐` planner 到 replyer 的信息继承增强
- `☐` 上下文选择策略从“纯按条数裁切”调整为“更适合大上下文模型”的策略
### 本阶段目标预算
- 以“大上下文真正可用,但不盲目塞满”为原则,先把私聊 planner 的目标输入预算定在 **256K token 级别**
- 不建议把整整 256K 都让历史消息吃满,应预留系统提示、工具定义、注入块、输出空间。
- 第一版建议按下面的思路分配:
- 总目标预算:`256K`
- 预留给系统提示 / 工具定义 / injected reminders / 输出余量:`32K ~ 64K`
- 可用于“历史消息 + 人物信息块 + 共同事件块”的主预算:`160K ~ 220K`
- replyer 阶段不必与 planner 一样大,可以显著更小:
- 优先依赖 planner 已整理出的参考信息
- replyer 可先控制在 `32K ~ 64K` 级别
### 本阶段要先定下的几个原则
- **历史回灌** 不是滑动窗口本身,而是滑动窗口的前置步骤。
- **token 滑窗** 不应只做“超了就 pop”而应做“固定高优先级块保底 + 历史候选池按 token 预算裁切”。
- **人物信息块、当前约定、最近共同事件** 的优先级应高于普通旧聊天。
- **planner 与 replyer 的上下文策略不必完全一样**
- planner 负责大窗理解
- replyer 负责在较小上下文内稳定生成
- **必须保留降级路径**
- 即使 tokenizer 暂时不可用,也能退回近似估算模式
- 但默认目标是本地 token 预算裁切,而不是继续硬按条数
### 如何做
- 为当前私聊补一个轻量信息聚合入口:
- 优先从现有 `person_info`
- 再从 A_memorix 的人物画像/关系证据中补充
- 统一格式化为稳定上下文块,默认注入 planner
- 在 runtime 重建时,按 `session_id` 从消息库回灌最近一段真实聊天历史,而不是从空 `_chat_history` 开始。
- replyer 生成阶段不再过度依赖临时工具结果,而是显式承接 planner 已整理出的稳定参考信息。
- 调整上下文窗口策略:
- 不再只用固定条数近似控制
- 引入更大的私聊历史回放范围
- 优先保留“人物关系/最近共同事件/当前约定”这类高价值上下文
### 1.1 runtime 历史重建
- 触发时机:
- 私聊 runtime 首次创建时
- 私聊 runtime 被回收后再次恢复时
- 定时跟进任务唤醒私聊 runtime 时
- 基本流程:
1. 根据 `session_id` 查询最近一段真实消息
2. 将消息重新构造成 Maisaka 使用的 `LLMContextMessage`
3. 写回 `_chat_history`
4. 再叠加本轮新收到的消息与 injected reminders
- 第一版建议:
- 先取最近 `200 ~ 1000` 条私聊真实消息作为“候选历史池”
- 不是全部送给模型,而是交给后续 token 滑窗裁切
- 注意:
- 历史重建应优先复用现有消息入库格式和 `SessionBackedMessage` 构建逻辑
- 不要单独造一套“历史专用消息对象”
### 1.2 本地 tokenizer / token 预算器
- 当前项目里已有 token 使用统计,但那是模型返回 usage 后的结果,不足以做请求前选窗。
- 因此本阶段需要新增一个“请求前 token 估算器”抽象层。
- 目标不是一开始追求 100% 精确,而是先把“按 token 预算裁切”跑通。
- 建议方案:
- 新增统一的 `context_token_counter` / `prompt_token_estimator`
- 输入为即将送给模型的消息列表或其序列化结果
- 输出为估算 token 数
- tokenizer 选择建议:
- 优先选择与当前 OpenAI-compatible 请求栈兼容的本地 tokenizer 方案
- 若误差验证可接受,可直接作为私聊上下文预算器
- 若误差过大,再考虑换成更贴近目标模型的 tokenizer
- 降级方案:
- tokenizer 初始化失败时,可暂时退回“字符数 / 中文字数近似预算”
- 但只作为 fallback不作为主路径
### 1.3 token 滑动窗口
- 目标不是“把 256K 塞满”,而是“尽量稳定地用到 256K 级别能力”。
- 建议采用“两层预算”:
- 第一层:固定保底块
- 第二层:历史滑窗块
- 固定保底块建议包含:
- 系统提示
- 当前私聊对象信息块
- 当前约定 / 当前跟进原因
- 最近共同事件摘要
- 必要的工具定义
- 历史滑窗块建议逻辑:
1. 从最近消息开始向前累加
2. 每加入一条,重新累加 token
3. 超预算则停止
4. 保证最近几轮真实对话优先进入窗口
- 不建议简单地“全拼后从头一直 pop 到合法”作为唯一策略。
- 更推荐:
- 先固定高优先级块
- 再对普通历史做从近到远的预算填充
### 1.4 高优先级上下文保底
- 下面这些信息不应与普通旧聊天放在同一优先级:
- 当前私聊对象是谁
- 你和对方目前是什么关系
- 近期共同事件
- 最近承诺/约定
- 当前定时跟进原因
- 这些内容即使在历史极长的情况下,也应尽量保底进入窗口。
- 这部分内容应从:
- `person_info`
- A_memorix 的人物画像 / 关系证据 / episode
- 当前会话内最近承诺
统一汇总出来
### 1.5 planner → replyer 信息继承
- 目前 replyer 容易再次遗忘,核心原因之一是它并不稳定承接 planner 已经查到和整理过的信息。
- 第一版建议:
- planner 输出时显式生成“稳定参考信息块”
- replyer 直接消费这块,而不是只依赖再看一遍历史
- 这样即使 replyer 窗口比 planner 小很多,也能保持人物信息连续性。
### 1.6 配置建议
- 本阶段建议新增但保持默认兼容的配置项,例如:
- `chat.private_context_rebuild_enabled`
- `chat.private_context_rebuild_recent_limit`
- `chat.private_context_token_budget`
- `chat.private_context_reserved_tokens`
- `chat.private_context_inject_relation_info`
- `chat.private_context_recent_events_limit`
- `chat.replyer_context_token_budget`
- 默认值应尽量保守,避免对现有用户造成突然的延迟或成本上升。
### 借鉴来源
- `MoFox-Core`
- 借鉴其“关系信息 + 印象 + 偏好 +聊天流印象”统一拼装进私聊上下文的思路
- `Muice-Chatbot`
- 基本不借鉴实现,只参考“最近历史 + 旧记忆回注入”的轻量骨架
### 验收标准
- 同一个私聊会话在沉默一段时间后再次收到消息bot 不应明显忘记对方是谁。
- 重建后的 runtime 能带上最近一段真实聊天历史,而不是只看最后一条消息。
- 私聊中不需要用户重复提醒bot 也能较稳定记住:
- 对方称呼
- 近期正在聊的事
- 最近约定/承诺
- 在日志或调试信息里,能看到“重建历史条数”“对象信息注入块”的明确痕迹。
- 能在日志或调试信息里看到:
- 候选历史条数
- 最终入窗条数
- 估算 token 数
- 被预算裁掉的原因
- 在高上下文测试下planner 能稳定工作在 `256K` 级别预算附近,而不是仍然被 60 条历史限制住。
- replyer 即使使用更小窗口,也不应明显丢失 planner 已确认的人物信息。
---
## 阶段 2定时跟进 V1
### 本阶段要解决的问题
- 当前私聊没有“我之后再来找你”的能力。
- 即使用户和 bot 约好了未来某个时间点,系统也无法主动在那时重新思考。
- 需要主动发言时,只能强行伪装成 `reply(msg_id=...)`,不自然。
- 如果直接让 planner 裸写 `message_text` 并发送,会让主动私聊和普通回复走成两套文案体系。
### 本阶段要增加/修改的点
- `☐` 新 builtin tool`schedule_private_followup`
- `☐` 新 builtin tool`send_private_message`
- `☐` 私聊定时跟进任务表
- `☐` 后台调度器
- `☐` runtime 的“带 reminder 唤醒 planner”入口
### 如何做
- `schedule_private_followup`
- 只允许在当前私聊里创建未来任务
- 记录触发时间、跟进原因、当时承诺话术
- 后台调度器
- 轮询 `pending` 任务
- 到点后 claim 任务并唤醒当前私聊 runtime
- runtime 新入口
-`<system-reminder>` 形式注入“这不是用户新消息,而是一次到点跟进触发”
- planner 基于当前上下文重新判断要不要发
- `send_private_message`
- 不直接裸发 `message_text`
- 而是作为 `replyer` 的新发送语义出口
- 复用现有人设、表达习惯、replyer 文案生成链路
- 最终仍复用现有 `send_service` / Platform IO / 写库 / 历史同步
### 借鉴来源
- `MoFox-Core`
- 借鉴“主动思考不是直接发消息,而是先重进上下文再判断”的理念
- `Muice-Chatbot`
- 明确不采用其“到点直接发固定文本”的路径
- 当前 maibot 架构
- 延续“planner 负责想、replyer 负责写”的职责分离,不让主动私聊成为例外
### 验收标准
- 私聊中可以成功创建一条未来跟进任务。
- 到点后系统会唤醒 planner而不是直接发写死文本。
- planner 可以根据当前情况选择:
- 主动发一条消息
- 什么都不发,直接结束
- 再次约下一次跟进
- 主动发出的消息是普通 bot 消息,不带旧消息引用。
- 主动发出的文本风格应与普通 `reply()` 路径保持一致,不应明显像另一套文案系统。
- 发送成功后仍会进入:
- 现有消息存储
- Maisaka 历史同步
- memory automation
## 端到端流转链路
### 1. 用户触发
- 用户在私聊中表达未来某个时间点需要 bot 主动跟进。
- 当前消息照常进入现有链路:
- 消息接收
- HeartFlow runtime
- Timing Gate
- Planner
### 2. Planner 决策
- Planner 分析当前局势后,可直接调用定时跟进工具。
- 工具应为 planner 直接可见的 action tool不走 deferred tools。
- 同一轮里planner 仍可继续:
- 调用 `reply`
- 正常回复用户
- 告知用户已经记下这次未来跟进
### 3. 工具执行
- 暂定工具名:`schedule_private_followup`
- 工具接收参数后,直接写入任务存储。
- 工具只允许操作当前私聊会话,不允许 LLM 指定别的 session。
- tool result 与其他工具一样,复用现有 tool call 记录与上下文链路。
### 4. 后台调度器轮询
- 独立后台调度器定期扫描到期任务。
- 扫描条件:
- `status = pending`
- `send_at_ts <= now`
- 任务取出后,不直接发送,而是进入“会话唤醒”流程。
### 5. 会话唤醒
- 调度器根据 `session_id` 定位或恢复对应私聊会话。
- 如果对应 runtime 当前不在内存中,应先恢复/创建该会话的 Maisaka runtime。
- 调度器调用一个新的运行时入口,例如:
- `trigger_scheduled_followup(task)`
- 或同等语义的方法
- 这个入口负责以“额外注入消息”的形式进入 planner而不是伪造用户消息。
### 6. 到点后的 planner 行为
- 唤醒后的 planner 继续使用项目原有的主 prompt。
- 同时在请求尾部追加一条 `injected_user_messages`,内容使用项目已有的 `<system-reminder>` 风格。
- planner 基于当前上下文重新分析后,可以:
- 调用 `send_private_message`
- 调用 `reply`(仅当它明确需要围绕某条旧消息回复时)
- 调用 `finish`
- 再次调用 `schedule_private_followup`
### 7. 主动发送
- 当 planner 判断应主动发言时,调用 `send_private_message`
- `send_private_message` 直接复用现有 `send_service.text_to_stream_with_message(...)`
- 发送参数应满足:
- `stream_id = 当前 ToolExecutionContext.session_id`
- `storage_message = True`
- `sync_to_maisaka_history = True`
- `maisaka_source_kind` 建议使用新的主动发送来源标记,例如:
- `proactive_send`
- 若后续需要区分定时唤醒来源,可再在 metadata 中补充 `trigger_source=scheduled_followup`
### 8. 任务完成
- 本次唤醒流程成功结束后,原任务应被标记为完成。
- “完成”不等于“一定发了消息”。
- 只要本次唤醒和 planner 处理已成功结束,即可完成任务,并记录最终动作结果。
## 工具设计
### 工具 1`schedule_private_followup`
### `send_private_message` 设计细化
#### 工具职责
- 当前私聊创建一条未来触发的定时跟进任务
- 任务到点后唤醒当前私聊的 planner而不是直接发送固定文本。
#### 工具参数
- `send_at`
- 类型:`string`
- 含义:触发时间
- 建议:由 LLM 提供标准化后的绝对时间字符串
- `followup_reason`
- 类型:`string`
- 含义:这次未来跟进的原因/事项摘要
- 作用:到点时注入 reminder帮助 planner 理解为什么会被唤醒
- `assistant_commitment_text`
- 类型:`string`
- 含义当前这轮里bot 对用户作出的那句“未来会来找你/提醒你/跟进你”的承诺话术
- 作用:到点时也注入给 planner帮助它和之前的承诺保持一致
- `replace_existing`
- 类型:`boolean`
- 含义:是否覆盖当前私聊中尚未执行的旧跟进任务
- 默认建议:`false`
#### 工具隐含上下文
- 不要求 LLM 传 `session_id`
- 不要求 LLM 传 `user_id`
- 后端直接从当前 `ToolExecutionContext.session_id` 取目标私聊
- 如果当前不是私聊,工具直接失败
#### 工具返回
- 成功时至少返回:
- `task_id`
- `session_id`
- `send_at`
- `followup_reason`
- `assistant_commitment_text`
- `replace_existing`
- 若发生覆盖,返回被取消的旧任务 ID 列表
- 失败时返回明确原因:
- 非私聊
- 时间非法
- 跟进原因为空
- 承诺话术为空
- 存储失败
### 工具 2`send_private_message`
#### 工具职责
- 在当前私聊会话中主动发送一条可见消息。
- 当前私聊会话中主动发出一条可见消息
- 不依赖 `msg_id`
- 不默认带引用回复。
- 语义上是“主动开口”,但文案生成仍应走 `replyer`,而不是绕开 replyer 直接裸发。
#### 工具参数
- `message_text`
- 类型:`string`
- 含义:要主动发送给当前私聊对象的文本内容
#### 参数设计建议
- 不建议把主参数设计成裸 `message_text`
- 更推荐让 planner 提供“为什么说”,让 replyer 负责“具体怎么说”。
- 第一版建议参数:
- `proactive_reason`
- 类型:`string`
- 含义:这次主动发言的直接理由,作为 replyer 的“最新推理”
- `reference_info`
- 类型:`string`
- 含义:本轮主动发言依赖的事实性参考信息
- `trigger_source`
- 类型:`string`
- 含义:触发来源,例如 `scheduled_followup`
- `assistant_commitment_text`
- 类型:`string`
- 含义:若这次主动发言是在延续先前承诺,可作为风格一致性参考
#### 工具隐含上下文
- 不要求 LLM 传 `session_id`
- 不允许 LLM 指定别的目标会话
- 后端直接使用当前 `ToolExecutionContext.session_id`
#### 实现建议
- `send_private_message` 应复用当前 `reply()` 已有的 replyer 生成链路。
- 推荐执行路径:
1. planner 调用 `send_private_message`
2. tool 内部获取当前会话 replyer
3. 以“主动发言场景”调用 `generate_reply_with_context(...)`
4. 此时 `reply_message=None`
5. `reply_reason=proactive_reason`
6. `reference_info` 中整理注入 `reference_info + assistant_commitment_text + trigger_source`
7. replyer 生成文本后,再复用现有发送链路发出
#### 工具返回
#### 返回建议
- 成功时至少返回:
- `session_id`
- `message_text`
- `generated_message_text`
- `sent_message_id`
- `maisaka_source_kind`
- 失败时返回明确原因:
- 非私聊
- 文本为空
- replyer 生成失败
- 发送失败
### 工具关系
- `reply`:回应某条已有消息
- `send_private_message`:主动发言,不依赖某条已有消息
- `schedule_private_followup`:创建未来跟进任务,负责到点后唤醒 planner
---
## 任务数据模型
## 阶段 3主动私聊护栏与轻量主动能力
### 建议字段
- `id`
- `session_id`
- `send_at_ts`
- `status`
- `followup_reason`
- `assistant_commitment_text`
- `created_at_ts`
- `updated_at_ts`
- `created_by_tool_call_id`
- `cancelled_by_tool_call_id`
- `triggered_at_ts`
- `completed_at_ts`
- `completion_action`
- `sent_message_id`
- `last_error`
- `replace_existing`
### 本阶段要解决的问题
- 即使有定时跟进,系统也缺少“主动前的工程护栏”。
- 后续如果要做更常态化的主动私聊,没有最小打扰控制会很容易打扰用户。
### 状态
- `pending`
- `running`
- `completed`
- `cancelled`
- `failed`
### 本阶段要增加/修改的点
- `☐` 静默时段
- `☐` 最小主动间隔
- `☐` 可选每日上限
- `☐` skip reason 与 guardrail snapshot
- `☐` 轻量主动开场/问候 fallback
### 状态语义
- `pending`:已创建,等待到点唤醒
- `running`:调度器已取出,正在执行本次唤醒
- `completed`:本次唤醒流程已成功结束,无论是否真的发出消息
- `cancelled`:被显式取消或被新任务覆盖
- `failed`:本次唤醒流程执行失败
### 如何做
- 在“任务到期”与“真正唤醒 planner”之间插入规则层
- 私聊校验
- 状态校验
- 静默时段判断
- 最小主动间隔判断
- 可选每日上限判断
- 对被跳过的任务记录:
- `skip_reason`
- `guardrail_snapshot_json`
- 为未来“非约定型主动私聊”预留轻量主动开场能力:
- 轻问候
- 轻话题冒泡
- 但此时仍不做完整主动聊天系统
### 完成动作建议
- `sent_private_message`
- `skipped`
- `rescheduled`
- `unknown`
### 借鉴来源
- `MoFox-Core`
- 重点借鉴其主动思考调度器中的工程护栏思路
- `Muice-Chatbot`
- 借鉴其轻量问候/轻话题池思路,仅作为 fallback
## 覆盖规则
### 验收标准
- 明显不适合打扰的时段,任务会被安全跳过或延后,而不是强行唤醒 planner。
- 日志中能明确看到“为什么没发”的 skip reason。
- 即使以后加入更强主动私聊,这一层规则也能直接复用。
### V1 定义
- `replace_existing = true` 时:
- 将当前 `session_id` 下所有 `pending` 任务置为 `cancelled`
- 再创建当前新任务
- `replace_existing = false` 时:
- 不取消旧任务
- 直接新增当前任务
---
### 这样做的原因
- 不引入模糊分类
- 不猜“哪个旧任务和哪个新任务算同类”
- 规则简单、确定、易解释
## 阶段 4私聊状态机活人感增强
## 到点注入给 planner 的提示方式
### 本阶段要解决的问题
- 当前主动私聊即使能发,也缺少“发完之后等你回复”的真实感。
- 系统无法区分“及时回”“晚回”“一直没回”。
### 复用原则
- 继续复用原有 [prompts/zh-CN/maisaka_chat.prompt](/D:/mai-bot-align-pre17/prompts/zh-CN/maisaka_chat.prompt)
- 不复制一整份新 prompt
- 不把 reminder 伪装成真实聊天历史消息
- 复用现有 `injected_user_messages` 机制
### 本阶段要增加/修改的点
- `☐` `WAITING / IDLE` 私聊状态机
- `☐` 主动发送后的等待态
- `☐` 超时后再判断是否继续等、追问或闭嘴
- `☐` 连续超时计数与打扰保护
### 风格要求
- 注入内容应沿用项目现有 `<system-reminder>...</system-reminder>` 风格
- 它在请求里以 `user` 身份追加,但语义上明确说明“不是用户刚刚发来的新消息”
- 应提醒 planner
- 这是一次定时跟进触发
- 当前为什么会被唤醒
- 你之前对用户说过什么
- 可以主动发言,也可以不发言
- 除非明确需要回应某条旧消息,否则不要默认调用 `reply`
### 如何做
- 在阶段 2 的基础上,扩展主动发送后的状态记录:
- 最近一次主动发送时间
- 是否正在等待回复
- 等待配置
- 连续超时计数
- 收到用户消息时区分:
- `reply_in_time`
- `reply_late`
- `new_message`
- 等待超时后,再重新进行一轮轻量判断:
- 继续等
- 轻追问
- 结束等待,不再打扰
### 提示文案打样
```text
<system-reminder>
以下内容不是用户刚刚发来的新消息,而是当前私聊的一次定时跟进触发。
### 借鉴来源
- `MoFox-Core`
- 这是最核心的借鉴来源,重点是其 `WAITING / IDLE` 与超时后再思考的设计
- `Muice-Chatbot`
- 基本不借鉴
触发时间:{trigger_at}
### 验收标准
- 主动发言后,系统能进入等待态,而不是把这件事完全忘掉。
- 用户及时回复和很久后才回复时,系统的反应能出现可区分差异。
- 多次未回复后,系统会自动降低继续打扰的倾向。
你之前在这个私聊中设置过一次定时跟进。
当时记录的跟进原因:
{followup_reason}
---
当时你已经对用户说过:
{assistant_commitment_text}
## 阶段 5关系阶段与主线推进
请结合当前上下文重新分析现在是否适合主动发言:
- 如果仍然适合,就继续正常分析,并在需要时调用 send_private_message
- 如果当前话题已经变化、用户已经提到过同类内容、约定已经失效,或者现在不适合打扰,就不要强行发送,直接 finish
- 不要把这段提醒当成用户的新发言。
- 除非你明确是在回应历史中的某条具体消息,否则不要默认调用 reply。
</system-reminder>
```
### 本阶段要解决的问题
- 当前即使记住了人、能主动聊,也还没有真正的关系推进结构
- 缺少“朋友 → 更亲密阶段”的可持续演进逻辑
## 与现有链路的复用
### 本阶段要增加/修改的
- `☐` 关系阶段状态
- `☐` 阶段推进条件
- `☐` 近期主线事件/共同事件摘要
- `☐` 更适合私聊恋爱化推进的上下文注入
### Prompt 与上下文
- 复用 Maisaka planner 主 prompt
- 复用现有上下文窗口选择逻辑
- 复用 `injected_user_messages` 追加尾部提醒
### 如何做
- 在现有 `person_info` / A_memorix 证据层之上,增加一层更明确的关系状态表达。
- 阶段推进仍然尽量保持轻规则,不急着做全硬编码 romance engine。
- 优先做:
- 阶段状态
- 最近共同事件摘要
- 对阶段敏感的 prompt 注入
- 后续再看是否要加入更强规则。
### 发送
- `send_private_message` 复用 `send_service.text_to_stream_with_message(...)`
- 继续复用现有 `Platform IO` 和 Napcat 出口
### 借鉴来源
- `MoFox-Core`
- 借鉴其“关系分数 + 关系阶段 + 印象文案”的分层思路
- 不直接采用其当前阶段命名作为最终产品逻辑
- `Muice-Chatbot`
- 不足以支撑这一阶段
### 存储
- 复用现有消息发送成功后的消息存储链路
### 验收标准
- 私聊中的称呼、关心方式、主动发言方式,会随关系阶段出现可感知变化。
- 最近共同事件能够被作为关系推进依据,而不是每次都从零开始。
- 系统在长期私聊中呈现出更稳定的关系连续性,而不是只有短期记忆连续性。
### 上下文写回
- 复用 `sync_to_maisaka_history=True`
- 复用 runtime 的 `append_sent_message_to_chat_history(...)`
---
### Tool call 记录
- 创建任务时,复用现有 Planner tool 执行记录链路
- 到点唤醒不伪造一条新的 LLM tool call
- 到点执行本质上属于调度器唤醒会话,而不是伪造用户发言
## 配置与 WebUI 影响评估
## 需要补的实现模块
### 配置面
- 配置面预计是“小改”,不是大改。
- 建议新增独立配置块,例如:
- `private_followup.enabled`
- `private_followup.scheduler_interval_seconds`
- `private_followup.quiet_hours_start`
- `private_followup.quiet_hours_end`
- `private_followup.min_interval_between_proactive_seconds`
- `private_followup.max_daily_followups_per_session`
- `private_followup.inject_relation_info`
- `private_followup.recent_shared_events_limit`
- 原则:
- 默认值完整
- 老配置不动也能跑
- 优先改模板并递增版本号
### 1. 新内置工具
- 新增 `schedule_private_followup` builtin tool
- 新增 `send_private_message` builtin tool
### WebUI 面
- V1 到 V3 原则上不需要大改聊天协议。
- 聊天渲染层:
- `send_private_message` 发送的仍然是普通 bot 消息
- 不要求新增 `MessageSegment.type`
- 工具渲染层:
- 新工具复用现有 tool record 展示链路
- 前端最多看到新的 `tool_name`
- 可选增强:
- 定时跟进任务列表
- 任务取消/重试管理页
- 主动护栏状态可视化
- 私聊状态机状态展示
### 2. 任务存储
- 新增一张私聊定时跟进任务表
- 提供最小操作:
- create
- cancel pending by session
- list due pending
- mark running
- mark completed
- mark failed
## 分阶段验收顺序建议
- 第 0 阶段先稳定开发环境,避免自动更新打断改造过程。
- 第一阶段先解决遗忘与历史重建,不急着上主动聊天。
- 第二阶段打通定时跟进 V1。
- 第三阶段补主动护栏与轻量主动能力。
- 第四阶段再做 `WAITING / IDLE` 状态机。
- 第五阶段最后做关系阶段与主线推进。
### 3. 后台调度器
- 独立异步循环
- 固定间隔扫描 due tasks
- 负责 claim 任务、唤醒会话、更新状态
### 4. Maisaka 运行时入口
- 为运行时新增一个“带 reminder 进入 planner 一轮”的入口
- 该入口负责把 `<system-reminder>` 注入到 planner 请求尾部
### 5. 主程序启动
- 在现有后台任务体系中注册该调度器
## 最小验证路径
### 验证目标
- 确认创建任务、到点唤醒、重新思考、主动发送、写库、上下文同步都能走通
### 建议路径
1. 在私聊中让 bot 创建一条几分钟后的 `schedule_private_followup`
2. 当轮正常 `reply` 给用户,说明之后会再来跟进
3. 到点后由调度器唤醒当前私聊的 planner
4. planner 收到 `<system-reminder>` 后重新分析
5. 若判断合适,调用 `send_private_message`
6. 发送应复用现有 Napcat / Platform IO 出口成功发出
7. 发出的消息应进入:
- 现有消息存储
- Maisaka 上下文历史
- 现有 memory automation
## 当前建议的落地顺序
- 第一步:先把工具边界定下来:`schedule_private_followup``send_private_message`
- 第二步:把任务表与任务存储定下来
- 第三步:补运行时“带 reminder 唤醒 planner”入口
- 第四步:补后台调度器并接入主程序
- 第五步:打通主动发送、写库、上下文同步
- 第六步:再考虑取消/查看任务工具
## 当前最推荐的开发顺序
1. 断开 WebUI / 本体自动更新检测
2. 私聊对象信息常态注入
3. runtime 历史重建
4. planner → replyer 信息继承增强
5. `schedule_private_followup`
6. `send_private_message`
7. `send_private_message` 的 replyer 驱动实现
8. 后台调度器与 runtime 唤醒入口
9. 到点前护栏
10. `WAITING / IDLE` 扩展
11. 阶段/主线推进