agentmux_cef\wrr/classify.rs
1// Copyright 2026, AgentMux Corp.
2// SPDX-License-Identifier: Apache-2.0
3//
4// Phase B.9.1 — class-name filter for the WRR Win32 event hook.
5//
6// Hook callbacks fire for every window-object event in the host
7// process: CEF browser top-levels (the ones we care about), CEF
8// subprocess HWNDs, OS-managed transient HWNDs (tooltips, IME
9// candidate lists, message-only windows), and so on. Forwarding
10// every event over the IPC pipe would flood the wire with noise
11// the reducer can't act on.
12//
13// `is_app_class` filters at the hook callback so only candidate
14// AgentMux top-level windows produce IPC traffic. The classifier
15// is allowlist-based — false negatives (a real window we silently
16// drop) are worse than false positives (a non-app HWND that the
17// reducer just ignores), so we keep the allowlist conservative
18// and let the reducer's `pending_hwnds` machinery age out spurious
19// entries.
20
21/// Phase B.9.1 — does this Win32 window class look like an
22/// AgentMux top-level window?
23///
24/// CEF's top-level windows in CEF 146 use the
25/// `Chrome_WidgetWin_*` class family (CEF Views wraps each native
26/// window in a Chromium widget host). AgentMux's main window
27/// shows up as `Chrome_WidgetWin_1`. Browser-pane child HWNDs and
28/// CEF subprocess HWNDs use `Chrome_RenderWidgetHostHWND` and
29/// related — those are explicitly excluded.
30pub fn is_app_class(class_name: &str) -> bool {
31 // Chromium widget host windows. CEF wraps every top-level
32 // CefWindow in one. The numeric suffix varies (`_0` `_1` etc.)
33 // depending on CEF init order.
34 if class_name.starts_with("Chrome_WidgetWin_") {
35 return true;
36 }
37 // CEF Views Native Window (rare in our build but possible).
38 if class_name == "CefBrowserWindow" {
39 return true;
40 }
41 false
42}
43
44/// Phase B.9.1 — explicit excludes for class names that LOOK
45/// app-like but should never produce drift. Currently empty
46/// because `is_app_class` is allowlist-based; kept as a hook for
47/// follow-up tuning if the allowlist gets too permissive.
48pub fn is_explicitly_excluded(_class_name: &str) -> bool {
49 false
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 #[test]
57 fn chrome_widget_win_classes_match() {
58 assert!(is_app_class("Chrome_WidgetWin_0"));
59 assert!(is_app_class("Chrome_WidgetWin_1"));
60 assert!(is_app_class("Chrome_WidgetWin_42"));
61 }
62
63 #[test]
64 fn cef_browser_window_matches() {
65 assert!(is_app_class("CefBrowserWindow"));
66 }
67
68 #[test]
69 fn renderer_subprocess_class_is_filtered_out() {
70 assert!(!is_app_class("Chrome_RenderWidgetHostHWND"));
71 assert!(!is_app_class("Intermediate D3D Window"));
72 }
73
74 #[test]
75 fn unrelated_classes_filtered_out() {
76 assert!(!is_app_class("tooltips_class32"));
77 assert!(!is_app_class("Static"));
78 assert!(!is_app_class(""));
79 }
80}