deepseek-ai/deepseek-harness--packages-client-ui-conversation ↗★ 263k

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

Target-neutral Conversation assembly, shell, composer, queue, and view navigation

패키지
@deepseek-ai/dsh-client-ui-conversation
호환성
미검증
Cordis peer 범위
workspace:~
버전
0.2.1-rc.3
라이선스
MIT
최근 업데이트
2026. 10. 23.

같은 패키지 이름의 다른 저장소

설치

검증된 bundle이 없거나 호환성 검사에 실패했습니다. 먼저 저장소 설명을 읽어 주세요. 전체 README 읽기 ↗


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

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

English | 中文

Desktop product events use the optional product analytics service; ordinary Web interactions are excluded.

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. A View Definition may declare toolCallFocus to translate a tool-call id into its focus identity. Conversation supplies an Inspect callback only while such a target has a visible View entry; Chat consumes this callback without selecting a target.

ConversationBinding.openTurn is an identity-stable, read-only source of the latest turn number when its start is loaded and it remains open, or undefined otherwise. Observed turn changes publish synchronously even when no View is active. Fixed stop input reads this source alongside Session running and pending-interaction state. Unloading the Session binding detaches its event-source subscription.

The adapter passes each SessionEventLikeEntry directly to the assembler. Its outer type distinguishes durable events from Client-only transient events, while its inner event always exposes type, seq, time, and data; Definitions receive that inner SessionEventLike. Replacement windows may include both entry variants, while historical prepends carry durable entries and live appends may carry either. Definitions use the same match/start/update interface for durable and transient events. The earliest loaded start initializes State; every later Match, including another start for that identity, updates it. Definitions that do not consume Assistant deltas return null for assistant/live-chunk. Replacement windows and revision gaps rebuild from the complete loaded window; contiguous append, prepend, and Assistant-settlement revisions use incremental assembly. Settlement removes only the named attempt's transient matches, applies its optional durable entry, and recomputes the affected Contexts from their remaining earliest start, refreshing predecessor indexes and dependents without replacing unrelated target nodes. A Context with no remaining start has no State. Its key and previously published nodes remain available to later evidence until a full window rebuild; the owning Definition may hide those nodes. 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.

Definitions may share the same read-only Match and Location for one input and lifecycle role. Each Definition keeps its own Context, State, and ordered Match list; Match objects are not reused across assembler instances.

Registration accepts the existing callable match or a read-only event-type-to-handler table. The Registry copies table entries and precomputes ordered candidate Sets on registration changes. Function-form Definitions keep their callable API and receive every event; table handlers receive only their declared types. Changing a table requires unregistering and registering again. Legacy assembler adapters with only entries() and fallbackEntry() retain their original dispatch path.

All accepted Matches update State in order. Repeated updates to one Context are coalesced between flushes. A Context retains its key while kind and id match; Definition-facing Context snapshots remain distinct objects. The fallback handles only inputs that no ordinary Definition of its target matched. A Definition can return exclusive: true from match() to claim an event on its target: other Definitions of that target do not receive the event and the target's fallback does not run (semantics).

Location lookup indexes Steps by Turn and Step number, so a non-boundary append does not scan the Turn's Step array. Each input's numeric coordinates and resolved Location share one private index record; published Location values remain immutable. Boundary updates, replacement, and prepend keep the indexes aligned with the current timeline.

A target becomes active when shell selection resolves it or when its source receives a first subscriber. The assembler replaces that target from current Contexts once and keeps it active for later incremental flushes; creating a source does not activate it and unsubscription does not deactivate it.

UiConversation.groups registers one optional business Group Definition per existing target. It consumes projected Node changes, changed Turns, and indexed target positions after Node materialization, including first activation, and owns all segmentation and group data. Indexed Turn reads preserve interruptions from neighbouring Nodes, allowing business updates to stay within affected Turns and groups. The assembler validates and installs root references and keyed group snapshots before publishing Node, Group, and Location data sources. Ungrouped targets retain their original path. Grouping defines input lifetime, typed registration, atomic updates, and renderer responsibilities.

Group registration rejects a missing View target. Removing an already registered View Definition pauses its grouping and clears its published results without removing the Group Definition; registering that View again rebuilds from the current loaded timeline through the existing replacement flow. Switching View tabs does not remove a View Definition.

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. The shared request inspection serves every target: ctx.uiConversation.inspectSystemPrompt(previous, event) interprets system messages and positional replacements as immutable loaded-surface state. It selects the last nonempty surviving system node in surface order, retains only surviving replacement positions for chained rewrites, and withholds the prompt after an unindexed older endpoint until prepend replay supplies its order. Target-owned Definitions retain historical cards independently. ctx.uiConversation.inspectRequestPrompt(previous, header, system) classifies request changes against that effective prompt; ordinary messages and stream chunks require no system-state work.

Sent user text

UiConversation.userText registers synchronous match(text, from, context) rules. Each call offers one business or Text candidate; the evaluator selects the earliest start and uses order and stable ID only for same-start ties. Each caller owns its query cache. Context.read(key) invalidates only readers of changed Facts, while future candidates and no-match results are reused within their recorded search ranges. Inputs and outputs use readonly types without runtime freezing. See the UserText types and design decision.

