fix(session-export): preserve streamed browser downloads

This commit is contained in:
NI0317
2026-08-12 18:44:04 +08:00
parent 8c7dab8755
commit 785f41daed
35 changed files with 226 additions and 136 deletions
@@ -10,13 +10,13 @@ Session export needs a stable Session-level visible action and an equivalent sla
## Decision
`@deepseek-ai/dsh-session-export` registers a Web-only `/export` human command and provides the browser `ctx.sessionExport` controller. The command records an ordinary `command/run` and `command/done`; after `command.execute` returns a successful result, `dsh-client-ui-command` emits a local acknowledgment that asks this browser's controller to download ApiProxy's existing `GET /api/session.export` ZIP. Other clients render the broadcast command nodes without repeating the browser side effect. The 111×32 `Session log` capsule in the Session Header calls that controller directly. Both paths therefore use the same Host endpoint, browser save operation, in-flight state, error handling, and Modal.
`@deepseek-ai/dsh-session-export` registers a Web-only `/export` human command and provides the browser `ctx.sessionExport` controller. The command records an ordinary `command/run` and `command/done`; after `command.execute` returns a successful result, `dsh-client-ui-command` emits a local acknowledgment that asks this browser's controller to download ApiProxy's existing `GET /api/session.export` ZIP. Other clients render the broadcast command nodes without repeating the browser side effect. The 111×32 `Session log` capsule in the Session Header calls that controller directly. Both paths use a `HEAD` preflight for preparation errors, then hand the GET URL to the browser download manager so JavaScript never buffers the ZIP; they share the same in-flight state and Modal.
The Header contribution occupies the right-aligned `conversation.session.header.utilities` list and renders the `Session log` text capsule with its trailing download icon plus the shared Modal. The title-adjacent `conversation.session.header.actions` list continues to own mode, Subagent, and Task entries, so mounting Session export does not reorder or move them. The export contribution does not observe Session history. A per-Session controller collapses concurrent gestures, aborts active fetches when its plugin disposes, ignores late requests after disposal, and preserves a user's closed state when the request later completes.
The Header contribution occupies the right-aligned `conversation.session.header.utilities` list and renders the `Session log` text capsule with its trailing download icon plus the shared Modal. The title-adjacent `conversation.session.header.actions` list continues to own mode, Subagent, and Task entries, so mounting Session export does not reorder or move them. The export contribution does not observe Session history. A per-Session controller collapses concurrent gestures, aborts active preflights when its plugin disposes, ignores late requests after disposal, and preserves a user's closed state when the request later completes.
The ZIP endpoint and persistence `readRaw` capability remain owned by `dsh-host-apiproxy` and the persistence package. The endpoint flushes a live root Session before reading its artifact, so the local acknowledgment cannot race ahead of durable command lifecycle rows. This package does not serialize Session events, write Host files, deliver Host paths, or implement SQLite fallback.
The package compiles its Host command and invariant through `tsconfig.host.json`, while `tsconfig.client.json` owns the browser controller, Header action, Modal, and their face-named tests. The repository Host and Client aggregates reference only the matching project, so their incompatible Cordis `Context` merges never enter one TypeScript program.
The package is an ordinary Client aggregate project. Its single `tsconfig.json` compiles the Node loader entries and browser contribution together; Host-side tests still exercise the command and invariant through their source entries.
## Alternatives considered
@@ -28,4 +28,4 @@ The package compiles its Host command and invariant through `tsconfig.host.json`
## Consequences
The Header action and `/export` download the same ZIP and show the same feedback. An executed command remains visible in the durable transcript without creating a model turn. Deployments whose persistence backend has no raw per-Session artifact receive the endpoint's existing failure; SQLite support remains separate work. Command availability before a Session's first turn is separate work.
The Header action and `/export` download the same ZIP and show the same feedback. An executed command remains visible in the durable transcript without creating a model turn. The preflight reports failures found before streaming starts; failures while the browser consumes the GET remain browser-download failures. Deployments whose persistence backend has no raw per-Session artifact receive the endpoint's existing failure; SQLite support remains separate work. Command availability before a Session's first turn is separate work.