From 2e9e05f32a7abae2496a6e2308efc325529ebc07 Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Thu, 21 May 2026 11:14:35 +0000 Subject: [PATCH 1/8] =?UTF-8?q?ci:=20add=20PR=20watch=20workflow=20?= =?UTF-8?q?=E2=80=94=20notify=20Discord=20on=20upstream=20activity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-watch.yml | 51 ++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 .github/workflows/pr-watch.yml diff --git a/.github/workflows/pr-watch.yml b/.github/workflows/pr-watch.yml new file mode 100644 index 00000000..664a957c --- /dev/null +++ b/.github/workflows/pr-watch.yml @@ -0,0 +1,51 @@ +name: PR Watch — Upstream Activity Notify + +on: + schedule: + - cron: '0 */4 * * *' + workflow_dispatch: + +jobs: + watch: + runs-on: ubuntu-latest + steps: + - name: Check upstream activity and notify Discord + env: + GH_TOKEN: ${{ secrets.GH_PAT }} + DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} + SINCE: '2026-05-19T14:00:00Z' + run: | + NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ) + FOUND=0 + MSG="" + + COMMENTS=$(gh api "/repos/openabdev/openab/issues/829/comments" \ + --jq ".[] | select(.created_at > \"$SINCE\") | \"💬 [\(.user.login)] \(.created_at[:16]): \(.body[:120])\"" 2>/dev/null) + if [ -n "$COMMENTS" ]; then + FOUND=1 + MSG="${MSG}\n**PR #829 — New Comments:**\n${COMMENTS}" + fi + + REVIEWS=$(gh api "/repos/openabdev/openab/pulls/829/reviews" \ + --jq ".[] | select(.submitted_at > \"$SINCE\") | \"🔍 [\(.user.login)] [\(.state)] \(.submitted_at[:16]): \(.body[:120])\"" 2>/dev/null) + if [ -n "$REVIEWS" ]; then + FOUND=1 + MSG="${MSG}\n**PR #829 — New Reviews:**\n${REVIEWS}" + fi + + PR_STATE=$(gh api /repos/openabdev/openab/pulls/829 \ + --jq '"state=\(.state) mergeable=\(.mergeable_state) updated=\(.updated_at[:16])"' 2>/dev/null) + MSG="${MSG}\n**PR #829 Status:** ${PR_STATE}" + + if [ "$FOUND" -eq 1 ]; then + HEADER="🔔 **GitHub Watch Report** | $(date -u '+%Y-%m-%d %H:%M UTC')" + FULL_MSG="${HEADER}\n${MSG}" + else + FULL_MSG="✅ **GitHub Watch** | $(date -u '+%Y-%m-%d %H:%M UTC') — No new activity on PR #829 since ${SINCE}." + fi + + printf '%b' "$FULL_MSG" > /tmp/msg.txt + PAYLOAD=$(jq -n --rawfile msg /tmp/msg.txt '{content: $msg}') + curl -sf -X POST "$DISCORD_WEBHOOK_URL" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD" From 7f54e16ce7d3cc26ea1b5fece6dc7ad073848306 Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Thu, 21 May 2026 14:27:59 +0000 Subject: [PATCH 2/8] fix(discord): dedup WarnAndStop warning across multiple bot processes When N bots share a thread and all hit the soft turn limit simultaneously, each bot's per-process BotTurnTracker independently fires WarnAndStop, resulting in N duplicate warnings posted to the thread. Fix: before posting the warning, fetch the last 10 messages in the thread and skip if any bot message already contains 'Bot turn limit reached'. Fail-open: if the API call fails, the warning is still posted. Closes #530 --- src/discord.rs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/discord.rs b/src/discord.rs index 837e8048..2747810f 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -397,7 +397,25 @@ impl EventHandler for Handler { .bot_participated_in_thread(&ctx.http, msg.channel_id, bot_id) .await; if participated { - let _ = msg.channel_id.say(&ctx.http, &user_message).await; + // Dedup: skip if another bot already posted the same + // warning in this thread. Prevents N duplicate warnings + // when N bot processes each hit the soft limit. (#530) + let already_warned = msg + .channel_id + .messages( + &ctx.http, + serenity::builder::GetMessages::new().limit(10), + ) + .await + .unwrap_or_default() + .iter() + .any(|m| { + m.author.bot + && m.content.contains("Bot turn limit reached") + }); + if !already_warned { + let _ = msg.channel_id.say(&ctx.http, &user_message).await; + } } } return; From 4774668f46f729e58fc6f213014f72034f0b2e3e Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Thu, 21 May 2026 15:14:13 +0000 Subject: [PATCH 3/8] test(discord): add unit tests for turn_limit_warning_present dedup helper (#530) --- src/discord.rs | 85 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/src/discord.rs b/src/discord.rs index 2747810f..0fba4ebc 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -400,19 +400,15 @@ impl EventHandler for Handler { // Dedup: skip if another bot already posted the same // warning in this thread. Prevents N duplicate warnings // when N bot processes each hit the soft limit. (#530) - let already_warned = msg - .channel_id - .messages( - &ctx.http, - serenity::builder::GetMessages::new().limit(10), - ) - .await - .unwrap_or_default() - .iter() - .any(|m| { - m.author.bot - && m.content.contains("Bot turn limit reached") - }); + let already_warned = turn_limit_warning_present( + &msg.channel_id + .messages( + &ctx.http, + serenity::builder::GetMessages::new().limit(10), + ) + .await + .unwrap_or_default(), + ); if !already_warned { let _ = msg.channel_id.say(&ctx.http, &user_message).await; } @@ -2169,6 +2165,14 @@ fn should_process_user_message( } } +/// Returns true if any bot message in `messages` contains a turn limit warning. +/// Used to dedup `WarnAndStop` across multiple bot processes sharing a thread. (#530) +fn turn_limit_warning_present(messages: &[Message]) -> bool { + messages + .iter() + .any(|m| m.author.bot && m.content.contains("Bot turn limit reached")) +} + #[cfg(test)] mod tests { use super::*; @@ -2957,4 +2961,59 @@ mod tests { fn normal_channel_creates_thread() { assert!(!should_skip_thread_creation(false, false)); } + + // --- WarnAndStop dedup tests (#530) --- + + fn make_message(is_bot: bool, content: &str) -> Message { + serde_json::from_value(serde_json::json!({ + "id": "1", + "channel_id": "1", + "author": { + "id": "1", + "username": "bot", + "discriminator": "0000", + "bot": is_bot, + "avatar": null + }, + "content": content, + "timestamp": "2026-01-01T00:00:00+00:00", + "edited_timestamp": null, + "tts": false, + "mention_everyone": false, + "mentions": [], + "mention_roles": [], + "attachments": [], + "embeds": [], + "pinned": false, + "type": 0 + })) + .expect("valid Message JSON") + } + + #[test] + fn dedup_detects_existing_bot_warning() { + let msgs = vec![make_message(true, "⚠️ Bot turn limit reached (20/20). A human must reply.")]; + assert!(turn_limit_warning_present(&msgs)); + } + + #[test] + fn dedup_ignores_human_warning_text() { + // Same text from a human should not count as a warning + let msgs = vec![make_message(false, "⚠️ Bot turn limit reached (20/20). A human must reply.")]; + assert!(!turn_limit_warning_present(&msgs)); + } + + #[test] + fn dedup_returns_false_when_no_warning() { + let msgs = vec![ + make_message(true, "hello"), + make_message(false, "world"), + ]; + assert!(!turn_limit_warning_present(&msgs)); + } + + #[test] + fn dedup_returns_false_for_empty_messages() { + assert!(!turn_limit_warning_present(&[])); + } } From 14a2592b15d45c30d26e4d06d364db77825dab2e Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Thu, 21 May 2026 15:38:13 +0000 Subject: [PATCH 4/8] =?UTF-8?q?chore:=20remove=20pr-watch.yml=20=E2=80=94?= =?UTF-8?q?=20unrelated=20to=20#530=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/pr-watch.yml | 51 ---------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/pr-watch.yml diff --git a/.github/workflows/pr-watch.yml b/.github/workflows/pr-watch.yml deleted file mode 100644 index 664a957c..00000000 --- a/.github/workflows/pr-watch.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: PR Watch — Upstream Activity Notify - -on: - schedule: - - cron: '0 */4 * * *' - workflow_dispatch: - -jobs: - watch: - runs-on: ubuntu-latest - steps: - - name: Check upstream activity and notify Discord - env: - GH_TOKEN: ${{ secrets.GH_PAT }} - DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} - SINCE: '2026-05-19T14:00:00Z' - run: | - NOW=$(date -u +%Y-%m-%dT%H:%M:%SZ) - FOUND=0 - MSG="" - - COMMENTS=$(gh api "/repos/openabdev/openab/issues/829/comments" \ - --jq ".[] | select(.created_at > \"$SINCE\") | \"💬 [\(.user.login)] \(.created_at[:16]): \(.body[:120])\"" 2>/dev/null) - if [ -n "$COMMENTS" ]; then - FOUND=1 - MSG="${MSG}\n**PR #829 — New Comments:**\n${COMMENTS}" - fi - - REVIEWS=$(gh api "/repos/openabdev/openab/pulls/829/reviews" \ - --jq ".[] | select(.submitted_at > \"$SINCE\") | \"🔍 [\(.user.login)] [\(.state)] \(.submitted_at[:16]): \(.body[:120])\"" 2>/dev/null) - if [ -n "$REVIEWS" ]; then - FOUND=1 - MSG="${MSG}\n**PR #829 — New Reviews:**\n${REVIEWS}" - fi - - PR_STATE=$(gh api /repos/openabdev/openab/pulls/829 \ - --jq '"state=\(.state) mergeable=\(.mergeable_state) updated=\(.updated_at[:16])"' 2>/dev/null) - MSG="${MSG}\n**PR #829 Status:** ${PR_STATE}" - - if [ "$FOUND" -eq 1 ]; then - HEADER="🔔 **GitHub Watch Report** | $(date -u '+%Y-%m-%d %H:%M UTC')" - FULL_MSG="${HEADER}\n${MSG}" - else - FULL_MSG="✅ **GitHub Watch** | $(date -u '+%Y-%m-%d %H:%M UTC') — No new activity on PR #829 since ${SINCE}." - fi - - printf '%b' "$FULL_MSG" > /tmp/msg.txt - PAYLOAD=$(jq -n --rawfile msg /tmp/msg.txt '{content: $msg}') - curl -sf -X POST "$DISCORD_WEBHOOK_URL" \ - -H "Content-Type: application/json" \ - -d "$PAYLOAD" From c9db57a19fb92b97c290500fb7a288b31be4f0ed Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Thu, 21 May 2026 15:41:45 +0000 Subject: [PATCH 5/8] refactor(discord): extract BOT_TURN_LIMIT_WARNING_PREFIX constant; clarify best-effort semantics - Add BOT_TURN_LIMIT_WARNING_PREFIX const to bot_turns.rs so the dedup check and the warning text share a single source of truth - Update turn_limit_warning_present() to use the constant - Add doc comment clarifying best-effort / race-window limitation - Update tests to use the constant Addresses review feedback from internal review. --- src/bot_turns.rs | 5 +++++ src/discord.rs | 16 +++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/bot_turns.rs b/src/bot_turns.rs index c69971f8..ddad8d85 100644 --- a/src/bot_turns.rs +++ b/src/bot_turns.rs @@ -13,6 +13,11 @@ use std::collections::HashMap; /// between human resets. pub const HARD_BOT_TURN_LIMIT: u32 = 1000; +/// Stable prefix used in all bot turn limit warning messages. +/// Referenced by the dedup check in the Discord adapter — changing this +/// string requires updating the dedup check too. +pub const BOT_TURN_LIMIT_WARNING_PREFIX: &str = "⚠️ Bot turn limit reached"; + #[derive(Debug, PartialEq, Eq)] pub enum TurnResult { /// Counter below limits — continue normally. diff --git a/src/discord.rs b/src/discord.rs index 0fba4ebc..9e552155 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -1,7 +1,7 @@ use crate::acp::protocol::ConfigOption; use crate::acp::ContentBlock; use crate::adapter::{AdapterRouter, ChannelRef, ChatAdapter, MessageRef, SenderContext}; -use crate::bot_turns::{BotTurnTracker, TurnAction, TurnSeverity}; +use crate::bot_turns::{BotTurnTracker, TurnAction, TurnSeverity, BOT_TURN_LIMIT_WARNING_PREFIX}; use crate::config::{AllowBots, AllowUsers, SttConfig}; use crate::format; use crate::media; @@ -2167,16 +2167,20 @@ fn should_process_user_message( /// Returns true if any bot message in `messages` contains a turn limit warning. /// Used to dedup `WarnAndStop` across multiple bot processes sharing a thread. (#530) +/// Note: this is best-effort — a narrow race window exists where two bots fetch +/// simultaneously and both see no warning, resulting in a duplicate. For most +/// deployments this is acceptable; strict once-only semantics would require +/// shared state (e.g. gateway-owned emission or distributed lock). fn turn_limit_warning_present(messages: &[Message]) -> bool { messages .iter() - .any(|m| m.author.bot && m.content.contains("Bot turn limit reached")) + .any(|m| m.author.bot && m.content.contains(BOT_TURN_LIMIT_WARNING_PREFIX)) } #[cfg(test)] mod tests { use super::*; - use crate::bot_turns::{TurnResult, HARD_BOT_TURN_LIMIT}; + use crate::bot_turns::{TurnResult, HARD_BOT_TURN_LIMIT, BOT_TURN_LIMIT_WARNING_PREFIX}; // --- resolve_mentions tests --- @@ -2992,14 +2996,16 @@ mod tests { #[test] fn dedup_detects_existing_bot_warning() { - let msgs = vec![make_message(true, "⚠️ Bot turn limit reached (20/20). A human must reply.")]; + let msg = format!("{} (20/20). A human must reply.", BOT_TURN_LIMIT_WARNING_PREFIX); + let msgs = vec![make_message(true, &msg)]; assert!(turn_limit_warning_present(&msgs)); } #[test] fn dedup_ignores_human_warning_text() { // Same text from a human should not count as a warning - let msgs = vec![make_message(false, "⚠️ Bot turn limit reached (20/20). A human must reply.")]; + let msg = format!("{} (20/20). A human must reply.", BOT_TURN_LIMIT_WARNING_PREFIX); + let msgs = vec![make_message(false, &msg)]; assert!(!turn_limit_warning_present(&msgs)); } From 5fc831d834712fd831e589212c0c35d0d139d6cb Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Thu, 21 May 2026 16:10:54 +0000 Subject: [PATCH 6/8] fix(bot_turns): use BOT_TURN_LIMIT_WARNING_PREFIX in hard limit message and test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ensures the constant is the true single source of truth for the warning prefix — both soft and hard limit messages now use it. Addresses 臥龍 re-review feedback. --- src/bot_turns.rs | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/bot_turns.rs b/src/bot_turns.rs index ddad8d85..5a8b40a4 100644 --- a/src/bot_turns.rs +++ b/src/bot_turns.rs @@ -80,8 +80,9 @@ impl BotTurnTracker { severity: TurnSeverity::Soft, turns: n, user_message: format!( - "⚠️ Bot turn limit reached ({n}/{soft}). \ + "{} ({n}/{soft}). \ A human must reply in this thread to continue bot-to-bot conversation.", + BOT_TURN_LIMIT_WARNING_PREFIX, soft = self.soft_limit, ), }, @@ -89,8 +90,9 @@ impl BotTurnTracker { severity: TurnSeverity::Hard, turns: HARD_BOT_TURN_LIMIT, user_message: format!( - "🛑 Hard bot turn limit reached ({HARD_BOT_TURN_LIMIT}). \ - A human must reply to continue." + "🛑 {} ({HARD_BOT_TURN_LIMIT}/{HARD_BOT_TURN_LIMIT} hard limit). \ + A human must reply to continue.", + BOT_TURN_LIMIT_WARNING_PREFIX, ), }, TurnResult::Throttled | TurnResult::Stopped => TurnAction::SilentStop, @@ -281,9 +283,11 @@ mod tests { TurnAction::WarnAndStop { severity: TurnSeverity::Soft, turns: 3, - user_message: "⚠️ Bot turn limit reached (3/3). \ - A human must reply in this thread to continue bot-to-bot conversation." - .to_string(), + user_message: format!( + "{} (3/3). \ + A human must reply in this thread to continue bot-to-bot conversation.", + BOT_TURN_LIMIT_WARNING_PREFIX, + ), }, ); } @@ -309,8 +313,10 @@ mod tests { severity: TurnSeverity::Hard, turns: HARD_BOT_TURN_LIMIT, user_message: format!( - "🛑 Hard bot turn limit reached ({HARD_BOT_TURN_LIMIT}). \ - A human must reply to continue." + user_message: format!( + "🛑 {} ({HARD_BOT_TURN_LIMIT}/{HARD_BOT_TURN_LIMIT} hard limit). \ + A human must reply to continue.", + BOT_TURN_LIMIT_WARNING_PREFIX, ), }, ); From 7c9e1566bc75553f4d5920ff3cd71a17a0800a6f Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Fri, 22 May 2026 09:22:41 +0000 Subject: [PATCH 7/8] fix(discord): use (bool, &str) tuples in turn_limit_warning_present to avoid serenity Message construction in tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follows existing codebase convention (see format_thread_export boundary comment) of not constructing serenity::model::channel::Message in unit tests. Call site maps Message → (is_bot, content) pairs before passing to the helper. Tests now use plain tuple slices — no serde_json needed. --- src/discord.rs | 69 ++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/src/discord.rs b/src/discord.rs index 9e552155..6355e17d 100644 --- a/src/discord.rs +++ b/src/discord.rs @@ -400,15 +400,19 @@ impl EventHandler for Handler { // Dedup: skip if another bot already posted the same // warning in this thread. Prevents N duplicate warnings // when N bot processes each hit the soft limit. (#530) - let already_warned = turn_limit_warning_present( - &msg.channel_id - .messages( - &ctx.http, - serenity::builder::GetMessages::new().limit(10), - ) - .await - .unwrap_or_default(), - ); + let recent = msg + .channel_id + .messages( + &ctx.http, + serenity::builder::GetMessages::new().limit(10), + ) + .await + .unwrap_or_default(); + let pairs: Vec<(bool, &str)> = recent + .iter() + .map(|m| (m.author.bot, m.content.as_str())) + .collect(); + let already_warned = turn_limit_warning_present(&pairs); if !already_warned { let _ = msg.channel_id.say(&ctx.http, &user_message).await; } @@ -2171,10 +2175,14 @@ fn should_process_user_message( /// simultaneously and both see no warning, resulting in a duplicate. For most /// deployments this is acceptable; strict once-only semantics would require /// shared state (e.g. gateway-owned emission or distributed lock). -fn turn_limit_warning_present(messages: &[Message]) -> bool { +/// +/// Accepts `(is_bot, content)` pairs so the logic can be unit-tested without +/// constructing `serenity::model::channel::Message` values (see existing test +/// boundary comment at `format_thread_export`). +fn turn_limit_warning_present(messages: &[(bool, &str)]) -> bool { messages .iter() - .any(|m| m.author.bot && m.content.contains(BOT_TURN_LIMIT_WARNING_PREFIX)) + .any(|(is_bot, content)| *is_bot && content.contains(BOT_TURN_LIMIT_WARNING_PREFIX)) } #[cfg(test)] @@ -2968,54 +2976,21 @@ mod tests { // --- WarnAndStop dedup tests (#530) --- - fn make_message(is_bot: bool, content: &str) -> Message { - serde_json::from_value(serde_json::json!({ - "id": "1", - "channel_id": "1", - "author": { - "id": "1", - "username": "bot", - "discriminator": "0000", - "bot": is_bot, - "avatar": null - }, - "content": content, - "timestamp": "2026-01-01T00:00:00+00:00", - "edited_timestamp": null, - "tts": false, - "mention_everyone": false, - "mentions": [], - "mention_roles": [], - "attachments": [], - "embeds": [], - "pinned": false, - "type": 0 - })) - .expect("valid Message JSON") - } - #[test] fn dedup_detects_existing_bot_warning() { let msg = format!("{} (20/20). A human must reply.", BOT_TURN_LIMIT_WARNING_PREFIX); - let msgs = vec![make_message(true, &msg)]; - assert!(turn_limit_warning_present(&msgs)); + assert!(turn_limit_warning_present(&[(true, &msg)])); } #[test] fn dedup_ignores_human_warning_text() { - // Same text from a human should not count as a warning let msg = format!("{} (20/20). A human must reply.", BOT_TURN_LIMIT_WARNING_PREFIX); - let msgs = vec![make_message(false, &msg)]; - assert!(!turn_limit_warning_present(&msgs)); + assert!(!turn_limit_warning_present(&[(false, &msg)])); } #[test] fn dedup_returns_false_when_no_warning() { - let msgs = vec![ - make_message(true, "hello"), - make_message(false, "world"), - ]; - assert!(!turn_limit_warning_present(&msgs)); + assert!(!turn_limit_warning_present(&[(true, "hello"), (false, "world")])); } #[test] From 8ed75463b8152ed68cea509fabbdb1afcf62ee6a Mon Sep 17 00:00:00 2001 From: feiyun968-agent Date: Fri, 22 May 2026 14:13:44 +0000 Subject: [PATCH 8/8] fix(bot_turns): remove duplicate user_message line; restore hard limit message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove extraneous duplicate 'user_message: format!(' in hard limit test (CI blocker — caused compilation error) - Revert hard limit message to standalone '🛑 Hard bot turn limit reached' instead of using BOT_TURN_LIMIT_WARNING_PREFIX, which produced confusing '🛑 ⚠️ Bot turn limit reached' double-emoji output (Option B per review) - BOT_TURN_LIMIT_WARNING_PREFIX remains the single source of truth for the soft limit warning and the dedup check — hard limit is rare enough that dedup is not needed there Addresses masami-agent review findings. --- src/bot_turns.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/bot_turns.rs b/src/bot_turns.rs index 5a8b40a4..130fa717 100644 --- a/src/bot_turns.rs +++ b/src/bot_turns.rs @@ -90,9 +90,8 @@ impl BotTurnTracker { severity: TurnSeverity::Hard, turns: HARD_BOT_TURN_LIMIT, user_message: format!( - "🛑 {} ({HARD_BOT_TURN_LIMIT}/{HARD_BOT_TURN_LIMIT} hard limit). \ - A human must reply to continue.", - BOT_TURN_LIMIT_WARNING_PREFIX, + "🛑 Hard bot turn limit reached ({HARD_BOT_TURN_LIMIT}). \ + A human must reply to continue." ), }, TurnResult::Throttled | TurnResult::Stopped => TurnAction::SilentStop, @@ -313,10 +312,8 @@ mod tests { severity: TurnSeverity::Hard, turns: HARD_BOT_TURN_LIMIT, user_message: format!( - user_message: format!( - "🛑 {} ({HARD_BOT_TURN_LIMIT}/{HARD_BOT_TURN_LIMIT} hard limit). \ - A human must reply to continue.", - BOT_TURN_LIMIT_WARNING_PREFIX, + "🛑 Hard bot turn limit reached ({HARD_BOT_TURN_LIMIT}). \ + A human must reply to continue." ), }, );