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>
374 lines
17 KiB
Markdown
374 lines
17 KiB
Markdown
# Zhihu Hotlist Post-Export Auto-Open Design
|
|
|
|
## Background
|
|
|
|
The current Zhihu hotlist workflows already support two separate artifact outputs:
|
|
|
|
- `openxml_office` generates a local `.xlsx` file for hotlist export
|
|
- `screen_html_export` generates a local `.html` dashboard for presentation
|
|
|
|
Today, the workflow stops after artifact generation and returns a summary string such as:
|
|
|
|
- `已导出知乎热榜 Excel <path>`
|
|
- `已生成知乎热榜大屏 <path>`
|
|
|
|
That means the user still has to manually open the generated file.
|
|
|
|
The user wants one additional post-export action, but only one at a time:
|
|
|
|
1. for Excel-oriented tasks, automatically open the generated `.xlsx` with the system default spreadsheet application
|
|
2. for dashboard-oriented tasks, automatically open the generated local dashboard HTML inside the running sgBrowser session
|
|
|
|
This is an exclusive choice, not a combined mode.
|
|
|
|
## Current Runtime Facts
|
|
|
|
The implementation must match the current browser/runtime boundary that already exists in the repo:
|
|
|
|
- the active service submit path in `src/service/server.rs` constructs `BrowserCallbackBackend`
|
|
- `BrowserCallbackBackend::invoke(Action::Navigate, ...)` currently emits `sgBrowerserOpenPage`, which opens a new visible browser tab and keeps the helper page alive
|
|
- `WsBrowserBackend::invoke(Action::Navigate, ...)` has different semantics and a different transport path from the callback-host service path
|
|
- `MacPolicy::validate(...)` currently rejects empty or non-domain values, so a raw `file://...` navigation cannot pass through the normal domain validation path today
|
|
- `screen_html_export` already returns `presentation.url`, which is the existing `file://` presentation URL contract for the generated dashboard
|
|
|
|
Those facts mean the design must not promise "replace the helper page" or "reuse identical tab behavior across all backends". The required success path for this slice is narrower: open the generated dashboard automatically in the current callback-host-backed sgBrowser service session without adding a new user-facing surface.
|
|
|
|
## Problem Statement
|
|
|
|
The existing workflow logic in `src/compat/workflow_executor.rs` already separates hotlist export from dashboard generation, but it treats both routes as artifact-only flows. The last mile is missing:
|
|
|
|
- the Excel route does not auto-open the generated file
|
|
- the dashboard route does not consume the generated dashboard presentation URL and open it automatically in the browser runtime
|
|
|
|
The risk is scope drift. This change must not:
|
|
|
|
- turn Excel-open and dashboard-open into a combined workflow
|
|
- add new help/help-like user-visible surfaces
|
|
- move orchestration into `frontend/service-console/`
|
|
- modify the websocket protocol
|
|
- modify `browser-helper.html`
|
|
- modify callback-host HTTP endpoints or their contracts
|
|
- change the artifact-generation contract of `openxml_office` or `screen_html_export`
|
|
|
|
## Goal
|
|
|
|
Extend the existing Zhihu hotlist post-export behavior so that:
|
|
|
|
- Excel tasks generate `.xlsx` and then auto-open it with the local system default spreadsheet application
|
|
- dashboard tasks generate `.html` and then auto-open that generated dashboard inside sgBrowser
|
|
|
|
On the current callback-host service path, "inside sgBrowser" means opening the generated dashboard in a new visible browser tab while the helper page stays alive. The user does not need to open the file manually.
|
|
|
|
## Non-goals
|
|
|
|
This slice does not include:
|
|
|
|
- opening Excel and dashboard in the same run
|
|
- adding a new combined route that auto-opens both artifacts
|
|
- adding any new help, helper, or user-visible assistance surface
|
|
- modifying `frontend/service-console/sg_claw_service_console.html`
|
|
- modifying `src/service/protocol.rs`
|
|
- modifying `browser-helper.html`
|
|
- modifying `/sgclaw/callback/*` contracts
|
|
- turning the browser backend into a general-purpose local filesystem browser
|
|
- changing the artifact-generation JSON contract of `openxml_office` or `screen_html_export`
|
|
|
|
## Chosen Approach
|
|
|
|
Keep the current two workflow routes, but add one route-specific post-export action to each:
|
|
|
|
- `ZhihuHotlistExportXlsx` -> generate `.xlsx`, then open it locally with the OS default app
|
|
- `ZhihuHotlistScreen` -> generate `.html`, then open the generated dashboard presentation URL in the browser runtime
|
|
|
|
For the dashboard route, use the existing `presentation.url` returned by `screen_html_export` as the authoritative browser-open URL. Do not invent a separate normal-path URL conversion layer when the tool already returns the presentation contract.
|
|
|
|
The compat opener must emit one exact navigate request shape for this case.
|
|
|
|
- `action`: `Action::Navigate`
|
|
- `expected_domain`: the exact literal `__sgclaw_local_dashboard__`
|
|
- `params.url`: the exact `presentation.url` returned by `screen_html_export`
|
|
- `params.sgclaw_local_dashboard_open.source`: the exact literal `compat.workflow_executor`
|
|
- `params.sgclaw_local_dashboard_open.kind`: the exact literal `zhihu_hotlist_screen`
|
|
- `params.sgclaw_local_dashboard_open.output_path`: the generated local dashboard artifact path
|
|
- `params.sgclaw_local_dashboard_open.presentation_url`: the same `file://` URL stored in `params.url`
|
|
|
|
On the current callback-host-backed service path, only that exact request shape is approved for the local-dashboard special case. A plain `Action::Navigate` with an arbitrary `file://...` URL, or a request missing any one of the required marker fields above, must continue to be rejected.
|
|
|
|
Because normal `MacPolicy` domain validation cannot accept `file://...`, add a narrow local-dashboard presentation allowance in the browser backend/security boundary. That allowance must be limited to this one case:
|
|
|
|
- only for `Action::Navigate`
|
|
- only for generated local dashboard presentation URLs
|
|
- only for local HTML presentation, not arbitrary local paths or generic file browsing
|
|
|
|
Why this approach:
|
|
|
|
- it preserves the existing mutual exclusivity between Excel export and dashboard presentation
|
|
- it keeps artifact generation in the existing tools
|
|
- it keeps browser opening inside the existing browser backend boundary
|
|
- it uses the existing `screen_html_export` presentation contract instead of duplicating it
|
|
- it avoids pushing orchestration into the service console or protocol layer
|
|
- it stays compatible with the current callback-host runtime, where visible navigation is new-tab based
|
|
- it limits the guaranteed browser-open behavior in this slice to the callback-host-backed service path that the user is using today
|
|
|
|
Rejected alternatives:
|
|
|
|
- add a combined "Excel + dashboard" route: explicitly rejected by user behavior
|
|
- let `frontend/service-console/` decide when to open generated files: wrong layer; the console is only a submit/view surface
|
|
- add help UI to expose output choices: explicitly unwanted by the user
|
|
- change `browser-helper.html` so the helper page itself becomes the dashboard: this would break the current helper-page persistence model
|
|
- promise a backend-agnostic "replace the current page" behavior: inaccurate because callback-host and websocket backends do not share identical navigate semantics
|
|
- require the websocket backend to gain matching local-dashboard visible-open behavior in this slice: outside the narrow current-service-path goal
|
|
|
|
## File Responsibilities
|
|
|
|
### `src/compat/workflow_executor.rs`
|
|
|
|
Continue to own:
|
|
|
|
- route detection for Zhihu hotlist workflows
|
|
- artifact generation orchestration
|
|
- post-export summary construction
|
|
|
|
New responsibilities in this slice:
|
|
|
|
- parse the successful artifact payloads after `openxml_office` and `screen_html_export`
|
|
- call the route-specific post-export opener only after artifact creation succeeds
|
|
- for the dashboard route, consume `presentation.url` from the `screen_html_export` result payload
|
|
- keep generation success and post-export open success/failure distinct in the returned summary
|
|
|
|
### `src/compat/artifact_open.rs`
|
|
|
|
New helper module to keep side effects out of `workflow_executor.rs`.
|
|
|
|
Responsibilities:
|
|
|
|
- open a generated local `.xlsx` with the system default application
|
|
- open a generated local dashboard presentation URL through the existing `BrowserBackend`
|
|
- construct the exact approved dashboard navigate request shape used by this slice
|
|
- define the narrow local-dashboard presentation token/constants used by the compat layer and backend compatibility path
|
|
- return narrow success/failure results so `workflow_executor.rs` can produce accurate summaries
|
|
|
|
This module must stay small and focused. It is not a general launcher framework.
|
|
|
|
### `src/browser/callback_backend.rs`
|
|
|
|
New narrow responsibility in this slice:
|
|
|
|
- at the `BrowserCallbackBackend::invoke(Action::Navigate, params, expected_domain)` entrypoint, recognize only the exact approved local-dashboard presentation request shape
|
|
- preserve the current callback-host behavior of using `sgBrowerserOpenPage`, which opens a new visible tab and keeps the helper page alive
|
|
- reject local-file navigate attempts that do not include the exact post-export marker payload from the compat layer
|
|
|
|
This slice must not change callback-host polling, helper bootstrap, or callback endpoint behavior.
|
|
|
|
### `src/browser/ws_backend.rs`
|
|
|
|
No required behavior change in this slice.
|
|
|
|
Notes:
|
|
|
|
- websocket transport semantics differ from the callback-host service path
|
|
- this spec does not require websocket backend local-dashboard visible-open support
|
|
- websocket-specific parity can be designed later as a separate slice if needed
|
|
|
|
### `src/security/mac_policy.rs`
|
|
|
|
New narrow responsibility in this slice:
|
|
|
|
- expose a small validation helper for the approved local-dashboard presentation case
|
|
- validate the real local presentation URL and artifact path for that case rather than treating `file://` as a normal allowed domain
|
|
- keep the normal domain-based validation path unchanged for ordinary remote navigation
|
|
|
|
The policy layer must not turn `file://` into a generally allowed "domain". This is an explicit special case for generated local dashboard presentation only.
|
|
|
|
### `src/compat/mod.rs`
|
|
|
|
Expose the new helper module.
|
|
|
|
## Route Semantics
|
|
|
|
### Excel export route
|
|
|
|
Trigger examples:
|
|
|
|
- `读取知乎热榜数据,并导出 excel 文件`
|
|
- `导出知乎热榜 xlsx`
|
|
|
|
Expected behavior:
|
|
|
|
1. collect hotlist rows
|
|
2. call `openxml_office`
|
|
3. obtain `output_path`
|
|
4. open the generated `.xlsx` using the local OS default spreadsheet application
|
|
5. return a success summary reflecting both generation and open state
|
|
|
|
Summary rules:
|
|
|
|
- open succeeded -> `已导出并打开知乎热榜 Excel <path>`
|
|
- open failed but file exists -> `已导出知乎热榜 Excel <path>,但自动打开失败:<reason>`
|
|
|
|
The workflow still counts artifact generation as successful even if the post-export open step fails.
|
|
|
|
### Dashboard route
|
|
|
|
Trigger examples:
|
|
|
|
- `读取知乎热榜数据并生成领导演示大屏`
|
|
- `生成知乎热榜 dashboard`
|
|
- `展示知乎热榜大屏`
|
|
|
|
Expected behavior:
|
|
|
|
1. collect hotlist rows
|
|
2. call `screen_html_export`
|
|
3. obtain `output_path`
|
|
4. obtain `presentation.url` from the tool result payload
|
|
5. invoke the browser opener through the existing `BrowserBackend`
|
|
6. return a success summary reflecting both generation and browser-open state
|
|
|
|
Summary rules:
|
|
|
|
- browser open succeeded -> `已在浏览器中打开知乎热榜大屏 <path>`
|
|
- browser open failed but file exists -> `已生成知乎热榜大屏 <path>,但浏览器自动打开失败:<reason>`
|
|
|
|
The workflow still counts artifact generation as successful even if the browser-open step fails.
|
|
|
|
## Browser Boundary
|
|
|
|
This slice must preserve the current browser/runtime boundary.
|
|
|
|
Allowed:
|
|
|
|
- use the existing `BrowserBackend`
|
|
- use the existing `Action::Navigate`
|
|
- use the existing `screen_html_export` `presentation.url`
|
|
- add a narrow compatibility path so local generated dashboard presentation can pass backend validation
|
|
|
|
Not allowed:
|
|
|
|
- change `browser-helper.html`
|
|
- introduce a new callback-host endpoint
|
|
- move file-opening responsibility into the frontend service console
|
|
- add a new browser-side bootstrap flow
|
|
- require websocket protocol changes
|
|
|
|
Important semantic note:
|
|
|
|
- on the current service callback-host path, dashboard open is expected to use `sgBrowerserOpenPage`, so the generated dashboard appears in a new visible browser tab while the helper page remains available for later tasks
|
|
- websocket-backed browser execution may continue to differ; this slice does not require matching visible-open semantics there
|
|
|
|
## Local Dashboard Presentation Allowance
|
|
|
|
The local dashboard browser-open path needs an explicit narrow validation rule because `file://...` cannot pass the normal domain allowlist.
|
|
|
|
Requirements for the narrow allowance:
|
|
|
|
- only approved for `Action::Navigate`
|
|
- only approved for the exact compat marker payload described above
|
|
- only approved for generated local dashboard presentation URLs
|
|
- only approved when the validated local artifact path points to the generated dashboard HTML artifact returned by the same `screen_html_export` success payload
|
|
- only approved for local HTML presentation, not arbitrary executables or unrelated local files
|
|
- ordinary remote navigation must continue using the existing `MacPolicy::validate(...)` domain rules unchanged
|
|
|
|
This keeps the behavior small and auditable while still satisfying the user-visible dashboard auto-open requirement.
|
|
|
|
## Local File Opening Boundary
|
|
|
|
The Excel auto-open action is a local runtime side effect, not a browser action.
|
|
|
|
Requirements:
|
|
|
|
- use the system default application for `.xlsx`
|
|
- support the current Windows environment first
|
|
- keep the implementation minimal and focused on the generated artifact path
|
|
|
|
Not required in this slice:
|
|
|
|
- a cross-platform abstraction beyond the minimal shape needed for the current repo environment
|
|
- opening arbitrary user-selected files
|
|
- exposing local file opening to the service websocket protocol
|
|
|
|
## Error Handling
|
|
|
|
### Excel route
|
|
|
|
If `.xlsx` generation fails:
|
|
|
|
- return the existing export failure
|
|
|
|
If `.xlsx` generation succeeds but auto-open fails:
|
|
|
|
- keep the artifact path in the summary
|
|
- mark only the auto-open step as failed
|
|
- do not delete the generated file
|
|
|
|
### Dashboard route
|
|
|
|
If `.html` generation fails:
|
|
|
|
- return the existing screen export failure
|
|
|
|
If `.html` generation succeeds but browser open fails:
|
|
|
|
- keep the artifact path in the summary
|
|
- mark only the browser-open step as failed
|
|
- do not delete the generated file
|
|
|
|
If the tool result is missing `presentation.url`:
|
|
|
|
- treat that as a protocol error in the post-export open step for this route
|
|
- keep the generated artifact path in the summary if it is available
|
|
- do not silently invent a different contract in the normal path
|
|
|
|
## Test Strategy
|
|
|
|
### Workflow tests
|
|
|
|
Update or add focused workflow coverage so that:
|
|
|
|
- Excel workflow still calls `openxml_office`
|
|
- dashboard workflow still calls `screen_html_export`
|
|
- the two routes remain mutually exclusive
|
|
- dashboard workflow consumes the tool's existing `presentation.url`
|
|
|
|
### New Excel post-export test
|
|
|
|
Add a focused regression proving:
|
|
|
|
- an Excel-oriented hotlist request triggers export
|
|
- the generated `.xlsx` path is passed into the local default-app opener
|
|
- no browser dashboard navigate is triggered for that route
|
|
|
|
### New dashboard post-export test
|
|
|
|
Add a focused regression proving:
|
|
|
|
- a dashboard-oriented hotlist request triggers HTML generation
|
|
- the generated tool payload `presentation.url` is used for browser open
|
|
- the browser backend receives a local-dashboard navigate request through the approved compat path
|
|
- no local spreadsheet opener is triggered for that route
|
|
|
|
### Backend/security compatibility tests
|
|
|
|
Add focused regressions proving:
|
|
|
|
- callback backend accepts the approved local-dashboard navigate case and still emits `sgBrowerserOpenPage`
|
|
- the narrow local-dashboard allowance rejects non-local or malformed URLs
|
|
- ordinary domain validation behavior remains unchanged for normal remote navigation
|
|
|
|
### Existing boundary tests remain unchanged
|
|
|
|
Do not change the service-console boundary guard. This slice is runtime behavior only.
|
|
|
|
## Acceptance Criteria
|
|
|
|
The slice is complete when all of the following are true:
|
|
|
|
1. Excel hotlist export still generates a local `.xlsx` artifact.
|
|
2. Excel hotlist export auto-opens that `.xlsx` with the system default spreadsheet application.
|
|
3. Dashboard hotlist export still generates a local `.html` artifact.
|
|
4. Dashboard hotlist export consumes the existing `screen_html_export` `presentation.url` and auto-opens it in the current callback-host-backed sgBrowser service session.
|
|
5. On the current callback-host service path, the dashboard opens automatically in a visible browser tab without breaking the helper-page runtime.
|
|
6. Excel-open and dashboard-open remain separate user-chosen flows, not a combined mode.
|
|
7. No new help/help-like user-visible surface is added.
|
|
8. The service console, websocket protocol, `browser-helper.html`, and callback-host endpoint surface remain untouched.
|