- Auto-connect WebSocket on page load in service console - Settings modal for editing sgclaw_config.json (API key, base URL, model, skills dir, etc.) - UpdateConfig/ConfigUpdated protocol messages for remote config save - save_to_path() for SgClawSettings serialization - ConfigUpdated handler in sg_claw_client binary - Protocol serialization tests for new message types - HTML test assertions for auto-connect and settings UI - Additional pending changes: deterministic submit, org units, lineloss xlsx export, browser script tool, and docs 🤖 Generated with [Qoder][https://qoder.com]
4.8 KiB
Helper Page Lifecycle Fix v2 — Same-Connection Close + Open
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Prevent orphaned helper pages across process restarts by closing existing ones before opening new ones, all on the same WebSocket connection.
Architecture: In bootstrap_helper_page, after registering with the browser WS, send sgHideBrowerserClosePage (best-effort, silently ignored if no page exists), then send sgHideBrowerserOpenPage. Change use_hidden_domain to true.
Tech Stack: Rust, tungstenite, SuperRPA browser WS protocol
Task 1: Add close-before-open in bootstrap_helper_page
Files:
-
Modify:
src/browser/callback_host.rs:345-374(bootstrap_helper_page function) -
Step 1: Add close command before open command in bootstrap_helper_page
Replace the current bootstrap_helper_page function. After recv_bootstrap_prelude, send the close command first, then the open command:
fn bootstrap_helper_page(
browser_ws_url: &str,
request_url: &str,
helper_url: &str,
use_hidden_domain: bool,
) -> Result<(), PipeError> {
let (mut websocket, _) = connect(browser_ws_url)
.map_err(|err| PipeError::Protocol(format!("browser websocket connect failed: {err}")))?;
configure_bootstrap_socket(&mut websocket)?;
websocket
.send(Message::Text(
r#"{"type":"register","role":"web"}"#.to_string().into(),
))
.map_err(|err| PipeError::Protocol(format!("browser websocket register failed: {err}")))?;
let _ = recv_bootstrap_prelude(&mut websocket);
// Close any orphaned helper page from a previous process run.
// Best-effort: if no page exists, the browser silently ignores this.
let (open_action, close_action) = if use_hidden_domain {
("sgHideBrowerserOpenPage", "sgHideBrowerserClosePage")
} else {
("sgBrowerserOpenPage", "sgBrowserClosePage")
};
let close_payload = json!([request_url, close_action, helper_url]).to_string();
let _ = websocket.send(Message::Text(close_payload.into()));
let payload = json!([
request_url,
open_action,
helper_url,
])
.to_string();
websocket
.send(Message::Text(payload.into()))
.map_err(|err| PipeError::Protocol(format!("helper bootstrap send failed: {err}")))?;
Ok(())
}
Key changes from current code:
-
After
recv_bootstrap_prelude, add the close command (best-effort, ignore errors) -
Compute both
open_actionandclose_actionfromuse_hidden_domainflag -
Send close first, then open on the same WebSocket connection
-
Step 2: Change
use_hidden_domaintotruein server.rs
In src/service/server.rs, at the start_with_browser_ws_url call, change false to true:
match LiveBrowserCallbackHost::start_with_browser_ws_url(
browser_ws_url,
&bootstrap_url,
Duration::from_secs(15),
BROWSER_RESPONSE_TIMEOUT,
true, // use_hidden_domain: hidden domain for invisible helper
) {
- Step 3: Build
Run: cargo build 2>&1
Expected: 0 errors.
- Step 4: Run callback_host tests
Run: cargo test --lib -- callback_host 2>&1
Expected: 12 tests pass (including live_callback_host_sends_bootstrap_open_page_command which still checks for sgBrowerserOpenPage because the test passes false, and live_callback_host_hidden_domain_sends_hide_open_page_command which passes true).
Note: The test passes false for use_hidden_domain, so the close command will use sgBrowserClosePage. The test's fake WebSocket server will receive both the close and open frames. The test only checks that sgBrowerserOpenPage is present, which is still true.
- Step 5: Commit
git add src/browser/callback_host.rs src/service/server.rs
git commit -m "fix(callback_host): close orphaned helper page before opening new one on same WS"
Task 2: Full verification
Files: None (verification only)
- Step 1: Full test suite
Run: cargo test 2>&1
Expected: All tests pass except pre-existing lineloss_period_resolver_prompts_for_missing_period failure.
- Step 2: Verify key behavioral changes
Manually confirm:
bootstrap_helper_pagesends close command before open command (both on same WS connection)use_hidden_domainistrueinserver.rs— helper page opens in hidden domainDrop for LiveBrowserCallbackHostremains simple (shutdown only, no close attempt)cached_hostis still inmod.rsouter loop (process-internal deduplication)