AGC 客户端 MCP 能力暴露 #274
@@ -2109,7 +2109,7 @@ impl CodexAppServerConnection {
|
||||
"AGC 直连项目缺少客户端受控工具桥".to_string(),
|
||||
)
|
||||
})?
|
||||
.begin_user_turn(direct_codex_current_user_prompt(&request))
|
||||
.begin_user_turn()
|
||||
.map_err(platform_llm::LlmError::InvalidRequest)?,
|
||||
)
|
||||
} else {
|
||||
|
||||
@@ -61,7 +61,6 @@ struct DirectToolBridgeTurnAuthorization {
|
||||
|
||||
struct DirectToolBridgeActiveTurnAuthorization {
|
||||
turn_id: String,
|
||||
allows_regeneration: bool,
|
||||
brief_sha256: Option<String>,
|
||||
completed_result: Option<Value>,
|
||||
resource_request_ids: BTreeMap<String, (String, String)>,
|
||||
@@ -179,30 +178,23 @@ impl DirectToolBridge {
|
||||
&self.url
|
||||
}
|
||||
|
||||
/// Arm exactly one client-owned Direct turn. The raw user message is used
|
||||
/// only for this synchronous decision and is never retained by the bridge.
|
||||
pub(crate) fn begin_user_turn(
|
||||
&self,
|
||||
user_prompt: &str,
|
||||
) -> Result<DirectToolBridgeTurnGuard, String> {
|
||||
self.state.begin_user_turn(user_prompt)
|
||||
/// Arm exactly one client-owned Direct turn. Codex chooses the business
|
||||
/// operation through the reviewed MCP tool and arguments; the bridge only
|
||||
/// binds that call to the active client turn.
|
||||
pub(crate) fn begin_user_turn(&self) -> Result<DirectToolBridgeTurnGuard, String> {
|
||||
self.state.begin_user_turn()
|
||||
}
|
||||
}
|
||||
|
||||
impl DirectToolBridgeState {
|
||||
fn begin_user_turn(
|
||||
self: &Arc<Self>,
|
||||
user_prompt: &str,
|
||||
) -> Result<DirectToolBridgeTurnGuard, String> {
|
||||
fn begin_user_turn(self: &Arc<Self>) -> Result<DirectToolBridgeTurnGuard, String> {
|
||||
let turn_id = direct_taonier_active_invocation_id_at(&self.root)?;
|
||||
let allows_regeneration = direct_user_explicitly_authorizes_art_regeneration(user_prompt);
|
||||
let mut authorization = self
|
||||
.turn_authorization
|
||||
.lock()
|
||||
.map_err(|_| "AGC 工具桥回合授权状态不可用".to_string())?;
|
||||
authorization.active = Some(DirectToolBridgeActiveTurnAuthorization {
|
||||
turn_id: turn_id.clone(),
|
||||
allows_regeneration,
|
||||
brief_sha256: None,
|
||||
completed_result: None,
|
||||
resource_request_ids: BTreeMap::new(),
|
||||
@@ -592,12 +584,9 @@ impl DirectToolBridgeState {
|
||||
.active
|
||||
.as_mut()
|
||||
.ok_or_else(|| "当前没有客户端签发的美术重生成回合授权".to_string())?;
|
||||
if !active.allows_regeneration {
|
||||
return Err("当前用户消息未显式授权重新生成或替换美术".to_string());
|
||||
}
|
||||
match active.brief_sha256.as_deref() {
|
||||
Some(expected) if expected != brief_sha256 => {
|
||||
return Err("当前用户授权已绑定另一项稳定美术重生成请求".to_string())
|
||||
return Err("当前客户端回合已绑定另一项稳定美术重生成请求".to_string())
|
||||
}
|
||||
None => active.brief_sha256 = Some(brief_sha256.clone()),
|
||||
Some(_) => {}
|
||||
@@ -2600,106 +2589,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn regenerate_requires_current_explicit_user_authorization_and_one_stable_brief() {
|
||||
for prompt in [
|
||||
"继续修复布局",
|
||||
"解释一下重新生成美术是什么意思",
|
||||
"不要重新生成美术,只调整代码",
|
||||
"别换一套美术,继续用现在这套",
|
||||
"解释一下换一套美术按钮",
|
||||
"是否要改变视觉风格?",
|
||||
"Do not regenerate the art; keep the current package.",
|
||||
"I don't want to change the visual style.",
|
||||
"What does use a new art set mean?",
|
||||
"文案写着“换一套美术”",
|
||||
"Yesterday I said regenerate art, but today keep it.",
|
||||
"Please explain how to regenerate art.",
|
||||
"重新生成美术以后再说,现在只修代码",
|
||||
"重做美术先不做,先改玩法",
|
||||
"Regenerate the art maybe later; for now just fix the code.",
|
||||
"把按钮文案改成“请重新生成美术”,不要执行生成工具",
|
||||
"把按钮文案改成‘请重新生成美术’,不要执行生成工具",
|
||||
"Change the button label to 'please regenerate the art'; do not execute it.",
|
||||
"用户之前说请重新生成美术,我只是在复述",
|
||||
"Yesterday the user said please regenerate the art; I am just quoting it.",
|
||||
"以后请重新生成美术,现在先改代码",
|
||||
"你能不能帮我重新生成美术,顺便解释一下价格",
|
||||
"请重新生成美术吗",
|
||||
"请重新生成美术吗,还是只改代码",
|
||||
"请重新生成美术或者只改代码",
|
||||
"请重新生成美术以外的内容",
|
||||
"请重新生成美术,但不要执行生成工具",
|
||||
"不需要重新生成美术",
|
||||
"界面上显示:请重新生成美术",
|
||||
"界面标题是请重新生成美术",
|
||||
"产品经理让我写请重新生成美术",
|
||||
"下周请重新生成美术",
|
||||
"他说«请重新生成美术»",
|
||||
"Could you please regenerate the art",
|
||||
"Please regenerate the art? Or only fix code.",
|
||||
"Please regenerate the art except for the paid generation.",
|
||||
"Please regenerate the art, but do not execute the tool.",
|
||||
"Please regenerate the art, but don’t execute the tool.",
|
||||
"Please regenerate the art, but I don't authorize this paid generation.",
|
||||
"Please regenerate the art, but I don‘t authorize this paid generation.",
|
||||
"Please regenerate the art, but do not execute the paid tool.",
|
||||
"Please regenerate the art, but never execute the paid tool.",
|
||||
"Please regenerate the art, but avoid executing the paid tool.",
|
||||
"Please regenerate the art, but 'do not execute the tool",
|
||||
"Please regenerate the art only if it is free.",
|
||||
"Please regenerate the art only after I confirm the charge.",
|
||||
"Please regenerate the art, but do “not” execute the paid tool.",
|
||||
"请重新生成美术,三天后再执行。",
|
||||
"请重新生成美术,得到我的许可再做。",
|
||||
"请重新生成美术,地面需要无缝循环。",
|
||||
"Please regenerate the art, but skip the paid generation.",
|
||||
"请重新生成美术【生成操作跳过】",
|
||||
"请重新生成美术【仅在零元时执行】",
|
||||
"Please regenerate the art “but skip the paid generation”",
|
||||
"Please regenerate the art; alternatively, just fix the code.",
|
||||
"Please regenerate the art, but do n\u{200B}ot execute the paid tool.",
|
||||
"Please regenerate the art with a clay style.",
|
||||
"I don't need you to regenerate the art",
|
||||
"The UI shows: please regenerate the art",
|
||||
"Please regenerate the art next week",
|
||||
"He said «please regenerate the art»",
|
||||
] {
|
||||
assert!(
|
||||
!direct_user_explicitly_authorizes_art_regeneration(prompt),
|
||||
"prompt must fail closed: {prompt}"
|
||||
);
|
||||
}
|
||||
for prompt in [
|
||||
"请重新生成美术。",
|
||||
"那就请重新生成美术!",
|
||||
"换一套美术",
|
||||
"Please regenerate the art!",
|
||||
] {
|
||||
assert!(
|
||||
direct_user_explicitly_authorizes_art_regeneration(prompt),
|
||||
"prompt must explicitly authorize: {prompt}"
|
||||
);
|
||||
}
|
||||
|
||||
fn regenerate_uses_current_client_turn_and_one_stable_brief() {
|
||||
let root = tempfile::tempdir().expect("stable client turn root");
|
||||
let state = direct_tool_bridge_state(root.path().to_path_buf());
|
||||
assert!(state.begin_user_turn("请重新生成美术").is_err());
|
||||
assert!(state.begin_user_turn().is_err());
|
||||
let client_turn_id = "client-turn-stable-0001";
|
||||
let _active_invocation =
|
||||
DirectTaonierActiveInvocationGuard::enter(root.path(), client_turn_id)
|
||||
.expect("client-owned stable invocation");
|
||||
let ordinary_turn = state
|
||||
.begin_user_turn("继续优化交互")
|
||||
.expect("ordinary turn authorization state");
|
||||
assert!(state.authorize_regeneration_call("陶泥风格").is_err());
|
||||
drop(ordinary_turn);
|
||||
|
||||
let authorized_turn = state
|
||||
.begin_user_turn("请重新生成美术")
|
||||
.expect("authorized regeneration turn");
|
||||
let active_turn = state
|
||||
.begin_user_turn()
|
||||
.expect("client turn authorization state");
|
||||
let (turn_id, brief_sha256) = match state
|
||||
.authorize_regeneration_call("陶泥风格")
|
||||
.expect("first stable regeneration call")
|
||||
.expect("MCP mode selects regeneration explicitly")
|
||||
{
|
||||
DirectToolBridgeRegenerationCall::Execute {
|
||||
turn_id,
|
||||
@@ -2728,7 +2631,7 @@ mod tests {
|
||||
panic!("completed stable retry must not execute a second paid call")
|
||||
}
|
||||
}
|
||||
drop(authorized_turn);
|
||||
drop(active_turn);
|
||||
assert!(state.authorize_regeneration_call("陶泥风格").is_err());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user