Consolidate the browser task runtime around the callback path, add safer artifact opening for Zhihu exports, and cover the new service/browser flows with focused tests and supporting docs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
638 lines
29 KiB
Markdown
638 lines
29 KiB
Markdown
# Zhihu Hotlist Post-Export Auto-Open Implementation Plan
|
|
|
|
> **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:** Extend the existing Zhihu hotlist Excel and dashboard routes so each route can auto-open its own generated artifact after export, while preserving the current callback-host-backed browser boundary and route exclusivity.
|
|
|
|
**Architecture:** Keep orchestration in `src/compat/workflow_executor.rs`, but move post-export side effects into a new `src/compat/artifact_open.rs` helper so workflow routing stays readable. Excel auto-open is a local OS-launch side effect; dashboard auto-open reuses `screen_html_export`'s existing `presentation.url` and sends one narrow, marker-based `Action::Navigate` request through `BrowserCallbackBackend`, with a matching special-case validator in `MacPolicy` so arbitrary `file://` navigation remains blocked.
|
|
|
|
**Tech Stack:** Rust, serde_json, std::process::Command, std::path, Cargo tests
|
|
|
|
---
|
|
|
|
## File Map
|
|
|
|
- Create: `src/compat/artifact_open.rs`
|
|
- Define the narrow post-export helper surface for this slice only
|
|
- Parse and validate generated artifact payload fields passed in by the workflow layer
|
|
- Open generated `.xlsx` files with the local default app
|
|
- Build the exact approved local-dashboard navigate payload
|
|
- Keep one testable internal seam, `open_exported_xlsx_with(output_path, opener)`, so unit tests can prove the generated `.xlsx` path is handed to the launcher without starting a real spreadsheet app
|
|
- Include unit tests in the same file for exact Excel path handoff and launcher-failure reporting
|
|
- Modify: `src/compat/mod.rs`
|
|
- Export the new `artifact_open` module
|
|
- Modify: `src/compat/workflow_executor.rs`
|
|
- Keep route detection and artifact generation where they are now
|
|
- Change `export_xlsx(...)` and `export_screen(...)` so they parse tool payloads, call the route-specific opener, and produce the new success/failure summaries
|
|
- Modify: `src/browser/callback_backend.rs`
|
|
- Recognize only the approved local-dashboard navigate request shape at `Action::Navigate`
|
|
- Keep normal remote navigate behavior unchanged
|
|
- Continue emitting `sgBrowerserOpenPage` for the approved local-dashboard case so the helper page stays alive and the dashboard opens in a new visible tab
|
|
- Add focused callback-backend unit tests in the existing test module for approved and malformed local-dashboard requests
|
|
- Modify: `src/security/mac_policy.rs`
|
|
- Add a narrow validator for the approved local-dashboard presentation case
|
|
- Keep `validate(...)` unchanged for ordinary remote-domain flow
|
|
- Reject malformed marker payloads, non-HTML local paths, and mismatched `file://` / output-path combinations
|
|
- Modify: `tests/compat_runtime_test.rs`
|
|
- Keep the concrete hotlist workflow regressions in this existing integration test file
|
|
- Extend existing Zhihu hotlist export/screen regressions to assert the new summaries and the dashboard marker payload
|
|
- Keep the Excel route workflow assertion limited to summary plus “no dashboard navigate marker,” because exact launcher handoff is covered in `src/compat/artifact_open.rs` unit tests
|
|
- Modify: `tests/browser_tool_test.rs`
|
|
- Add `MacPolicy` coverage for approved local-dashboard presentation, rejected malformed presentation, and unchanged normal-domain validation in one exact file
|
|
- Extend the existing `default_rules_allow_zhihu_navigation` area with the new local-dashboard validation tests rather than creating a second policy test location
|
|
- Reference only if summary wording ripples outward: `tests/agent_runtime_test.rs:173-258`
|
|
- Existing direct-runtime user-visible summary assertion for Zhihu Excel export
|
|
- Reference only if summary wording ripples outward: `tests/service_task_flow_test.rs:704-839`
|
|
- Existing CLI-to-service user-visible summary assertion for Zhihu Excel export
|
|
- Reference only if summary wording ripples outward: `tests/service_ws_session_test.rs:755-869`
|
|
- Existing service-binary user-visible summary assertion for Zhihu Excel export
|
|
- Reference: `tests/compat_screen_html_export_tool_test.rs`
|
|
- Reuse the exact test seam `screen_html_export_tool_renders_dashboard_html_with_presentation_contract`
|
|
- Existing proof that `screen_html_export` already returns `presentation.url`
|
|
- Reference: `docs/superpowers/specs/2026-04-06-zhihu-hotlist-post-export-auto-open-design.md`
|
|
|
|
## Scope Guardrails
|
|
|
|
- Do not modify `frontend/service-console/sg_claw_service_console.html`.
|
|
- Do not modify `src/service/protocol.rs`.
|
|
- Do not modify `browser-helper.html`.
|
|
- Do not modify `/sgclaw/callback/*` endpoint contracts.
|
|
- Do not modify websocket protocol framing or `src/browser/ws_protocol.rs`.
|
|
- Do not turn Excel-open and dashboard-open into a combined mode.
|
|
- Do not add a general-purpose local file browser or generic `file://` allowlist.
|
|
- Do not move post-export decisions into the frontend service console.
|
|
- Do not require websocket-backend parity in this slice.
|
|
|
|
### Task 1: Add failing workflow tests for route-specific post-export actions
|
|
|
|
**Files:**
|
|
- Modify: `tests/compat_runtime_test.rs:2154-2304`
|
|
- Reference: `src/compat/workflow_executor.rs:375-446`
|
|
- Reference: `docs/superpowers/specs/2026-04-06-zhihu-hotlist-post-export-auto-open-design.md`
|
|
|
|
- [ ] **Step 1: Rewrite the Excel hotlist assertion as a red test for the new summary only**
|
|
|
|
Keep the current flow setup, but tighten the expectation so it proves the workflow route now reports post-export open success while staying exclusive from the dashboard path.
|
|
|
|
Target shape:
|
|
|
|
```rust
|
|
#[test]
|
|
fn handle_browser_message_chains_hotlist_skill_into_xlsx_export_and_auto_open() {
|
|
// existing setup
|
|
assert!(summary.contains("已导出并打开知乎热榜 Excel"));
|
|
assert!(generated.exists());
|
|
assert!(!sent.iter().any(|message| {
|
|
matches!(
|
|
message,
|
|
AgentMessage::Command { action, params, .. }
|
|
if action == &Action::Navigate
|
|
&& params.get("sgclaw_local_dashboard_open").is_some()
|
|
)
|
|
}));
|
|
}
|
|
```
|
|
|
|
Do not try to prove real OS launching in this workflow test. The exact `.xlsx` path handoff to the launcher belongs in `src/compat/artifact_open.rs` unit tests from Task 2.
|
|
|
|
- [ ] **Step 2: Rewrite the dashboard hotlist assertion as a red test for browser auto-open**
|
|
|
|
Tighten the existing dashboard test so it proves the workflow consumes `presentation.url` and emits the approved compat marker payload.
|
|
|
|
Target shape:
|
|
|
|
```rust
|
|
#[test]
|
|
fn handle_browser_message_chains_hotlist_skill_into_screen_export_and_auto_open() {
|
|
// existing setup
|
|
assert!(summary.contains("已在浏览器中打开知乎热榜大屏"));
|
|
let navigate = sent.iter().find_map(|message| match message {
|
|
AgentMessage::Command { action, params, security, .. }
|
|
if action == &Action::Navigate
|
|
&& security.expected_domain == "__sgclaw_local_dashboard__" => Some((params, security)),
|
|
_ => None,
|
|
}).expect("dashboard route should emit local-dashboard navigate request");
|
|
|
|
assert!(navigate.0["url"].as_str().unwrap().starts_with("file://"));
|
|
assert_eq!(navigate.0["sgclaw_local_dashboard_open"]["source"], json!("compat.workflow_executor"));
|
|
assert_eq!(navigate.0["sgclaw_local_dashboard_open"]["kind"], json!("zhihu_hotlist_screen"));
|
|
assert_eq!(navigate.0["sgclaw_local_dashboard_open"]["presentation_url"], navigate.0["url"]);
|
|
}
|
|
```
|
|
|
|
Also assert that this route still logs `call screen_html_export` and does not invoke the Excel opener path.
|
|
|
|
- [ ] **Step 3: Add a missing-`presentation.url` regression in the workflow test module if none exists**
|
|
|
|
Put this close to the existing hotlist tests and keep it narrow:
|
|
|
|
```rust
|
|
#[test]
|
|
fn handle_browser_message_reports_dashboard_auto_open_protocol_error_when_presentation_url_is_missing() {
|
|
// mock screen_html_export success payload with output_path but no presentation.url
|
|
// assert summary contains 已生成知乎热榜大屏 <path>,但浏览器自动打开失败:
|
|
}
|
|
```
|
|
|
|
Use the existing summary/path helpers in the file instead of inventing new parsing helpers.
|
|
|
|
- [ ] **Step 4: Run the focused compat runtime tests to verify they fail**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_xlsx_export_and_auto_open --test compat_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_screen_export_and_auto_open --test compat_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_reports_dashboard_auto_open_protocol_error_when_presentation_url_is_missing --test compat_runtime_test -- --exact
|
|
```
|
|
|
|
Expected: FAIL because the workflow still returns artifact-only summaries and has no post-export open handling.
|
|
|
|
- [ ] **Step 5: Commit the red workflow tests**
|
|
|
|
```bash
|
|
git add tests/compat_runtime_test.rs
|
|
git commit -m "test: add hotlist post-export auto-open regressions"
|
|
```
|
|
|
|
### Task 2: Implement the compat post-export opener and update workflow summaries
|
|
|
|
**Files:**
|
|
- Create: `src/compat/artifact_open.rs`
|
|
- Modify: `src/compat/mod.rs`
|
|
- Modify: `src/compat/workflow_executor.rs:375-446`
|
|
- Test: `src/compat/artifact_open.rs`
|
|
- Test: `tests/compat_runtime_test.rs`
|
|
|
|
- [ ] **Step 1: Add the red unit tests in `src/compat/artifact_open.rs` before writing production code**
|
|
|
|
Create the new module with a `#[cfg(test)]` block first so the Excel opener has an exact, non-UI verification seam.
|
|
|
|
Target tests:
|
|
|
|
```rust
|
|
#[test]
|
|
fn open_exported_xlsx_with_passes_generated_path_to_launcher() {
|
|
let mut seen = None;
|
|
let result = open_exported_xlsx_with(Path::new("C:/tmp/zhihu-hotlist.xlsx"), |path| {
|
|
seen = Some(path.to_path_buf());
|
|
Ok(())
|
|
});
|
|
assert!(matches!(result, PostExportOpen::Opened));
|
|
assert_eq!(seen.unwrap(), PathBuf::from("C:/tmp/zhihu-hotlist.xlsx"));
|
|
}
|
|
|
|
#[test]
|
|
fn open_exported_xlsx_with_reports_launcher_failure() {
|
|
let result = open_exported_xlsx_with(Path::new("C:/tmp/zhihu-hotlist.xlsx"), |_path| {
|
|
Err("launcher failed".to_string())
|
|
});
|
|
assert!(matches!(result, PostExportOpen::Failed(reason) if reason.contains("launcher failed")));
|
|
}
|
|
```
|
|
|
|
Add one matching dashboard payload test in the same file:
|
|
|
|
```rust
|
|
#[test]
|
|
fn open_local_dashboard_uses_exact_approved_marker_payload() {
|
|
// FakeBrowserBackend records invoke(action, params, expected_domain)
|
|
// assert expected_domain == "__sgclaw_local_dashboard__"
|
|
// assert params.url == params.sgclaw_local_dashboard_open.presentation_url
|
|
// assert source/kind/output_path all match the approved contract
|
|
}
|
|
```
|
|
|
|
This step is mandatory so the Excel route is proven to hand the generated path to the opener without launching a real application.
|
|
|
|
- [ ] **Step 2: Run the new unit tests to verify they fail**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_exported_xlsx_with_passes_generated_path_to_launcher --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_exported_xlsx_with_reports_launcher_failure --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_local_dashboard_uses_exact_approved_marker_payload --lib -- --exact
|
|
```
|
|
|
|
Expected: FAIL because `src/compat/artifact_open.rs` does not exist yet.
|
|
|
|
- [ ] **Step 3: Create the small compat opener module**
|
|
|
|
Add one focused helper module rather than embedding side effects directly into `workflow_executor.rs`.
|
|
|
|
Target shape:
|
|
|
|
```rust
|
|
pub const LOCAL_DASHBOARD_EXPECTED_DOMAIN: &str = "__sgclaw_local_dashboard__";
|
|
pub const LOCAL_DASHBOARD_SOURCE: &str = "compat.workflow_executor";
|
|
pub const LOCAL_DASHBOARD_KIND_ZHIHU_HOTLIST_SCREEN: &str = "zhihu_hotlist_screen";
|
|
|
|
pub enum PostExportOpen {
|
|
Opened,
|
|
Failed(String),
|
|
}
|
|
|
|
pub fn open_exported_xlsx(output_path: &Path) -> PostExportOpen {
|
|
open_exported_xlsx_with(output_path, launch_with_default_xlsx_app)
|
|
}
|
|
|
|
fn open_exported_xlsx_with<F>(output_path: &Path, opener: F) -> PostExportOpen
|
|
where
|
|
F: FnOnce(&Path) -> Result<(), String>,
|
|
{ /* test seam */ }
|
|
|
|
pub fn open_local_dashboard(
|
|
browser_backend: &dyn BrowserBackend,
|
|
output_path: &Path,
|
|
presentation_url: &str,
|
|
) -> PostExportOpen { /* invoke Action::Navigate with exact marker payload */ }
|
|
```
|
|
|
|
Keep the module tiny. The only dedicated test seam in this file should be `open_exported_xlsx_with(...)`; do not introduce a general launcher trait.
|
|
|
|
- [ ] **Step 4: Implement the Windows-first `.xlsx` opener minimally**
|
|
|
|
Use a focused local launcher that targets the current environment first.
|
|
|
|
Preferred target shape:
|
|
|
|
```rust
|
|
Command::new("cmd")
|
|
.args(["/C", "start", "", output_path_as_windows_string])
|
|
```
|
|
|
|
Requirements:
|
|
|
|
```text
|
|
- fail if the path does not exist
|
|
- do not swallow command-spawn errors
|
|
- do not open arbitrary user-selected files from outside this workflow
|
|
- keep cross-platform behavior minimal; only add a fallback branch if required to keep tests/build portable
|
|
```
|
|
|
|
If you need a non-Windows fallback for compilation, keep it obviously minimal and out of the hot path.
|
|
|
|
- [ ] **Step 5: Parse payloads in `workflow_executor.rs` and call the new helper**
|
|
|
|
Refactor `export_xlsx(...)` and `export_screen(...)` just enough to separate:
|
|
|
|
```text
|
|
- tool execution
|
|
- payload parsing
|
|
- route-specific post-export open
|
|
- summary formatting
|
|
```
|
|
|
|
Minimal target behavior:
|
|
|
|
```rust
|
|
match open_exported_xlsx(&output_path) {
|
|
PostExportOpen::Opened => format!("已导出并打开知乎热榜 Excel {output_path}"),
|
|
PostExportOpen::Failed(reason) => format!("已导出知乎热榜 Excel {output_path},但自动打开失败:{reason}"),
|
|
}
|
|
```
|
|
|
|
```rust
|
|
match open_local_dashboard(browser_backend, &output_path, &presentation_url) {
|
|
PostExportOpen::Opened => format!("已在浏览器中打开知乎热榜大屏 {output_path}"),
|
|
PostExportOpen::Failed(reason) => format!("已生成知乎热榜大屏 {output_path},但浏览器自动打开失败:{reason}"),
|
|
}
|
|
```
|
|
|
|
Change signatures only as much as needed to pass `browser_backend` into the dashboard route. Do not broaden unrelated call chains.
|
|
|
|
- [ ] **Step 6: Export the helper module**
|
|
|
|
Update `src/compat/mod.rs`:
|
|
|
|
```rust
|
|
pub mod artifact_open;
|
|
```
|
|
|
|
Do not reorder unrelated module exports unless rustfmt does it.
|
|
|
|
- [ ] **Step 7: Run the focused library and workflow regressions to verify green**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_exported_xlsx_with_passes_generated_path_to_launcher --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_exported_xlsx_with_reports_launcher_failure --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_local_dashboard_uses_exact_approved_marker_payload --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_xlsx_export_and_auto_open --test compat_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_screen_export_and_auto_open --test compat_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_reports_dashboard_auto_open_protocol_error_when_presentation_url_is_missing --test compat_runtime_test -- --exact
|
|
```
|
|
|
|
Expected: PASS for the new library tests and the workflow regressions, unless the dashboard-open path still fails at backend/policy validation.
|
|
|
|
- [ ] **Step 8: Commit the compat opener and workflow changes**
|
|
|
|
```bash
|
|
git add src/compat/artifact_open.rs src/compat/mod.rs src/compat/workflow_executor.rs tests/compat_runtime_test.rs
|
|
git commit -m "feat: auto-open zhihu hotlist export artifacts"
|
|
```
|
|
|
|
### Task 3: Add failing backend and security tests for the narrow local-dashboard allowance
|
|
|
|
**Files:**
|
|
- Modify: `src/browser/callback_backend.rs:536-840`
|
|
- Modify: `tests/browser_tool_test.rs` (`default_rules_allow_zhihu_navigation` section plus new local-dashboard validation tests)
|
|
- Reference: `src/security/mac_policy.rs:56-132`
|
|
|
|
- [ ] **Step 1: Add a red callback-backend acceptance test for the approved local-dashboard request shape**
|
|
|
|
Extend the existing `src/browser/callback_backend.rs` test module with one focused navigate test.
|
|
|
|
Target shape:
|
|
|
|
```rust
|
|
#[test]
|
|
fn callback_backend_accepts_approved_local_dashboard_navigate_request() {
|
|
let host = Arc::new(FakeCallbackHost::new(vec![success_reply(json!({ "navigated": true }))]));
|
|
let backend = BrowserCallbackBackend::new(
|
|
host.clone(),
|
|
test_policy(),
|
|
"http://127.0.0.1:17888/sgclaw/browser-helper.html",
|
|
);
|
|
|
|
let output = backend.invoke(
|
|
Action::Navigate,
|
|
json!({
|
|
"url": "file:///C:/tmp/zhihu-hotlist-screen.html",
|
|
"sgclaw_local_dashboard_open": {
|
|
"source": "compat.workflow_executor",
|
|
"kind": "zhihu_hotlist_screen",
|
|
"output_path": "C:/tmp/zhihu-hotlist-screen.html",
|
|
"presentation_url": "file:///C:/tmp/zhihu-hotlist-screen.html"
|
|
}
|
|
}),
|
|
"__sgclaw_local_dashboard__",
|
|
);
|
|
|
|
assert!(output.unwrap().success);
|
|
assert_eq!(host.requests()[0].command, json!([
|
|
"http://127.0.0.1:17888/sgclaw/browser-helper.html",
|
|
"sgBrowerserOpenPage",
|
|
"file:///C:/tmp/zhihu-hotlist-screen.html"
|
|
]));
|
|
}
|
|
```
|
|
|
|
Do not weaken any existing normal-domain tests.
|
|
|
|
- [ ] **Step 2: Add red rejection tests in exact files**
|
|
|
|
Put malformed-request rejection in `src/browser/callback_backend.rs` next to the acceptance test:
|
|
|
|
```rust
|
|
#[test]
|
|
fn callback_backend_rejects_local_dashboard_navigate_without_required_marker_fields() {}
|
|
```
|
|
|
|
Put policy-only validation in `tests/browser_tool_test.rs` so all public `MacPolicy` assertions stay in one place:
|
|
|
|
```rust
|
|
#[test]
|
|
fn mac_policy_rejects_non_html_local_dashboard_presentation() {}
|
|
|
|
#[test]
|
|
fn default_rules_allow_zhihu_navigation() {
|
|
let policy = MacPolicy::load_from_path(...).unwrap();
|
|
policy.validate(&Action::Navigate, "www.zhihu.com").unwrap();
|
|
}
|
|
```
|
|
|
|
Do not create a second `MacPolicy` regression location.
|
|
|
|
- [ ] **Step 3: Run the focused backend/policy tests to verify red**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" callback_backend_accepts_approved_local_dashboard_navigate_request --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" callback_backend_rejects_local_dashboard_navigate_without_required_marker_fields --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" mac_policy_rejects_non_html_local_dashboard_presentation --test browser_tool_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" default_rules_allow_zhihu_navigation --test browser_tool_test -- --exact
|
|
```
|
|
|
|
Expected: the new local-dashboard tests FAIL; `default_rules_allow_zhihu_navigation` should still PASS.
|
|
|
|
- [ ] **Step 4: Commit the red backend/security tests**
|
|
|
|
```bash
|
|
git add src/browser/callback_backend.rs tests/browser_tool_test.rs
|
|
git commit -m "test: lock local dashboard navigate boundary"
|
|
```
|
|
|
|
### Task 4: Implement the narrow callback-backend and MacPolicy allowance
|
|
|
|
**Files:**
|
|
- Modify: `src/browser/callback_backend.rs:300-351`
|
|
- Modify: `src/security/mac_policy.rs:56-132`
|
|
- Maybe modify: `src/security/mod.rs:9-27`
|
|
- Test: `src/browser/callback_backend.rs:536-840`
|
|
- Test: `tests/browser_tool_test.rs` (`default_rules_allow_zhihu_navigation` section plus new local-dashboard validation tests)
|
|
|
|
- [ ] **Step 1: Add a narrow local-dashboard validation helper in `MacPolicy`**
|
|
|
|
Keep `validate(...)` unchanged for ordinary domain flow. Add one small explicit helper instead.
|
|
|
|
Target shape:
|
|
|
|
```rust
|
|
pub fn validate_local_dashboard_presentation(
|
|
&self,
|
|
action: &Action,
|
|
expected_domain: &str,
|
|
presentation_url: &str,
|
|
output_path: &str,
|
|
) -> Result<(), SecurityError> {
|
|
// require Action::Navigate
|
|
// require expected_domain == "__sgclaw_local_dashboard__"
|
|
// require file:// URL
|
|
// require .html path
|
|
// require normalized file URL path matches output_path
|
|
}
|
|
```
|
|
|
|
If you need a new `SecurityError` variant for malformed local-dashboard input, add the smallest one that keeps error text clear.
|
|
|
|
- [ ] **Step 2: Recognize only the exact approved request shape in `BrowserCallbackBackend::invoke(...)`**
|
|
|
|
Before the normal `self.mac_policy.validate(&action, expected_domain)?` path runs, detect the one approved special case.
|
|
|
|
Minimal target behavior:
|
|
|
|
```rust
|
|
if let Some(local_dashboard) = approved_local_dashboard_request(&action, ¶ms, expected_domain) {
|
|
self.mac_policy.validate_local_dashboard_presentation(
|
|
&action,
|
|
expected_domain,
|
|
&local_dashboard.presentation_url,
|
|
&local_dashboard.output_path,
|
|
)?;
|
|
} else {
|
|
self.mac_policy.validate(&action, expected_domain)?;
|
|
}
|
|
```
|
|
|
|
The helper should require all of these fields exactly:
|
|
|
|
```text
|
|
- action == Action::Navigate
|
|
- expected_domain == "__sgclaw_local_dashboard__"
|
|
- params.url exists
|
|
- params.sgclaw_local_dashboard_open.source == "compat.workflow_executor"
|
|
- params.sgclaw_local_dashboard_open.kind == "zhihu_hotlist_screen"
|
|
- params.sgclaw_local_dashboard_open.output_path exists
|
|
- params.sgclaw_local_dashboard_open.presentation_url exists and equals params.url
|
|
```
|
|
|
|
Anything else must continue down the normal rejection path.
|
|
|
|
- [ ] **Step 3: Keep `build_command(Action::Navigate, ...)` simple**
|
|
|
|
Do not add a second browser opcode or change the callback-host runtime contract. The approved local-dashboard case should still flow into the existing navigate command builder so the emitted command stays:
|
|
|
|
```rust
|
|
json!([
|
|
self.helper_page_url,
|
|
"sgBrowerserOpenPage",
|
|
target_url,
|
|
])
|
|
```
|
|
|
|
- [ ] **Step 4: Run the focused backend/security tests to verify green**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" callback_backend_accepts_approved_local_dashboard_navigate_request --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" callback_backend_rejects_local_dashboard_navigate_without_required_marker_fields --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" mac_policy_rejects_non_html_local_dashboard_presentation --test browser_tool_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" default_rules_allow_zhihu_navigation --test browser_tool_test -- --exact
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 5: Re-run the dashboard workflow regression after backend validation lands**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_screen_export_and_auto_open --test compat_runtime_test -- --exact
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 6: Commit the backend/security implementation**
|
|
|
|
```bash
|
|
git add src/browser/callback_backend.rs src/security/mac_policy.rs src/security/mod.rs tests/browser_tool_test.rs tests/compat_runtime_test.rs
|
|
git commit -m "fix: allow approved local dashboard auto-open"
|
|
```
|
|
|
|
If `src/security/mod.rs` did not change, omit it from the commit.
|
|
|
|
### Task 5: Run the focused verification sweep
|
|
|
|
**Files:**
|
|
- Verify: `src/compat/artifact_open.rs`
|
|
- Verify: `tests/compat_runtime_test.rs`
|
|
- Verify: `tests/compat_screen_html_export_tool_test.rs`
|
|
- Verify: `tests/browser_tool_test.rs`
|
|
- Verify: `src/browser/callback_backend.rs` test module
|
|
- Reference only if summary wording ripples outward: `tests/agent_runtime_test.rs:173-258`
|
|
- Reference only if summary wording ripples outward: `tests/service_task_flow_test.rs:704-839`
|
|
- Reference only if summary wording ripples outward: `tests/service_ws_session_test.rs:755-869`
|
|
|
|
- [ ] **Step 1: Re-run the library and workflow regressions**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_exported_xlsx_with_passes_generated_path_to_launcher --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_exported_xlsx_with_reports_launcher_failure --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" open_local_dashboard_uses_exact_approved_marker_payload --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_xlsx_export_and_auto_open --test compat_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_chains_hotlist_skill_into_screen_export_and_auto_open --test compat_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" handle_browser_message_reports_dashboard_auto_open_protocol_error_when_presentation_url_is_missing --test compat_runtime_test -- --exact
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 2: Re-run the tool contract regression that the dashboard route depends on**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" screen_html_export_tool_renders_dashboard_html_with_presentation_contract --test compat_screen_html_export_tool_test -- --exact
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 3: Re-run the callback-backend and policy boundary tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" callback_backend_accepts_approved_local_dashboard_navigate_request --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" callback_backend_rejects_local_dashboard_navigate_without_required_marker_fields --lib -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" mac_policy_rejects_non_html_local_dashboard_presentation --test browser_tool_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" default_rules_allow_zhihu_navigation --test browser_tool_test -- --exact
|
|
```
|
|
|
|
Expected: PASS
|
|
|
|
- [ ] **Step 4: Re-run outward-facing summary regressions only if needed**
|
|
|
|
Only if the updated summary text breaks existing assertions, run exactly these existing regressions and adjust only the affected expectation text:
|
|
|
|
```bash
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" production_submit_task_routes_zhihu_through_ws_backend_without_helper_bootstrap --test agent_runtime_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" client_to_service_regression_routes_zhihu_without_helper_bootstrap_or_invalid_hmac_seed_output --test service_task_flow_test -- --exact
|
|
cargo test --manifest-path "D:/data/ideaSpace/rust/sgClaw/claw-new/Cargo.toml" service_binary_submit_flow_routes_zhihu_without_helper_bootstrap --test service_ws_session_test -- --exact
|
|
```
|
|
|
|
Expected: PASS for any test you had to touch. Skip this step entirely if those files needed no edits.
|
|
|
|
- [ ] **Step 5: Inspect scope before finishing with exact git commands**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
git diff --name-only -- src/compat/artifact_open.rs src/compat/mod.rs src/compat/workflow_executor.rs src/browser/callback_backend.rs src/security/mac_policy.rs src/security/mod.rs tests/compat_runtime_test.rs tests/browser_tool_test.rs tests/agent_runtime_test.rs tests/service_task_flow_test.rs tests/service_ws_session_test.rs
|
|
git diff --stat -- src/compat/artifact_open.rs src/compat/mod.rs src/compat/workflow_executor.rs src/browser/callback_backend.rs src/security/mac_policy.rs src/security/mod.rs tests/compat_runtime_test.rs tests/browser_tool_test.rs tests/agent_runtime_test.rs tests/service_task_flow_test.rs tests/service_ws_session_test.rs
|
|
```
|
|
|
|
Confirm the diff only touches:
|
|
|
|
```text
|
|
- compat workflow/orchestration
|
|
- compat post-export helper module
|
|
- callback backend narrow local-dashboard acceptance
|
|
- MacPolicy narrow local-dashboard validation
|
|
- focused related tests
|
|
```
|
|
|
|
Confirm it does **not** touch:
|
|
|
|
```text
|
|
- frontend/service-console/
|
|
- src/service/protocol.rs
|
|
- browser-helper.html
|
|
- callback-host endpoint contracts
|
|
- websocket transport/protocol files
|
|
```
|
|
|
|
- [ ] **Step 6: Commit only if verification required additional code changes**
|
|
|
|
```bash
|
|
git add src/compat/artifact_open.rs src/compat/mod.rs src/compat/workflow_executor.rs src/browser/callback_backend.rs src/security/mac_policy.rs tests/compat_runtime_test.rs tests/browser_tool_test.rs tests/agent_runtime_test.rs tests/service_task_flow_test.rs tests/service_ws_session_test.rs
|
|
git commit -m "test: tighten hotlist post-export auto-open verification"
|
|
```
|
|
|
|
If verification required no further code changes, do not create an extra commit.
|