diff --git a/src/agent/routine_engine.rs b/src/agent/routine_engine.rs
index 220e32c9..dca4cf85 100644
--- a/src/agent/routine_engine.rs
+++ b/src/agent/routine_engine.rs
@@ -1996,18 +1996,19 @@ fn sanitize_summary(s: &str) -> String {
/// Only strips patterns that look like real HTML/XML tags (e.g. `
`, ``,
/// `
`), not generic angle-bracket content like `Vec
`,
/// `cat < input.txt`, or comparison operators.
+///
+/// Also strips HTML comments (``), SVG/MathML tags, and custom elements
+/// (tags containing hyphens like ``).
fn strip_html_tags(s: &str) -> String {
use std::sync::LazyLock;
- // Matches opening tags like ,
, ,
- // closing tags like , , and self-closing tags like .
- // Does NOT match things like Vec, x<10, or < input.txt because those
- // don't have a letter immediately after '<' followed by valid tag structure,
- // or they aren't among recognized HTML tag names.
+ // HTML comment pattern:
+ static COMMENT_RE: LazyLock> =
+ LazyLock::new(|| Regex::new(r"").ok());
+
+ // Known HTML/SVG/MathML tag names. Includes SVG tags (svg, path, circle, etc.)
+ // and MathML tags (math, mrow, etc.) that can carry event handlers.
static HTML_TAG_RE: LazyLock > = LazyLock::new(|| {
- // Match or where tagname starts with a letter.
- // We restrict to known HTML tag names to avoid false positives on generic
- // identifiers like Vec.
let tags = "a|abbr|address|area|article|aside|audio|b|base|bdi|bdo|blockquote|\
body|br|button|canvas|caption|cite|code|col|colgroup|data|datalist|dd|del|\
details|dfn|dialog|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|\
@@ -2016,14 +2017,34 @@ fn strip_html_tags(s: &str) -> String {
option|output|p|param|picture|pre|progress|q|rp|rt|ruby|s|samp|script|\
section|select|slot|small|source|span|strong|style|sub|summary|sup|table|\
tbody|td|template|textarea|tfoot|th|thead|time|title|tr|track|u|ul|var|\
- video|wbr";
- Regex::new(&format!(r"(?i)?(?:{})(?:\s[^>]*)?>", tags)).ok()
+ video|wbr|\
+ svg|g|path|circle|ellipse|line|polyline|polygon|rect|text|tspan|defs|\
+ clippath|mask|pattern|image|use|symbol|marker|lineargradient|\
+ radialgradient|stop|filter|foreignobject|animate|animatetransform|\
+ math|mrow|mi|mo|mn|ms|mtext|mfrac|msqrt|mroot|msub|msup|msubsup|\
+ munder|mover|munderover|mtable|mtr|mtd|mspace|mpadded|mfenced|menclose";
+ // Handles: , , , , ,
+ Regex::new(&format!(r"(?i)?(?:{})(?:\s[^>]*)?\s*/?>", tags)).ok()
});
- match HTML_TAG_RE.as_ref() {
- Some(re) => re.replace_all(s, "").into_owned(),
- None => s.to_string(),
+ // Custom elements: tags containing a hyphen (web components spec requires it).
+ // E.g. , ,
+ static CUSTOM_ELEMENT_RE: LazyLock> =
+ LazyLock::new(|| Regex::new(r"(?i)?\w+-[\w-]*(?:\s[^>]*)?\s*/?>").ok());
+
+ let mut result = s.to_string();
+
+ if let Some(re) = COMMENT_RE.as_ref() {
+ result = re.replace_all(&result, "").into_owned();
}
+ if let Some(re) = HTML_TAG_RE.as_ref() {
+ result = re.replace_all(&result, "").into_owned();
+ }
+ if let Some(re) = CUSTOM_ELEMENT_RE.as_ref() {
+ result = re.replace_all(&result, "").into_owned();
+ }
+
+ result
}
#[cfg(test)]
@@ -2654,6 +2675,46 @@ mod tests {
);
}
+ #[test]
+ fn test_sanitize_summary_strips_all_html_forms() {
+ use super::sanitize_summary;
+
+ // Self-closing tags without whitespace: ,
+ assert_eq!(sanitize_summary("line1 line2"), "line1line2");
+ assert_eq!(sanitize_summary("text more"), "textmore");
+ assert_eq!(sanitize_summary("text more"), "textmore");
+
+ // HTML comments
+ assert_eq!(sanitize_summary("beforeafter"), "beforeafter");
+ assert_eq!(sanitize_summary("ab"), "ab");
+
+ // SVG tags (can carry event handlers)
+ assert_eq!(
+ sanitize_summary("payload "),
+ "payload"
+ );
+ assert_eq!(sanitize_summary(" "), "");
+
+ // MathML tags
+ assert_eq!(sanitize_summary("x "), "x");
+
+ // Custom elements (web components with hyphens)
+ assert_eq!(
+ sanitize_summary("beforeinner after"),
+ "beforeinnerafter"
+ );
+ assert_eq!(
+ sanitize_summary("content "),
+ "content"
+ );
+
+ // Generics must still be preserved
+ assert_eq!(
+ sanitize_summary("expected Vec"),
+ "expected Vec"
+ );
+ }
+
#[test]
fn test_status_display_label_readable() {
use super::status_display_label;