Files
claw/docs/superpowers/specs/2026-04-06-zhihu-hotlist-post-export-auto-open-design.md
木炎 bdf8e12246 feat: align browser callback runtime and export flows
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>
2026-04-06 21:44:53 +08:00

17 KiB

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.