修复 Claude Direct MCP 回合授权死循环
Project CI / AI game creator shell Rust crates (push) Successful in 2m47s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 4m49s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 6m16s
Project CI / Backend tests (push) Failing after 4m53s
Project CI / Frontend tests (push) Successful in 3m36s
Project CI / Repository checks (push) Failing after 3m57s
Project CI / AI game creator shell web tests (push) Successful in 2m33s
Project CI / Native shell tests (push) Successful in 6m55s
Project CI / AI game creator shell Rust crates (push) Successful in 2m47s
Project CI / AI game creator shell Rust lane 2/2 (push) Successful in 4m49s
Project CI / AI game creator shell Rust lane 1/2 (push) Successful in 6m16s
Project CI / Backend tests (push) Failing after 4m53s
Project CI / Frontend tests (push) Successful in 3m36s
Project CI / Repository checks (push) Failing after 3m57s
Project CI / AI game creator shell web tests (push) Successful in 2m33s
Project CI / Native shell tests (push) Successful in 6m55s
内部 Claude MCP 使用 Direct 工具桥并绑定当前回合授权 保留外部 MCP 隔离模式并补充回归测试与排障记录
This commit is contained in:
@@ -960,7 +960,8 @@ pub(crate) async fn direct_game_creator_claude_code_chat_at(
|
||||
observer: Option<&mut (dyn FnMut(TurnObservation) + Send)>,
|
||||
) -> Result<String, String> {
|
||||
direct_turn_trace("claude-executor-enter");
|
||||
let (mcp_url, mcp_token) = start_external_mcp_loopback(root, llm.web_search_enabled).await?;
|
||||
let (mcp_url, mcp_token) =
|
||||
start_external_mcp_loopback_for_direct_turn(root, llm.web_search_enabled).await?;
|
||||
direct_turn_trace("claude-mcp-loopback-ready");
|
||||
let _mcp_guard = ExternalMcpStopGuard;
|
||||
let home = crate::project::resolve_local_project_path(root, ".agent/runtime/claude-code/home")?;
|
||||
|
||||
@@ -4236,8 +4236,9 @@ pub(crate) async fn start_direct_tool_bridge(
|
||||
pub(crate) async fn start_external_client_tool_bridge(
|
||||
root: &Path,
|
||||
controlled_web_search: bool,
|
||||
direct_turn_execution: bool,
|
||||
) -> Result<DirectToolBridge, String> {
|
||||
start_tool_bridge_for_source(root, controlled_web_search, false).await
|
||||
start_tool_bridge_for_source(root, controlled_web_search, direct_turn_execution).await
|
||||
}
|
||||
|
||||
async fn start_tool_bridge_for_source(
|
||||
@@ -4518,6 +4519,18 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn external_client_bridge_can_be_enabled_for_a_direct_turn() {
|
||||
let temporary = tempfile::tempdir().unwrap();
|
||||
init_local_game_project_at(temporary.path(), "external-direct-turn", "外部直连回合")
|
||||
.unwrap();
|
||||
let bridge = start_external_client_tool_bridge(temporary.path(), false, true)
|
||||
.await
|
||||
.expect("direct Claude bridge");
|
||||
assert!(bridge.state.direct_turn_execution);
|
||||
drop(bridge);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn host_cancellation_releases_queued_image_without_a_new_post() {
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
|
||||
@@ -41,6 +41,7 @@ tokio::task_local! {
|
||||
}
|
||||
|
||||
pub(crate) struct ExternalMcpServer {
|
||||
_direct_turn_guard: Option<super::direct_tool_bridge::DirectToolBridgeTurnGuard>,
|
||||
_bridge: super::direct_tool_bridge::DirectToolBridge,
|
||||
task: tokio::task::JoinHandle<()>,
|
||||
}
|
||||
@@ -1759,6 +1760,21 @@ async fn handle_external_mcp_http_request(
|
||||
pub(crate) async fn start_external_mcp_loopback(
|
||||
root: &Path,
|
||||
controlled_web_search: bool,
|
||||
) -> Result<(String, String), String> {
|
||||
start_external_mcp_loopback_with_mode(root, controlled_web_search, false).await
|
||||
}
|
||||
|
||||
pub(crate) async fn start_external_mcp_loopback_for_direct_turn(
|
||||
root: &Path,
|
||||
controlled_web_search: bool,
|
||||
) -> Result<(String, String), String> {
|
||||
start_external_mcp_loopback_with_mode(root, controlled_web_search, true).await
|
||||
}
|
||||
|
||||
async fn start_external_mcp_loopback_with_mode(
|
||||
root: &Path,
|
||||
controlled_web_search: bool,
|
||||
direct_turn_execution: bool,
|
||||
) -> Result<(String, String), String> {
|
||||
let root = validate_direct_tools_project_root(root)?;
|
||||
let session = current_platform_session()
|
||||
@@ -1771,9 +1787,17 @@ pub(crate) async fn start_external_mcp_loopback(
|
||||
let address = listener
|
||||
.local_addr()
|
||||
.map_err(|error| format!("读取客户端 MCP 地址失败:{error}"))?;
|
||||
let bridge =
|
||||
super::direct_tool_bridge::start_external_client_tool_bridge(&root, controlled_web_search)
|
||||
.await?;
|
||||
let bridge = super::direct_tool_bridge::start_external_client_tool_bridge(
|
||||
&root,
|
||||
controlled_web_search,
|
||||
direct_turn_execution,
|
||||
)
|
||||
.await?;
|
||||
let direct_turn_guard = if direct_turn_execution {
|
||||
Some(bridge.begin_user_turn()?)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let state = ExternalMcpHttpState {
|
||||
bridge_url: bridge.url().to_string(),
|
||||
root,
|
||||
@@ -1798,6 +1822,7 @@ pub(crate) async fn start_external_mcp_loopback(
|
||||
drop(previous);
|
||||
}
|
||||
*guard = Some(ExternalMcpServer {
|
||||
_direct_turn_guard: direct_turn_guard,
|
||||
_bridge: bridge,
|
||||
task,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user