Files
deepseek-harness/.agents/notes/implemented/feature/2026-07-29-directory-picker-adaptive-default.md
T
creatixchu 8cec74748b feat(host): adaptive directory-picker default via -auto chooser
Add @deepseek-ai/dsh-host-directory-picker-auto, a node-half-only chooser
that samples the host situation once at boot (bind host via a new
httpServer.host getter, SSH markers, platform, DISPLAY/WAYLAND_DISPLAY)
and mounts the matching dual-face backend (-native or -browse) as a real
Loader entry in the in-memory root tree; the effect disposer removes it.
Entry-level mounting keeps the seam's one-row-swaps-both-faces invariant:
the client module table discovers the mounted backend's browser half
exactly as a config row's. apps/cli now composes -auto as its
directory-picker row; composing a backend row directly remains the pin.
2026-07-29 18:34:06 +08:00

4.5 KiB

Agent Note: Adaptive default for the directory-picker interaction

Status: implemented

English | 中文

Problem

The directory-picker seam made the interaction a cordis.yml swap point, but the shipped composition still had to pin one backend: -browse everywhere meant a local operator never got the OS chooser, -native everywhere breaks every remote deployment. The right default depends on facts only the running host knows — where the server binds, whether the process was launched over SSH, whether a display session exists — so no static row is correct for all deployments.

Decision

A third sibling package, dsh-host-directory-picker-auto: a node-half-only chooser that owns no picking code and no UI. Its apply samples the host facts exactly once at boot — bind host from the injected httpServer (a new host getter mirrors the existing port), SSH_CONNECTION/SSH_TTY, platform, DISPLAY/WAYLAND_DISPLAY — resolves them through one exported pure function, and mounts the chosen dual-face backend with ctx.loader.create({name}) into the Loader's in-memory root tree; the effect's disposer removes the entry again. native requires every attended-host signal (loopback bind ∧ no SSH markers ∧ display session, assumed on darwin/win32); anything ambiguous resolves to browse, which works everywhere. apps/cli now mounts -auto as its directory-picker row; composing -native or -browse directly remains the pin.

Why entry-level mounting is the load-bearing mechanism: the client module table (dsh-client-modules) reconciles Loader entries reactively over internal/plugin, so a backend mounted as a real entry gets its browser half discovered exactly as a config-row's would be — the seam's one-row-swaps-both-faces invariant survives adaptivity with zero duplicated client code. The dev HMR row (AppCLIEntry) is the mechanism precedent. Root-tree targeting matters: the root tree's write() is a no-op, so the resolved row can never be persisted back into cordis.yml (the Include subtree does write).

Alternatives considered

  • Boot-glue resolution in AppCLIEntry (ship both rows with static disabled, patch disabled from a --directory-picker=auto|native|browse flag). Works — PatchOptions patches metadata, and the modules scan skips disabled rows — but leaves the decision app-private where every future composition re-implements it; the chooser plugin gives any cordis.yml the same one-row adaptivity. Reintroduce the flag only when a deployment needs to force a backend without editing its yml.
  • One merged plugin branching per call (client tries pick, falls back to the browse dialog on directory-picker-unavailable). Rejected: the client would need both flows in one bundle — the bundle-purity gate forbids cross-plugin value imports and jscpd forbids copying the dialog — and per-call probing pays a doomed RPC on every open of a browse host.
  • Resurrecting the wire advertisement so both client flows mount and branch on the host's kind. Rejected: reverses the seam note's deletion for no consumer the chooser doesn't already serve, and collides with the single directory-flow holes.
  • Per-connection adaptivity (native for a loopback browser, browse for a remote one, same server). Deferred: needs a per-client capability, the advertisement above, and both flows mounted; no deployment serves both operator shapes at once today.

Consequences

  • The shipped web GUI adapts out of the box: attended local host → OS chooser; SSH launch, all-interfaces bind, or headless host → in-app browser. Detection is a heuristic (a detached tmux session loses SSH_*; a non-Aqua darwin process still counts as displayed) — a wrong native choice degrades to the backend's existing retryable failure dialog, and composing -browse pins the safe interaction.
  • One resolution per boot keeps the seam's capability-stability contract; per-connection shapes remain out of scope until a deployment demands them.
  • Mounting the chooser and a backend row together fails loud (duplicate directoryPicker service; duplicate flow in the single holes).
  • The host typecheck aggregate now references the two backend projects (declarations only, node entries carry no client merge) so the chooser's REAL-composition test can mount them — the mirror of the client aggregate's webserver reference.