Skip to content

Commit 38c8a42

Browse files
committed
fix: stabilize real-mouse pet on new Codex App / 稳定新版 App 桌宠真实鼠标交互
中文:适配新版 Codex App 的 avatar overlay 生命周期变化,避免瞬时能力探测失败、旧 driver 清理和迟到 dispatcher 订阅停掉新的真实鼠标 runtime。改用宠物几何边界优先让出原生 hover/拖拽。此提交不包含桌宠持续状态功能。 English: Adapt the real-mouse pet runtime to the new Codex App avatar-overlay lifecycle. Transient capability probe failures, stale driver cleanup, and late dispatcher subscriptions no longer retire the replacement runtime. Mascot geometry yields to native hover and drag interaction. Persistent pet status is intentionally excluded.
1 parent 285f40e commit 38c8a42

3 files changed

Lines changed: 47 additions & 28 deletions

File tree

assets/inject/pet-real-mouse-inject.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(() => {
2-
const runtimeVersion = "4";
2+
const runtimeVersion = "5";
33
const existing = window.__codexPlusPetRealMouseLook;
44
if (existing?.version === runtimeVersion && existing?.isReady?.()) return;
55
existing?.stop?.();
@@ -76,12 +76,18 @@
7676
if (!resolved || typeof resolved.subscribe !== "function") {
7777
throw new Error("V2 avatar overlay dispatcher unavailable");
7878
}
79-
dispatcher = resolved;
80-
unsubscribe = dispatcher.subscribe(eventType, (message) => {
79+
if (stopped) throw new Error("pet real-mouse runtime was retired during dispatcher setup");
80+
const nextUnsubscribe = resolved.subscribe(eventType, (message) => {
8181
if (sendingSynthetic) return;
8282
nativeCursorActive = !!message?.point;
8383
if (nativeCursorActive) syntheticActive = false;
8484
});
85+
if (stopped) {
86+
nextUnsubscribe?.();
87+
throw new Error("pet real-mouse runtime was retired during dispatcher subscription");
88+
}
89+
dispatcher = resolved;
90+
unsubscribe = nextUnsubscribe;
8591
return dispatcher;
8692
}).catch((error) => {
8793
dispatcherPromise = null;
@@ -116,6 +122,7 @@
116122
async function updateScreenPoint(screenPoint) {
117123
if (stopped || !acceptsUpdates || updateInFlight) return;
118124
const mascot = document.querySelector(mascotSelector);
125+
runtime.mascotHovered = false;
119126
if (!mascot || document.visibilityState !== "visible" || dragging || nativeCursorActive) {
120127
if (!nativeCursorActive) clearSyntheticPoint();
121128
return;
@@ -128,14 +135,16 @@
128135
updateInFlight = true;
129136
try {
130137
const localPoint = { x: screenPoint.x - window.screenX, y: screenPoint.y - window.screenY };
131-
const hit = document.elementFromPoint(localPoint.x, localPoint.y);
132-
const mascotHovered = mascot.matches(":hover") || !!hit?.closest?.(mascotSelector);
138+
const bounds = mascot.getBoundingClientRect();
139+
const mascotHovered = localPoint.x >= bounds.left
140+
&& localPoint.x <= bounds.right
141+
&& localPoint.y >= bounds.top
142+
&& localPoint.y <= bounds.bottom;
133143
runtime.mascotHovered = mascotHovered;
134144
if (mascotHovered) {
135145
clearSyntheticPoint();
136146
return;
137147
}
138-
const bounds = mascot.getBoundingClientRect();
139148
const centerX = bounds.left + bounds.width / 2;
140149
const centerY = bounds.top + bounds.height / 2;
141150
const distance = Math.hypot(localPoint.x - centerX, localPoint.y - centerY);

crates/codex-plus-core/src/launcher.rs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2275,22 +2275,24 @@ async fn confirmed_pet_overlay_targets(
22752275
let Some(websocket_url) = target.web_socket_debugger_url.as_deref() else {
22762276
continue;
22772277
};
2278-
if pet_overlay_supports_v2_cursor(websocket_url).await {
2278+
if pet_overlay_supports_v2_cursor(websocket_url)
2279+
.await
2280+
.unwrap_or(false)
2281+
{
22792282
confirmed.push(target);
22802283
}
22812284
}
22822285
Ok(confirmed)
22832286
}
22842287

2285-
async fn pet_overlay_supports_v2_cursor(websocket_url: &str) -> bool {
2286-
crate::bridge::evaluate_script_with_await_promise(
2288+
async fn pet_overlay_supports_v2_cursor(websocket_url: &str) -> anyhow::Result<bool> {
2289+
let result = crate::bridge::evaluate_script_with_await_promise(
22872290
websocket_url,
22882291
&crate::assets::pet_real_mouse_capability_probe_script(),
22892292
true,
22902293
)
2291-
.await
2292-
.as_ref()
2293-
.is_ok_and(runtime_evaluate_result_is_true)
2294+
.await?;
2295+
Ok(runtime_evaluate_result_is_true(&result))
22942296
}
22952297

22962298
async fn sync_pet_real_mouse_overlay(debug_port: u16, _helper_port: u16) -> anyhow::Result<()> {
@@ -2304,8 +2306,17 @@ async fn sync_pet_real_mouse_overlay(debug_port: u16, _helper_port: u16) -> anyh
23042306
let Some(websocket_url) = target.web_socket_debugger_url.as_deref() else {
23052307
continue;
23062308
};
2307-
let supports_v2 = enabled && pet_overlay_supports_v2_cursor(websocket_url).await;
2308-
let script = if supports_v2 {
2309+
let script = if !enabled {
2310+
crate::assets::pet_real_mouse_stop_script()
2311+
} else if pet_overlay_supports_v2_cursor(websocket_url)
2312+
.await
2313+
.with_context(|| {
2314+
format!(
2315+
"failed to probe pet overlay capability in target {} ({})",
2316+
target.id, target.url
2317+
)
2318+
})?
2319+
{
23092320
crate::assets::pet_real_mouse_script()
23102321
} else {
23112322
crate::assets::pet_real_mouse_stop_script()
@@ -2353,15 +2364,6 @@ async fn run_pet_real_mouse_cursor_driver(debug_port: u16) {
23532364
);
23542365
}
23552366
}
2356-
for target in &targets {
2357-
if let Some(websocket_url) = target.web_socket_debugger_url.as_deref() {
2358-
let _ = crate::bridge::evaluate_script(
2359-
websocket_url,
2360-
crate::assets::pet_real_mouse_stop_script(),
2361-
)
2362-
.await;
2363-
}
2364-
}
23652367
drivers.abort_all();
23662368
while drivers.join_next().await.is_some() {}
23672369
tokio::time::sleep(std::time::Duration::from_secs(5)).await;
@@ -2399,9 +2401,13 @@ async fn run_pet_real_mouse_target_driver(debug_port: u16, target: crate::cdp::C
23992401
)
24002402
.await;
24012403

2402-
let _ =
2403-
crate::bridge::evaluate_script(websocket_url, crate::assets::pet_real_mouse_stop_script())
2404-
.await;
2404+
if result.is_ok() {
2405+
let _ = crate::bridge::evaluate_script(
2406+
websocket_url,
2407+
crate::assets::pet_real_mouse_stop_script(),
2408+
)
2409+
.await;
2410+
}
24052411
match result {
24062412
Ok(()) => {
24072413
PET_CURSOR_DRIVER_FAILED.store(false, Ordering::Relaxed);

crates/codex-plus-core/tests/cdp_bridge.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ fn pet_real_mouse_script_uses_cdp_push_and_native_avatar_event() {
7272
assert!(script.contains("nativeCursorActive"));
7373
assert!(script.contains("transport: \"cdp-push\""));
7474
assert!(script.contains("updateScreenPoint(point)"));
75-
assert!(script.contains("mascot.matches(\":hover\")"));
76-
assert!(script.contains("document.elementFromPoint(localPoint.x, localPoint.y)"));
75+
assert!(script.contains("localPoint.x >= bounds.left"));
76+
assert!(script.contains("localPoint.y <= bounds.bottom"));
77+
assert!(!script.contains("document.elementFromPoint"));
7778
assert!(script.contains("if (mascotHovered)"));
7879
assert!(
7980
script
@@ -87,6 +88,9 @@ fn pet_real_mouse_script_uses_cdp_push_and_native_avatar_event() {
8788
assert!(!script.contains("/pet/cursor-position"));
8889
assert!(!script.contains("X-Codex-Plus-Pet-Token"));
8990
assert!(script.contains("delete window.__codexPlusPetRealMouseLook"));
91+
assert!(script.contains("retired during dispatcher setup"));
92+
assert!(script.contains("nextUnsubscribe?.()"));
93+
assert!(script.contains("const runtimeVersion = \"5\""));
9094
}
9195

9296
#[test]

0 commit comments

Comments
 (0)