Files
deepseek-harness/packages/client/connection
Yichen Jiang 6758da87ae feat(web): choose the default agent preset from General settings
One settings row picking which preset new sessions are composed from.

It is deliberately a new-session preference, not a live switch: a session's
preset is fixed at creation and the host refuses to adopt an existing session
under a different one, so the row says "applies to sessions you start from now
on" rather than implying it can retune a running agent.

Options and the current value come from one `agentPreset.list` call — the
roster already reports which id an unspecified session gets, so the row needs
no settings-schema introspection, unlike the permission row it is modelled on.
The write targets only the namespace's `default` field.

The menu marks `user` rows: a locally authored preset is exactly as privileged
as the plugins it names, and presenting it identically to a shipped one would
hide that.

An empty roster reads as `unavailable` and renders nothing, because composing
no presets is a valid deployment rather than a failure — distinct from a
roster call that failed, which surfaces its message.
2026-08-07 00:38:11 +08:00
..

@deepseek-ai/dsh-client-connection

English | 中文

Wire consumer layer: the client plugin's apply mounts ctx.connection (shared api client + current-page loopback state + single-consumer stream-loop starter); the export face carries the wire contract types, the AbstractApiClient seam, and the loop's sink/config types. The browser carrier uses HTTP POST for unary and respond operations and opens one downlink-only WebSocket each for events.mux and events.host; the in-process carrier satisfies the same two-stream abstraction. Loopback hostname classification stays package-internal: the /api Host fence and WebSocket upgrades use it directly, while other client plugins consume the derived ctx.connection.isLoopback state. The node half's /api route pins the privileged method set (host.pickDirectory, host.openPath, and the whole configuration plane — settings.describe/openDocument/update/replace/mutate and credentials.describe/set/unset; reads and native actions included, since describing returns the exposed configuration, opening acts on the Host desktop, and probing an arbitrary reference reports where a credential comes from) to loopback by passing the trust fence with an empty trust list — a declared trustedHosts authority reaches every other method, while these stay loopback-local until a real authentication layer exists. The platform carriers and ConnectionController loop are package-internal; apply selects and drives them. The downlink boundary is documented in the WebSocket downlink carrier Agent Note; the protocol contract is api-contracts v3 §3.

/api browser-trust fence

The node half guards every entry under /api before bridging or upgrading (src/api-request-trust.ts). Every request — browser-marked or not — must present a Host that is a loopback authority or matches a trustedHosts entry: exact on host:port entries, any port on port-less entries, both sides compared through WHATWG normalization (DNS-rebinding defense). There is deliberately no shortcut for unmarked HTTP requests: over plain HTTP a browser attaches neither Origin nor Fetch-Metadata to image and navigation reads, so an unmarked request may still be a rebound browser read with a readable response, and Host is the one header rebinding cannot forge; a browser WebSocket handshake carries Origin and passes the same comparison. Non-browser clients pass the same fence via loopback, the CLI-derived LAN IP literals, or a declared authority. When markers are present, an attached Origin must equal the Host authority, and an explicit sec-fetch-site: cross-site marker is refused. A trustedHosts entry that is not a bare, canonical host[:port] authority — one WHATWG parsing reads back exactly as written — fails the plugin load loudly: parsing would otherwise quietly authorize the hostname inside harness.internal/path, or broaden a dangling-colon or zero-padded port to an any-port grant. HTTP failures answer plain 403 before any RPC dispatch; upgrade failures reject the handshake before any event stream starts. A non-loopback (--host 0.0.0.0) deployment therefore needs its serving authorities trusted: the dsh CLI derives the machine's LAN IP literals itself and its --trusted-host flag declares named ones, so trustedHosts in cordis.yml is for compositions the CLI does not boot. The fence is a reachability policy, not authentication; the Web carrier provides no authentication layer. Decision record: the api browser-trust boundary Agent Note.

/api/events.mux and /api/events.host each accept a WebSocket upgrade and send only the corresponding ServerRequest text messages to the browser; the client sends no application data over these sockets. If either socket ends, the current connection generation fails and rebuilds both streams; readiness still requires both sockets to be open and the host.describe HTTP call to succeed. Host teardown terminates both sockets, aborts their sources, and waits for source cleanup before returning. Ordinary network GETs to these paths return 426 with no SSE fallback; toFetchHandler's SSE codec serves only the isomorphic in-process carrier.

Model Experience

None, as the wire consumer layer moves already-composed messages between browser and host; nothing here reaches a model request.

KV Cache effect

None; this package neither assembles nor sends a provider request.

Known Limitations and Deferred Work

  • History resumes an unattached session — opening history may create the host-side agent and add latency to the first open; there is no persistence-only read path.