diff --git a/src/agent.rs b/src/agent.rs index 9ed48f7..9431d81 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -1152,7 +1152,7 @@ pub fn spawn(job: Job, bot_id: u64, tx: Sender<(u64, StreamEvent)>, cancel: Arc< // any picture, shown and kept. let finish = |history: &mut Vec, output: &str, status: ToolStatus, image: Option| { // A click that changed nothing on the screen is trouble too, though it "worked". - if matches!(status, ToolStatus::Failed | ToolStatus::Denied) || !repeat_note.is_empty() || output.contains("畫面和上一步完全一樣") { + if matches!(status, ToolStatus::Failed | ToolStatus::Denied) || !repeat_note.is_empty() || output.contains(crate::computer::NOOP_TAG) { trouble.set(true); } job.note(crate::oplog::Record::ToolFinished { op: job.op.clone(), call_id: call.id.clone(), status: format!("{status:?}").to_lowercase() }); diff --git a/src/cloudbox.rs b/src/cloudbox.rs index 5820617..d490457 100644 --- a/src/cloudbox.rs +++ b/src/cloudbox.rs @@ -2085,7 +2085,7 @@ fn box_look(script: Option<&str>, after: Option, pointer: bool) -> Resul same }; if unchanged { - text.push_str("\n⚠️ 畫面和上一步完全一樣:這一步沒有點到東西。不要再做同一步;換一個編號、換一個方法(網頁用 browser_* 工具,或直接開網址),或用 hand_over_computer 請使用者幫忙。"); + text.push_str(crate::computer::NOOP_NOTE); } if lines.is_empty() { text.push_str("\n畫面上沒有可以照編號點的東西:只好照圖上的位置點 x、y。"); diff --git a/src/computer.rs b/src/computer.rs index de3435d..c511067 100644 --- a/src/computer.rs +++ b/src/computer.rs @@ -220,6 +220,12 @@ pub fn is_pointer_step(name: &str) -> bool { matches!(name, "computer_use_click" | "computer_use_scroll" | "computer_use_drag") } +/// The screen-did-not-change note. The sentence is for the model; the tag at the end is for +/// the harness (agent.rs): control flow matches the TAG, never the words (a wording change +/// used to silently turn off the whole "same click did nothing" guard). +pub const NOOP_TAG: &str = "[[noop]]"; +pub const NOOP_NOTE: &str = "\n⚠️ 畫面和上一步完全一樣:這一步沒有點到東西。不要再做同一步;換一個編號、換一個方法(網頁用 browser_* 工具,或直接開網址),或用 hand_over_computer 請使用者幫忙。[[noop]]"; + /// What the bot is told after computer_use_show (the picture itself goes into the chat). pub const SHOWN_NOTE: &str = "已把這台電腦現在的畫面(使用者看到的樣子,沒有編號)放進聊天,使用者看得到這張圖;你也看得到,可以用一兩句話說明畫面上有什麼"; @@ -1147,7 +1153,7 @@ fn look(after: Option, pointer: bool) -> Result<(String, Option) same }; if unchanged { - text.push_str("\n⚠️ 畫面和上一步完全一樣:這一步沒有點到東西。不要再做同一步;換一個編號、換一個方法(網頁用 browser_* 工具,或直接開網址),或用 hand_over_computer 請使用者幫忙。"); + text.push_str(crate::computer::NOOP_NOTE); } if hidden > 0 { text.push_str(&format!("\n(「{app}」有 {hidden} 個按鈕被別的視窗擋住或不在這個桌面,沒有編號:先 computer_use_open_app 把它叫到前面。)")); @@ -1772,6 +1778,12 @@ mod mac { mod tests { use super::*; + #[test] + fn the_noop_note_carries_its_tag() { + // The harness matches NOOP_TAG; the sentence may be reworded, the tag may not drift. + assert!(NOOP_NOTE.contains(NOOP_TAG)); + } + /// Keys wait when the app in front isn't the one of the latest look (no real key is pressed /// here: the check refuses before anything is sent, or there is no front app at all). #[test]