跳转到内容

架构

  • BubFramework:创建插件管理器、加载插件并执行 process_inbound()
  • BubHookSpecs:定义所有 hook 契约(src/bub/hookspecs.py)。
  • HookRuntime:以 sync/async 兼容方式执行 hook(src/bub/hook_runtime.py)。
  • Agent:内置的模型与工具运行时(src/bub/builtin/agent.py)。
  • ChannelManager:启动 channel、缓冲入站消息并路由出站消息(src/bub/channels/manager.py)。

BubFramework.process_inbound() 当前按以下顺序执行:

  1. 通过 resolve_session(message) 解析 session(为空时回退到 channel:chat_id)。
  2. 使用 BubFramework.workspace 中的 _runtime_workspace 初始化状态。
  3. 合并所有 load_state(message, session_id) 返回的字典。
  4. 通过 build_prompt(message, session_id, state) 构建 prompt(为空时回退到入站 content)。
  5. 执行 run_model_stream(prompt, session_id, state)
  6. 对每个流事件,调用 OutboundChannelRouter.dispatch_event(...),当目标 channel 存在时转发到 channel.on_event(event, message)
  7. 始终在 finally 块中执行 save_state(...)
  8. 通过 render_outbound(...) 渲染出站批次,然后展平。
  9. 如果没有出站消息,生成一条回退出站。
  10. 通过 dispatch_outbound(message) 分发每条出站消息。

如果没有插件实现 run_model_streamHookRuntime 会回退到 run_model(prompt, session_id, state),并将返回的文本适配为仅含一个文本块的流。

注册顺序:

  1. 内置插件 builtin
  2. 外部 entry point(group="bub"

执行顺序:

  1. HookRuntime 反转 pluggy 的实现顺序,因此后注册的插件先执行。
  2. call_first 返回第一个非 None 值。
  3. call_many 收集所有实现的返回值(包括 None)。

合并/覆盖细节:

  1. load_state 在合并前再次反转,使高优先级插件在键冲突时胜出。
  2. provide_channelsBubFramework.get_channels() 收集,第一个 channel 名称胜出,因此高优先级插件可以覆盖内置 channel 名称。
  • 对于普通 hook,HookRuntime 不会吞掉实现错误。
  • process_inbound() 捕获顶层异常,通知 on_error(stage="turn", ...),然后重新抛出。
  • on_error 本身是观察者安全的:一个观察者失败不会阻塞其他观察者。
  • 在同步调用(call_first_sync/call_many_sync)中,可等待的返回值会被跳过并记录警告。

内置 BuiltinImpl 的行为包括:

  • build_prompt:支持逗号命令模式;非命令文本可能包含 context_str
  • run_model_stream:委托给 Agent.run()
  • system_prompt:将默认 prompt 与工作区 AGENTS.md 组合。
  • register_cli_commands:安装 rungatewaychat 以及隐藏的诊断命令。
  • provide_channels:返回 telegramcli channel 适配器。
  • provide_tape_store:返回基于文件的 tape store,位于 ~/.bub/tapes

Channel 有两种不同的出站接口:

  • send(message):处理最终渲染的出站消息。
  • on_event(event, message):处理模型仍在运行时的原始流事件。

on_event 是可选的。当 channel 可以从增量渲染、输入指示器、进度更新或部分文本显示中受益时,请实现它。message 参数是原始入站消息,因此 channel 实现通常用它来恢复路由元数据,如目标 channel、chat id、session id 或消息类型。

如果 channel 不需要任何特殊的事件行为,可以忽略 on_event,完全依赖 send()

  • Envelope 有意保持弱类型(Any + 访问器辅助方法)。
  • 跨插件 state 没有全局强制的 schema。
  • 本文档中描述的运行时行为与当前源码一致。