Sidebar session list grows the figma 239-10458 feature set and the
workspace/session browsing region moves wholesale into ui-workspace:
- Group-by menu (WorkSpace / In one list): flat mode lists every session
top-level, strictly newest-first; the choice persists across reloads.
- Session rows get a 500ms hover detail card (title / relative time /
status line) and a ... menu (Rename / Fork session / Delete session,
visual-only for now); workspace headers get ... with Rename (wired) and
Delete workspace (visual-only).
- workspace.rename RPC: trims, rejects duplicate titles on the create
chain (workspace-name-conflict), no-op on same title; modal dialog with
client-side duplicate pre-check.
- workspace.insertSessionBefore RPC (DOM-insertBefore semantics, omitted
anchor appends): HTML5 drag reorder of root sessions inside a workspace
group; order truth stays host-side, the view refreshes from the
response/changed frame.
- Activity pinning removed: the session/event touchSession chain is gone;
workspace accounts are manually owned (new sessions prepend, explicit
reordering only). Contracts and tests updated, api catalog regenerated.
- ui-sidebar reduced to the column shell (brand, fold state machine, New
Session, Settings) exposing one sidebar.workspaces hole with a two-fact
owner share {wide, expandSidebar}; ui-workspace owns the whole region
(header, search, grouped/flat lists, dialogs, drag) plus the picker via
a shared WorkspaceCreateFlow. The old sidebar.workspace picker slot and
its deferral indirection are gone.
- ui-primitives: Menu gains label entries, danger rows, and
closeOnPointerLeave; new HoverCard (portaled, open-delay, disabled
guard). Hover card and row menu never coexist.
@deepseek-ai/dsh-host-apiproxy
The API gateway every client shape shares: the TS contract (src/api/, zero Node dependencies, importable from the browser), the fetch carrier pair (src/fetch/: toFetchHandler on the host side, AbstractApiClient plus platform subclasses on the client side), and the host-side implementation (src/api-proxy.ts: createApiProxy plus the default-exported ApiProxyService gateway plugin — config {provider, model, workspaceRoot?}, provides ctx.apiProxy). Transport-agnostic by design: this package registers no routes; carriers (HTTP today, IPC later) wrap ctx.apiProxy themselves. The shipped core composition lives in apps/cli/cordis.yml.
Contract layer (/api)
Wire messages form a four-quadrant discriminated union — who initiates × request/response — decoupled from the physical channel: ClientRequest (POST /api/<method> body), ServerResponse (that POST's response body), ServerRequest (SSE frame), ClientResponse (POST /api/respond body). Responses always echo the matching request's rpcId and never mint a new one. Method parameter/return structures live only in the domain interface signatures (SessionsApi, HostApi, EventsApi); RpcMethodMap registers the methods and every other position derives via RequestPayload<K>/ResponseValue<K>. Zod schemas anchor satisfies z.ZodType<Wire<T>> and parse at two levels: envelope first, business payload second, dispatched per method. Business errors ride RpcResult's error branch (RpcErrorDetailsMap closes the code set); HTTP status expresses only the carrier.
The layering/protocol decisions are recorded in the GUI layering and RPC protocol RFC; the browser-side consumption architecture in the web client architecture RFC.
The mux stream projects the latest log-backed title as a validated session/title control frame after each attached-session subscription baseline and immediately after the corresponding live raw title event. This projection does not add titles to session.list; cold sessions remain metadata-only there until opening or resuming attaches their logs.
Workspace and Session lists are separate reconnect baselines. workspace.create creates a unique name or adopts an existing directory, session.create accepts an optional preallocated Session id, and host/workspace-changed plus host/session-added carry committed increments in either arrival order. Frontend Workspace and Session Intents are client-only and have no wire method.
Carrier layer (/client + root)
AbstractApiClient holds every protocol invariant — rpcId minting, envelope wrap/unwrap, zod parsing, SSE frame decoding, unary timeout, microtask-batched envelope observation (subscribeEnvelopes) — while platform subclasses supply only the doFetch transport aspect. InProcessApiClient over toFetchHandler(api) is the isomorphic point: the full wire serialization/validation path with no network, used by dsh -p headless.
Model Experience
None, as the package defines the client↔host wire contract and carriers; nothing here reaches a model request.
KV Cache effect
None; this package neither assembles nor sends a provider request.
Known Limitations and Deferred Work
respondrouting is shipped, but pending-interaction state is host-side work — the wire shape (POST/api/respond,RpcReceipt) is final; the pending table that makes late/duplicate answers meaningful lives insrc/api-proxy.tsand is still minimal (questions only, no approvals).- Reserved seams stay out of
RpcMethodMap—session.fork,prompt.mode: 'inject',task.list,host.listModels, and a describehostInstanceIdare documented reservations; an unknown method fails loud at envelope parse rather than getting a not-implemented code. - No protocol version field — client and host ship together;
host.describegains a version negotiation field only when an independently released client exists.