fix(agent): Phase 9 实施审查修复

- cycle.rs: run_tool_loop 实现 PreRequest hook(之前 `let _ = hook_executor.as_ref()` 是空操作,
  导致 hook-based logging/monitoring 在流式工具循环中失效;现在与 submit_with_tools 行为对齐,
  含 should_block 检查,阻断时通过 StreamEvent::Error 事件化)
- session.rs: 删除 submit_turn_stream 末尾的 `let _ = hook_executor;` 死代码(Arc 引用生命周期
  由 Arc 自动管理)
- session.rs: 新增 2 个集成测试覆盖方案 §4 Step 5:
  - submit_turn_stream_end_to_end:mock provider → 消费流 → finalize_turn 后 cost_so_far
    正确更新(10/5 tokens)+ turn_index=1 + default slot 包含 user/assistant 消息
  - submit_turn_stream_triggers_turn_hooks:OnTurnStart 在 submit_turn_stream 返回流前
    触发(计数=1)+ OnTurnEnd 在 finalize_turn 前不触发(计数=0)+ finalize_turn 后触发(计数=1)
- docs/16-phase9-streaming-experience.md: 标注 finalize_turn Phase 10 签名变更(new_messages_from_cycle
  + Result 返回),Step 5 测试实现位置
- 测试 288 passed / 0 failed(基线 286 + 2 新增),clippy 0 警告,doc 0 warning
This commit is contained in:
徐涛
2026-07-08 23:19:24 +08:00
parent d4f27b5865
commit c36668071e
3 changed files with 130 additions and 9 deletions
+16 -3
View File
@@ -18,7 +18,7 @@ use tokio_stream::wrappers::UnboundedReceiverStream;
use crate::llm::compact::{CompactConfig, CompactState, microcompact, should_compact};
use crate::llm::cycle::retry::should_retry;
use crate::llm::error::LlmError;
use crate::llm::hooks::{HookContext, HookExecutor};
use crate::llm::hooks::{HookContext, HookEvent, HookExecutor};
use crate::llm::provider::LlmProvider;
use crate::llm::stream::StreamEvent;
use crate::llm::types::message::{ContentBlock, Message};
@@ -774,8 +774,21 @@ async fn run_tool_loop(
..Default::default()
};
// ② PreRequest hookfire-and-forget,仅占位保留以保持接口对称)
let _ = hook_executor.as_ref();
// ② PreRequest hook —— 与 `submit_with_tools` / `submit_stream` 行为对齐:
// 触发 hook → 检查 should_block → 阻断则事件化 Error + return 结束 task。
// 阻断原因透传,让消费者看到完整的拒绝原因。
if let Some(ref executor) = hook_executor {
let ctx = HookContext::new(HookEvent::PreRequest).with_request(&request);
let results = executor.execute(HookEvent::PreRequest, &ctx).await;
if let Some(blocking) = results.iter().find(|r| r.should_block) {
let reason = blocking
.reason
.clone()
.unwrap_or_else(|| "Blocked by pre-request hook".to_string());
let _ = tx.send(StreamEvent::Error { message: reason });
return;
}
}
// ③ chat_stream —— 第一层错误
let mut stream = match provider.chat_stream(request).await {