Files
deepseek-harness/packages/client/ui-conversation
Yichen Jiang 2f157dbd76 Merge remote-tracking branch 'origin/master' into worktree/web-textarea-refactor-991614
# Conflicts:
#	packages/client/ui-chat/src/client/chat/MessageItem.module.css
#	packages/client/ui-conversation/package.json
#	packages/client/ui-input-trigger/README.i18n.yaml
#	packages/client/ui-input-trigger/README.md
#	packages/client/ui-input-trigger/README.zh.md
#	packages/client/ui-reference/README.i18n.yaml
#	packages/client/ui-reference/README.md
#	packages/client/ui-reference/README.zh.md
#	pnpm-lock.yaml
2026-08-26 10:10:09 +08:00
..

description, kind
description kind
Target-neutral conversation assembly and browser shell: event and view registries, per-session bindings, input state, slots, and temporary composer takeovers. package-reference

@deepseek-ai/dsh-client-ui-conversation

English | 中文

Summary

ui-conversation owns target-neutral Conversation assembly and the shared browser shell. It consumes Session Controller SessionEventLikeEntry feeds, exposes React-free registries and per-Session bindings through ctx.uiConversation, and contributes the useConversation, useInput, and inputActions standard props through ctx.uiSession. It also owns the per-session durable image URL cache: ctx.uiConversation.imageUrl(sessionId, attachment) resolves one session-authorized browser URL per attachment and revokes it with the Session binding, so every Conversation target shares one session.attachment read. Concrete targets such as Chat are separate packages that register their own Definitions, snapshot builders, Views, and renderers.

Table of Contents


Conversation assembly

UiConversation.events is the single registry for event Definitions, and UiConversation.views is the single registry for target snapshot builders. Both registries reject duplicate keys, preserve registration order, return idempotent disposers, and rebuild existing bindings when their contribution roster changes. UiConversation.binding(bindingOrSessionId) returns one identity-stable Conversation binding for the current Session Controller binding. It does not open another event source.

The adapter passes each SessionEventLikeEntry directly to the assembler. Its outer type distinguishes scalar and packed records, while its inner event always exposes type, seq, time, and data; Definitions receive that inner SessionEventLike. Historical replace and prepend accept both entry variants, while live append accepts only SessionLiveEventEntry. Every Definition uses the same match and update methods for both event forms, while start receives only a standard event and the assembler rejects a packed start. Definitions that do not consume Assistant deltas return null for the packed tags. Replacement windows and revision gaps rebuild from the complete loaded window; contiguous append and prepend revisions use incremental assembly without expanding packed members. The assembler owns Context matching, Turn/Step locations, target node materialization, target activity, and stable target sources. ConversationSnapshot contains only target-neutral views and active-target facts; Session lifecycle state remains in SessionSnapshot.

Target packages declaration-merge their snapshot and Location data maps, then register with ctx.uiConversation.events.register(...) and ctx.uiConversation.views.register(...). A target reads its Session-owned source with ctx.uiConversation.binding(binding).target(targetId). Registrations are Cordis effects and their returned disposers remove the contribution from the same registry.

Shell and standard props

The package registers the optional-Session conversation shell, strict Session header/body entries, View list, composer chain and bar, input regions, Hero regions, queue dock, draft persistence, and phase calculation. ctx.uiSession.provide() materializes the Conversation and input sources from the same Session binding and supplies inputActions as a stable standard prop.

View selection is deterministic: a registered persisted selection wins, otherwise registered chat wins, otherwise no View renders. It never chooses the first registered View. Shell phase combines Session lifecycle with the active-target set; no target-specific snapshot is read by the shell.

The resident composer survives no-Session and Session transitions. The no-Session state keeps the same composer surface mounted but inert while the Workspace picker connects a blank Session. The surface is a shell-owned Lexical editor: reference chips are atomic decorator nodes carrying the owner's serialization identity (submission expands them through the owner codec), claimed slash commands stay styled leading text, folder text references carry the folder glyph as an icon prefix, and the draft's clipboard projection is mirrored into the per-Session Conversation store. Queue operations address exact queue occurrences through the scoped ctx.conversation service; queue previews render sent text through the shared inline reference projection from ui-primitives (wire session forms fold to their label), while an edit exposes the literal sent text. Busy Enter behavior is stored in the Host-backed ui-conversation settings namespace.

While a normal composer is running, its primary pointer action remains Stop when the draft is empty or input is unavailable. Actionable text or attachments switch the same seat to Queue Send; clearing or successfully submitting the draft restores Stop. The busy-Enter setting continues to select the Queue or Steer keyboard action. Continuable subagents keep separate Send and Stop actions (decision).

Temporary composer entries

conversation.composer is a generic chain. Its complete owner currency is:

/** Owner values used to elect a composer takeover. */
interface ComposerChainProps {
  /** Current Session identity used by temporary business-owned entries. */
  sessionId: SessionId | undefined
  /** Current Session lifecycle state, absent without a selected Session. */
  session: SessionSnapshot | undefined
  /** Effective business-owned interaction awaiting the user in this Session. */
  pendingInteraction: SessionPendingInteraction | undefined
}

A business package may install one entry only while a Remote waterfall request is pending:

import type { ComposerChainProps } from '@deepseek-ai/dsh-client-ui-conversation/client'
import type { ChainSelect, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import type { SessionId } from '@deepseek-ai/dsh-session/types'

interface Request {
  readonly sessionId: SessionId
}

type RequestComposerProps =
  PropsRuntime<'conversation.composer'> & { matched: Request }

const select: ChainSelect<ComposerChainProps, Request> = owner =>
  owner.sessionId === request.sessionId ? request : null

const dispose = ctx.slots.register(
  { name: 'conversation.composer', select },
  RequestComposer,
)

try {
  return await request.result
} finally {
  dispose()
}

The selector must be a pure function of the owner currency. Its non-null return is delivered to the component as matched; PropsRuntime<'conversation.composer'> supplies the standard Session and global props. Chain order remains ascending priority, then registration order, and the first non-null selector wins. The shell keeps the default composer mounted beneath a takeover. Request state, listeners, response encoding, and any request-specific child slots belong to the business package; they are not carried by SessionSnapshot or declared by this core package.

Model Experience

None, as this package renders browser state and sends user-admitted inputs through Session Controller APIs without constructing model requests.

KV Cache effect

None; Conversation assembly and browser input state do not alter provider-side prompt caching.

Known Limitations and Deferred Work

  • Only registered targets can render — the shell deliberately has no implicit fallback target beyond the registered chat preference.

Dev Note

Working context for maintainers — click to expand

None.