- 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]
118 lines
4.8 KiB
Markdown
118 lines
4.8 KiB
Markdown
# 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:
|
|
|
|
```rust
|
|
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_action` and `close_action` from `use_hidden_domain` flag
|
|
- Send close first, then open on the same WebSocket connection
|
|
|
|
- [ ] **Step 2: Change `use_hidden_domain` to `true` in server.rs**
|
|
|
|
In `src/service/server.rs`, at the `start_with_browser_ws_url` call, change `false` to `true`:
|
|
|
|
```rust
|
|
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**
|
|
|
|
```bash
|
|
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:
|
|
1. `bootstrap_helper_page` sends close command before open command (both on same WS connection)
|
|
2. `use_hidden_domain` is `true` in `server.rs` — helper page opens in hidden domain
|
|
3. `Drop for LiveBrowserCallbackHost` remains simple (shutdown only, no close attempt)
|
|
4. `cached_host` is still in `mod.rs` outer loop (process-internal deduplication)
|