文章目录
- [事件驱动架构详解 - TUI 与核心引擎通信机制](#事件驱动架构详解 - TUI 与核心引擎通信机制)
-
- [一 事件驱动架构概述](#一 事件驱动架构概述)
-
- [1.1 整体架构图](#1.1 整体架构图)
- [1.2 核心组件关系](#1.2 核心组件关系)
- [二 EventBus - 事件总线(单例模式)](#二 EventBus - 事件总线(单例模式))
-
- [2.1 核心设计](#2.1 核心设计)
- [2.2 单例实现](#2.2 单例实现)
- [2.3 事件类型定义](#2.3 事件类型定义)
- [2.4 事件类型完整列表](#2.4 事件类型完整列表)
- [2.5 EventBus 核心方法](#2.5 EventBus 核心方法)
- [三 TUI 与核心引擎的通信流程](#三 TUI 与核心引擎的通信流程)
-
- [3.1 完整通信流程图](#3.1 完整通信流程图)
- [3.2 TUI 初始化与事件订阅](#3.2 TUI 初始化与事件订阅)
- [3.3 AgentController 事件订阅](#3.3 AgentController 事件订阅)
- [四 核心通信路径详解](#四 核心通信路径详解)
-
- [4.1 Agent -> UI: 状态变更事件](#4.1 Agent -> UI: 状态变更事件)
- [4.2 Agent -> UI: 消息事件](#4.2 Agent -> UI: 消息事件)
- [4.3 Agent -> UI: 工具事件](#4.3 Agent -> UI: 工具事件)
- [4.4 Agent -> UI: Flag 发现事件](#4.4 Agent -> UI: Flag 发现事件)
- [4.5 UI -> Agent: 用户命令事件](#4.5 UI -> Agent: 用户命令事件)
- [五 事件数据流详解](#五 事件数据流详解)
-
- [5.1 Event 数据结构](#5.1 Event 数据结构)
- [5.2 各事件类型的数据结构](#5.2 各事件类型的数据结构)
- [六 线程安全机制](#六 线程安全机制)
-
- [6.1 TUI 与 Agent 的线程模型](#6.1 TUI 与 Agent 的线程模型)
- [6.2 call_from_thread() 机制](#6.2 call_from_thread() 机制)
- [6.3 EventBus 线程安全实现](#6.3 EventBus 线程安全实现)
- [七 事件驱动架构设计模式总结](#七 事件驱动架构设计模式总结)
-
- [7.1 使用的设计模式](#7.1 使用的设计模式)
- [7.2 架构优势](#7.2 架构优势)
- [八 完整示例:一次完整的用户交互流程](#八 完整示例:一次完整的用户交互流程)
-
- [8.1 场景描述](#8.1 场景描述)
- [8.2 详细流程](#8.2 详细流程)
- [8.3 关键代码路径](#8.3 关键代码路径)
- [九 调试技巧](#九 调试技巧)
-
- [9.1 启用详细事件日志](#9.1 启用详细事件日志)
- [9.2 添加自定义事件处理器](#9.2 添加自定义事件处理器)
- 附录:相关文件索引
事件驱动架构详解 - TUI 与核心引擎通信机制
一 事件驱动架构概述
1.1 整体架构图
Core Engine
EventBus (Singleton)
TUI Layer (Textual)
subscribe/emit
subscribe/emit
on_message
emit_command
ExcaliburApp
ActivityFeed
UserInput
StatusBar
F1 Help
Ctrl+P Pause
EventBus._handlers
AgentController
EGATSPlanner
AgentBackend
SessionStore
1.2 核心组件关系
| 组件 | 文件路径 | 职责 |
|---|---|---|
| EventBus | core/events.py |
事件总线(单例模式) |
| AgentController | core/controller.py |
核心引擎控制器 |
| ExcaliburApp | interface/tui.py |
TUI 应用程序主类 |
| ActivityFeed | interface/components/activity_feed.py |
活动流组件 |
二 EventBus - 事件总线(单例模式)
2.1 核心设计
python
class EventBus:
"""
线程安全的事件总线类,实现发布/订阅通信模式。
该类实现了单例模式,确保整个应用中只有一个事件总线实例。
使用锁机制保证多线程环境下的安全性。
"""
2.2 单例实现
python
# excalibur/core/events.py:90-123
class EventBus:
_instance: Optional["EventBus"] = None # 单例实例
_lock = threading.Lock() # 线程锁
@classmethod
def get(cls) -> "EventBus":
"""
获取单例 EventBus 实例。
使用双重检查锁定模式实现线程安全的单例创建。
"""
with cls._lock:
if cls._instance is None:
cls._instance = cls()
return cls._instance
2.3 事件类型定义
python
# excalibur/core/events.py:19-56
class EventType(Enum):
"""
事件类型枚举,定义所有可用的事件种类。
分类说明:
- Agent -> UI events: 代理向界面发送的事件(4 个核心事件)
- UI -> Agent events: 界面向代理发送的事件(2 个核心事件)
- EGATS Planner events: EGATS 规划器相关的事件
- TDA events: TDA(威胁驱动分析)相关的事件
- Memory events: 记忆子系统相关的事件
"""
2.4 事件类型完整列表
python
# Agent -> UI events (4 essential)
STATE_CHANGED = auto() # 状态变更:idle, running, paused, completed, error
MESSAGE = auto() # 消息事件:代理输出的文本信息
TOOL = auto() # 工具事件:工具的启动/完成状态
FLAG_FOUND = auto() # Flag 发现事件:检测到目标 flag
# UI -> Agent events (2 essential)
USER_COMMAND = auto() # 用户命令事件:pause, resume, stop
USER_INPUT = auto() # 用户输入事件:指令文本
# EGATS Planner events
TREE_NODE_SELECTED = auto() # 树节点选择事件:UCB 算法选择的节点被选中
TREE_NODE_EXPANDED = auto() # 树节点展开事件:新增子节点
TREE_NODE_PRUNED = auto() # 树节点剪枝事件:因高 TDI 值而被剪枝的分支
TREE_BACKPROPAGATE = auto() # 树回溯传播事件:承诺分数更新
TREE_PIVOT_SPAWNED = auto() # 树枢轴生成事件:创建新的枢轴子树
# TDA events
TDI_COMPUTED = auto() # TDI 计算完成事件
MODE_SELECTED = auto() # 模式选择事件:reconnaissance/exploitation/llm_decide
# Memory events
ENTITY_DISCOVERED = auto() # 实体发现事件
CONTEXT_COMPRESSED = auto() # 上下文压缩事件
CREDENTIAL_PROPAGATED = auto() # 凭证传播事件
2.5 EventBus 核心方法
python
# excalibur/core/events.py:124-263
class EventBus:
def subscribe(self, event_type, handler) -> None:
"""
订阅事件类型。
将指定的处理器函数添加到对应事件类型的处理器列表中。
Args:
event_type: 要订阅的事件类型
handler: 事件触发时调用的回调函数,接收 Event 对象作为参数
"""
def unsubscribe(self, event_type, handler) -> None:
"""
取消订阅事件类型。
从指定事件类型的处理器列表中移除指定的处理器函数。
"""
def emit(self, event: Event) -> None:
"""
向所有订阅者广播事件。
该方法会复制当前事件类型的处理器列表,然后遍历每个处理器并调用它们。
使用 contextlib.suppress 防止单个处理器的异常影响其他处理器的执行。
"""
# ========== 便捷方法:常用事件发射 ==========
def emit_state(self, state, details="", target=None, task=None) -> None:
"""广播状态变更事件。"""
def emit_message(self, text: str, msg_type: str = "info") -> None:
"""广播消息事件。"""
def emit_tool(self, status, name, args=None, result=None) -> None:
"""广播工具事件。"""
def emit_flag(self, flag: str, context: str = "") -> None:
"""广播 flag 发现事件。"""
def emit_command(self, command: str) -> None:
"""广播用户命令事件。"""
def emit_input(self, text: str) -> None:
"""广播用户输入事件。"""
三 TUI 与核心引擎的通信流程
3.1 完整通信流程图
AgentController EventBus ExcaliburApp (TUI) AgentController EventBus ExcaliburApp (TUI) 初始化阶段 运行阶段 alt [Tool execution] alt [Flag found] loop [EGATS Loop] 用户交互阶段 alt [User types instruction] subscribe(EVENTS) subscribe(EVENTS) start/continue emit(STATE_CHANGED: running) watch_agent_state("running") hide user_input emit(TREE_NODE_SELECTED) emit(TDI_COMPUTED) emit(MODE_SELECTED) query LLM emit(MESSAGE) _on_agent_message() emit(TOOL: start) emit(TOOL: complete) _on_tool() emit(FLAG_FOUND) _on_flag() emit(USER_COMMAND: pause) set_pause_requested=True emit(STATE_CHANGED: paused) watch_agent_state("paused") show user_input emit(USER_INPUT: text) inject(text) emit(MESSAGE)
3.2 TUI 初始化与事件订阅
python
# excalibur/interface/tui.py:192-223
def _setup_event_handlers(self) -> None:
"""
订阅 agent 事件以更新 UI。
此方法将各种事件类型绑定到对应的事件处理器,实现事件驱动的 UI 更新机制。
"""
if self._events is None:
return
# Agent -> UI events
self._events.subscribe(EventType.STATE_CHANGED, self._on_state_change)
self._events.subscribe(EventType.MESSAGE, self._on_agent_message)
self._events.subscribe(EventType.FLAG_FOUND, self._on_flag)
self._events.subscribe(EventType.TOOL, self._on_tool)
# EGATS planner events
self._events.subscribe(EventType.TREE_NODE_SELECTED, self._on_tree_event)
self._events.subscribe(EventType.TDI_COMPUTED, self._on_tree_event)
self._events.subscribe(EventType.MODE_SELECTED, self._on_tree_event)
self._events.subscribe(EventType.TREE_NODE_PRUNED, self._on_tree_event)
self._events.subscribe(EventType.TREE_PIVOT_SPAWNED, self._on_tree_event)
3.3 AgentController 事件订阅
python
# excalibur/core/controller.py:124-126
def __init__(self, ...):
# ========== 订阅用户事件 ==========
self.events.subscribe(EventType.USER_COMMAND, self._on_user_command)
self.events.subscribe(EventType.USER_INPUT, self._on_user_input)
四 核心通信路径详解
4.1 Agent -> UI: 状态变更事件
python
# excalibur/core/controller.py:154-172
def _set_state(
self,
state: AgentState,
details: str = "",
target: str | None = None,
task: str | None = None,
) -> None:
"""
更新状态并广播事件。
Args:
state: 新的代理状态
details: 状态的详细描述信息
target: 目标地址(可选)
task: 任务描述(可选)
"""
self._state = state
self.events.emit_state(state.value, details, target=target, task=task)
python
# excalibur/interface/tui.py:265-291
def _on_state_change(self, event: Event) -> None:
"""
处理 agent 状态变更事件。
Args:
event: 包含状态和详情的状态变更事件
"""
state = event.data.get("state", "unknown")
details = event.data.get("details", "")
# Update reactive state (triggers watch_agent_state)
self.call_from_thread(setattr, self, "agent_state", state)
# Log state change
if self._activity_feed and details:
self.call_from_thread(
self._activity_feed.add_message,
f"Agent: {details}",
"info",
)
4.2 Agent -> UI: 消息事件
python
# excalibur/core/controller.py:766-807
def _process_message(
self,
msg: AgentMessage,
output_parts: list[str],
flags_found: list[str],
) -> None:
"""
处理单个代理消息。
Args:
msg: 要处理的 AgentMessage 对象
"""
if msg.type == MessageType.TEXT:
output_parts.append(msg.content)
self.events.emit_message(msg.content) # 广播消息事件
python
# excalibur/interface/tui.py:292-315
def _on_agent_message(self, event: Event) -> None:
"""
处理 agent 消息事件。
Args:
event: 包含文本内容和消息类型的事件对象
"""
if not self._activity_feed:
return
text = event.data.get("text", "")
msg_type = event.data.get("type", "info")
if text:
self.call_from_thread(
self._activity_feed.add_message,
text,
msg_type,
)
4.3 Agent -> UI: 工具事件
python
# excalibur/core/controller.py:792-806
def _process_message(
self,
msg: AgentMessage,
output_parts: list[str],
flags_found: list[str],
) -> None:
"""
处理单个代理消息。
Args:
msg: 要处理的 AgentMessage 对象
"""
elif msg.type == MessageType.TOOL_START:
# ========== 工具开始事件 ==========
self.events.emit_tool(
status="start",
name=msg.tool_name or "unknown",
args=msg.tool_args,
)
elif msg.type == MessageType.TOOL_RESULT:
# ========== 工具完成事件 ==========
self.events.emit_tool(
status="complete",
name=msg.tool_name or "unknown",
result=msg.content,
)
python
# excalibur/interface/tui.py:341-380
def _on_tool(self, event: Event) -> None:
"""
处理工具调用事件。
Args:
event: 包含工具名称、状态、参数和结果的事件对象
"""
if not self._activity_feed:
return
status = event.data.get("status", "")
name = event.data.get("name", "")
args = event.data.get("args", {})
result = event.data.get("result")
if status == "start":
self.call_from_thread(
self._activity_feed.add_tool_execution,
name, args, "running", None,
)
elif status == "complete":
self.call_from_thread(
self._activity_feed.add_tool_execution,
name, args, "completed", result,
)
4.4 Agent -> UI: Flag 发现事件
python
# excalibur/core/controller.py:784-791
def _process_message(
self,
msg: AgentMessage,
output_parts: list[str],
flags_found: list[str],
) -> None:
"""
处理单个代理消息。
Args:
msg: 要处理的 AgentMessage 对象
"""
if msg.type == MessageType.TEXT:
output_parts.append(msg.content)
self.events.emit_message(msg.content)
# ========== 检测 flag ==========
detected = self._detect_flags(msg.content)
for flag in detected:
if flag not in flags_found:
flags_found.append(flag)
self.sessions.add_flag(flag, msg.content[:200])
self.events.emit_flag(flag, msg.content[:200]) # 广播 flag 事件
python
# excalibur/interface/tui.py:316-340
def _on_flag(self, event: Event) -> None:
"""
处理 flag 发现事件。
Args:
event: 包含 flag 字符串的事件对象
"""
if not self._activity_feed:
return
flag = event.data.get("flag", "")
if flag:
self.call_from_thread(
self._activity_feed.add_message,
f"🚩 FLAG FOUND: {flag}",
"success",
)
4.5 UI -> Agent: 用户命令事件
python
# excalibur/interface/tui.py:381-410
def watch_agent_state(self, state: str) -> None:
"""
响应 agent 状态变化 - 显示/隐藏输入字段。
Args:
state: 当前 agent 状态(idle/running/paused/completed/error)
"""
try:
user_input = self.query_one("#user_input", Input)
# Show input when paused, hide otherwise
user_input.display = state == "paused"
if state == "paused":
user_input.focus()
except Exception:
pass
python
# excalibur/core/controller.py:234-260
def _on_user_command(self, event: Event) -> None:
"""
处理用户命令事件(暂停/恢复/停止)。
Args:
event: 包含命令类型的事件对象
"""
cmd = event.data.get("command")
if cmd == "pause":
self.pause()
elif cmd == "resume":
self.resume()
elif cmd == "stop":
self.stop()
python
# excalibur/core/controller.py:249-260
def _on_user_input(self, event: Event) -> None:
"""
处理用户输入事件(自定义指令)。
Args:
event: 包含文本内容的事件对象
"""
text = event.data.get("text", "")
if text:
self.inject(text) # 注入指令到下一个暂停点
五 事件数据流详解
5.1 Event 数据结构
python
# excalibur/core/events.py:57-70@dataclass
class Event:
"""
事件数据类,封装事件的类型、数据和时间戳。
Attributes:
type: 事件类型,来自 EventType 枚举
data: 事件相关的数据字典,默认为空字典
timestamp: 事件发生的时间戳,默认使用当前时间
"""
5.2 各事件类型的数据结构
| 事件类型 | 数据结构 |
|---|---|
STATE_CHANGED |
{"state": str, "details": str, "target": str?, "task": str?} |
MESSAGE |
{"text": str, "type": str} |
TOOL |
{"status": str, "name": str, "args": dict, "result": any} |
FLAG_FOUND |
{"flag": str, "context": str} |
USER_COMMAND |
{"command": str} (pause/resume/stop) |
USER_INPUT |
{"text": str} |
TREE_NODE_SELECTED |
{"node_id": str, "description": str} |
TDI_COMPUTED |
{"node_id": str, "tdi_value": float} |
MODE_SELECTED |
{"mode": str, "tdi_value": float} |
TREE_NODE_PRUNED |
{"pruned_ids": list[str]} |
TREE_PIVOT_SPAWNED |
{"host": str} |
六 线程安全机制
6.1 TUI 与 Agent 的线程模型
Agent Thread
Main Thread
thread-safe updates
Textual Event Loop
TUI Render Loop
call_from_thread
asyncio.run
_egats_loop
emit events
6.2 call_from_thread() 机制
python
# excalibur/interface/tui.py:111-550
class ExcaliburApp(App[None]):
"""
Excalibur CTF 求解器的主要 TUI 应用程序。
架构说明:
- 使用 reactive 属性实现响应式 UI 更新
- 在后台线程中运行 agent,通过 call_from_thread 安全更新 UI
"""
python
# excalibur/interface/tui.py:259-264
def _on_tree_event(self, event: Event) -> None:
"""
处理 EGATS 树事件以更新活动流。
实现细节:
使用 call_from_thread() 确保从后台线程安全地更新 UI
"""
if not self._activity_feed:
return
etype = event.type
data = event.data
msg = ""
# ... build message ...
self.call_from_thread(
self._activity_feed.add_message,
msg,
"info",
)
6.3 EventBus 线程安全实现
python
# excalibur/core/events.py:90-171
class EventBus:
_instance: Optional["EventBus"] = None # 单例实例
_lock = threading.Lock() # 线程锁
def __init__(self) -> None:
self._handlers: dict[EventType, list[Callable[[Event], None]]] = {}
self._handler_lock = threading.Lock() # 处理器列表的独立锁
def subscribe(self, event_type: EventType, handler: Callable[[Event], None]) -> None:
"""
订阅事件类型。
使用_handler_lock 保护_handlers_字典的并发访问。
"""
with self._handler_lock:
if event_type not in self._handlers:
self._handlers[event_type] = []
if handler not in self._handlers[event_type]:
self._handlers[event_type].append(handler)
def emit(self, event: Event) -> None:
"""
向所有订阅者广播事件。
复制处理器列表以避免并发修改问题,使用 contextlib.suppress
防止单个处理器的异常影响其他处理器的执行。
"""
with self._handler_lock:
handlers = self._handlers.get(event.type, []).copy()
for handler in handlers:
# ========== 防止单个处理器异常影响其他处理器 ==========
with contextlib.suppress(Exception):
handler(event)
七 事件驱动架构设计模式总结
7.1 使用的设计模式
| 模式 | 应用位置 | 说明 |
|---|---|---|
| 单例模式 | EventBus |
全局唯一的事件总线实例 |
| 观察者模式 | AgentController, ExcaliburApp |
TUI 和 Controller 都订阅 EventBus |
| 发布/订阅模式 | EventBus.emit() |
事件总线作为消息中介 |
| 响应式设计 | ExcaliburApp.agent_state |
reactive 属性触发 watch_ 方法 |
7.2 架构优势
python
# 事件驱动架构的优势:
# 1. 解耦 (Decoupling)
# - TUI 和 AgentController 通过 EventBus 通信,不直接依赖对方
# - 可以独立修改 UI 或核心引擎逻辑
# 2. 可扩展性 (Extensibility)
# - 新增事件类型只需添加 EventType 枚举值
# - 新增处理器只需订阅对应的事件类型
# 3. 线程安全 (Thread Safety)
# - EventBus 使用锁机制保证并发访问安全
# - call_from_thread() 确保 UI 更新在主线程执行
# 4. 事件溯源 (Event Sourcing)
# - 所有状态变更都通过事件广播
# - 便于调试和日志记录
八 完整示例:一次完整的用户交互流程
8.1 场景描述
用户在 TUI 中按下 Ctrl+P 暂停 agent,然后输入指令,agent 恢复执行。
8.2 详细流程
AgentController EventBus ExcaliburApp (TUI) User AgentController EventBus ExcaliburApp (TUI) User 暂停阶段 用户输入阶段 恢复阶段 Press Ctrl+P emit(USER_COMMAND: pause) _on_user_command(event) set_pause_requested=True check_pause_stop() emit(STATE_CHANGED: paused) _on_state_change(event) agent_state = "paused" watch_agent_state("paused") show user_input Type instruction and press Enter emit(USER_INPUT: text) _on_user_input(event) inject(text) set_pause_requested=False resume_event.set() check_pause_stop() returns False emit(STATE_CHANGED: running) _on_state_change(event) agent_state = "running" watch_agent_state("running") hide user_input
8.3 关键代码路径
python
# 1. User presses Ctrl+P -> TUI sends pause command
# excalibur/interface/tui.py:145-146
Binding("ctrl+p", "toggle_pause", "Pause/Resume", priority=True)
def toggle_pause(self) -> None:
self._events.emit_command("pause") # UI -> Agent
# 2. Agent receives pause command
# excalibur/core/controller.py:234-248
def _on_user_command(self, event: Event) -> None:
cmd = event.data.get("command")
if cmd == "pause":
self.pause()
# 3. Agent sets pause flag and waits
# excalibur/core/controller.py:175-186
def pause(self) -> bool:
if self._state == AgentState.RUNNING:
self._pause_requested = True
return True
return False
# 4. Agent checks pause in loop and emits state change
# excalibur/core/controller.py:274-294
async def _check_pause_stop(self) -> bool:
if self._pause_requested:
self._pause_requested = False
self._set_state(AgentState.PAUSED, "Paused - waiting for input")
await self._resume_event.wait()
# ...
return False
# 5. TUI receives state change and shows input field
# excalibur/interface/tui.py:265-291
def _on_state_change(self, event: Event) -> None:
state = event.data.get("state", "unknown")
self.call_from_thread(setattr, self, "agent_state", state)
九 调试技巧
9.1 启用详细事件日志
python
# excalibur/interface/main.py:307-464
def run_raw_mode(args: argparse.Namespace) -> None:
"""
在原始 CLI 模式下运行,用于调试的流式输出。
此模式直接打印所有事件到 stdout,不经过 Rich 格式化,适合:
- 调试 agent 内部工作流程
- 捕获完整的事件日志
- 分析 EGATS 规划器行为
"""
9.2 添加自定义事件处理器
python
from excalibur.core.events import EventBus, EventType
def debug_handler(event: Event) -> None:
"""调试用事件处理器。"""
print(f"[DEBUG] {event.type.name}: {event.data}")
# 订阅所有事件类型
events = EventBus.get()
events.subscribe(EventType.STATE_CHANGED, debug_handler)
events.subscribe(EventType.MESSAGE, debug_handler)
events.subscribe(EventType.TOOL, debug_handler)
附录:相关文件索引
| 文件 | 路径 | 功能 |
|---|---|---|
| EventBus | excalibur/core/events.py |
事件总线(单例) |
| EventType | excalibur/core/events.py:19 |
事件类型枚举 |
| Event | excalibur/core/events.py:57 |
事件数据类 |
| AgentController | excalibur/core/controller.py |
核心引擎控制器 |
| ExcaliburApp | excalibur/interface/tui.py |
TUI 应用程序主类 |
| ActivityFeed | excalibur/interface/components/activity_feed.py |
活动流组件 |