Businesses extend UserTextDataMap and UserTextFacts on this package's /client entry, register a matcher through ctx.uiConversation.userText, and contribute their renderer to conversation.user.text.node. The session-scoped conversation.user.text Factory owns this inline keyed Slot. Whitespace rules also return ordinary Text candidates through the same scheduler; trim wins same-start edge collisions with collapse. Node registrations supply their own actions; interactive and CSS ellipsis remain React Props. Missing renderers display the original fragment; matching failures retain the complete original text and report the rule error.

Queue previews enable whitespace collapsing and document-edge trimming, retain emoji, and use CSS ellipsis without truncating the input. Editing and copying retain the original text; native attachments use their separate presentation.

Message quotes registers quote matching and rendering through the same UserText extension points. Each user-text container and draft editor owns an independent hover-card group, so its quote previews replace one another immediately.

The existing semantic DraftSnapshot retains quote text and clipboard-coordinate spans beside references. Storage accepts earlier string and reference-only drafts; Session switching, Workspace transfer, and failed-send restoration preserve atomic quotes through the existing editor runtime. inputActions.insertQuote(text) inserts a nonblank selection only while the composer is in plain mode. Submission emits ordinary Markdown text; pending echoes, queue rows, steering rows, and durable messages use the same text matcher.

UiConversation.interactions owns plugin registrations for selected-text and context-menu gestures. A registration supplies a synchronous handler whose ID selects its keyed Slot component; plugins use the Slot injection mechanism for rendering. Handlers call open() to display and call preventDefault() separately when replacing a browser default. Chat supplies DOM selections and local layout; original source data and Markdown offsets are not inferred from DOM positions.

Shell and standard props

The shared image slot props keep display choices separate from durable references: thumbnail requests a contained attachment-list thumbnail, while compact requests a cropped gallery tile. An optional per-image label supplies the accessible display name; loading and cache identity still use the original attachment reference. MessageImagesContentInput adds optional complete MessageImageLabels for the root-scoped conversation.images.content Factory. Message, tool, and Factory inputs carry the view's authorized loadImage callback explicitly; loading and URL caching remain with the resource owner. ui-attachment provides this Factory and owns rendering and the lightbox.

The composer uses a minimum 16px draft font on coarse-pointer devices to avoid focus-triggered browser zoom; larger font preferences still apply.

The composer measures its expanded control groups after size, content, visibility, or font-loading changes. If they cannot share a line, the row sets --dsh-composer-model-text-display: none and --dsh-composer-model-icon-display: block for the model seat; their defaults are block and none. Wrapping remains available if even the icon cannot fit.

The context meter follows the statistics-control radius.

The composer uses the shared text-input focus policy on mount, Session switching, and unlock. Identified phones and tablets require tapping the input; desktop devices retain automatic focus. On these mobile devices, a submission that empties the focused draft moves focus to the non-text composer card, dismissing the keyboard while retaining its navigation position. Rejected submissions, retained newer draft text, and another control already holding focus preserve focus. The Lexical patch keeps caret scrolling in the same coordinate system as DOM geometry when a mobile keyboard pans the page.

The context-occupancy button shows a ring and percentage below the input card, after the Session statistics. Clicking it opens the token breakdown in a panel kept inside the viewport, including when no statistics are shown; the button stays hidden until context usage and capacity are available.

With Coding Tools off, the shell hides only Trajectory; other plugin-contributed Views remain available. The View tab bar is hidden when fewer than two Views are available. Turning Coding Tools off while Trajectory is active returns to Chat; stored View preferences and recorded Session data remain intact. Turning it on makes Trajectory available again. View owners receive the available roster so their navigation actions can follow the same visibility.

While the root-scoped conversation.session.header.views.actions list has registrations, a More conversation views button with a tooltip follows the View tabs in the tab row. It opens a menu whose rows are that list's registrations. Each receives entries (Views outside the header in registration order with id, label, pinned, and active; Chat is never listed), onOpen(id), which selects the View in this Session, onPinnedChange(id, pinned), and onDismiss(), which closes the menu. The header keeps an in-memory list of unpinned View ids shared by every Session; the tab bar omits unpinned Views and places registered Chat first so it always remains outside More. Plugin reload resets pin choices, and legacy localStorage values are ignored. The tab bar's visibility counts available Views, not pinned ones, so the bar and More stay while every other View is unpinned. Unpinning the active View hides its tab immediately without changing the selected content. More lists only Views outside the header and identifies the current View through active. The header reads its available row width, reserves 48px for More and padding, and estimates 120px per tab. Remaining Views keep registration order in More. A single ResizeObserver watches the tab row so window resizing and sidebar expansion update the split; active selection does not change the order. The observer disconnects when the row is removed. If ResizeObserver is unavailable, the initial width calculation still applies without subsequent observation. The More button highlights and names the active View in its tooltip when that View is folded. Individual tabs are not measured. At least one preferred tab remains outside, and long labels truncate with a full-name tooltip. Unregistered View ids remain i