English | 中文
@deepseek-ai/dsh-message-feedback owns editable feedback for individual assistant messages. The canonical Session log stores feedback/message-put and feedback/message-delete; the immutable Session-level remark remains feedback/record. All three are log-only events that never enter model context.
Source: packages/feedback/message-feedback/src/types.ts
/** Opaque compare-and-set token for one exact feedback item revision. */
type MessageFeedbackVersion = Branded<'MessageFeedbackVersion'>
/** The human's overall judgment of one assistant message. */
type MessageFeedbackRating = 'positive' | 'negative'
/** One current feedback value and its opaque mutation token. */
interface MessageFeedbackItem {
/** Stable identity of the assistant message inside the owning Session. */
readonly messageId: MessageId
/** Overall positive or negative judgment. */
readonly rating: MessageFeedbackRating
/** Optional explanation, preserved verbatim after validation. */
readonly note?: string
/** Equality-only token replaced by every material create or update. */
readonly version: MessageFeedbackVersion
/** Host-assigned creation time in Unix epoch milliseconds. */
readonly createdAt: number
/** Host-assigned time of the most recent material update. */
readonly updatedAt: number
}
/** A material creation or edit, retaining its complete current value. */
interface MessageFeedbackPut {
/** Owning Session; inherited feedback in a fork belongs to its parent. */
readonly sessionId: SessionId
/** Value after this mutation, including the original creation time. */
readonly item: MessageFeedbackItem
}
/** A material deletion of one current feedback item. */
interface MessageFeedbackDelete {
/** Session that owns the deleted feedback. */
readonly sessionId: SessionId
/** Message whose feedback was removed. */
readonly messageId: MessageId
}
/** Read all message feedback belonging to one persisted Session lifecycle. */
interface MessageFeedbackListRequest {
/** Session whose feedback events should be read. */
readonly sessionId: SessionId
}
/** Current feedback values for one Session, in first-creation order. */
interface MessageFeedbackListValue {
/** Fresh immutable item snapshots. */
readonly items: readonly MessageFeedbackItem[]
}
/** Create or replace feedback for one assistant message. */
interface MessageFeedbackPutRequest {
/** Persisted Session that owns the target message. */
readonly sessionId: SessionId
/** Target assistant-message identity. */
readonly messageId: MessageId
/** Desired overall judgment. */
readonly rating: MessageFeedbackRating
/** Optional non-blank explanation. */
readonly note?: string
/** Observed item version, or `null` to require that no item exists. */
readonly ifVersion: MessageFeedbackVersion | null
}
/** Delete feedback for one message after observing its current version. */
interface MessageFeedbackDeleteRequest {
/** Session that owns the feedback. */
readonly sessionId: SessionId
/** Message whose feedback should be absent after this operation. */
readonly messageId: MessageId
/** Observed item version; ignored when the item is already absent. */
readonly ifVersion: MessageFeedbackVersion
}
/** Idempotent deletion acknowledgement. */
interface MessageFeedbackDeleteValue {
/** Stable postcondition shared by the first deletion and every retry. */
readonly absent: true
}
/** No persisted Session header exists for the requested id. */
interface MessageFeedbackSessionNotFound {
readonly code: 'session-not-found'
readonly sessionId: SessionId
}
/** The id does not name a derived, append-origin assistant message. */
interface MessageFeedbackTargetNotFound {
readonly code: 'target-not-found'
readonly sessionId: SessionId
readonly messageId: MessageId
}
/** A material mutation did not match the addressed item's current version. */
interface MessageFeedbackVersionConflict {
readonly code: 'version-conflict'
/** Authoritative current item, or `null` when it does not exist. */
readonly current: MessageFeedbackItem | null
}
/** A supplied note contains no non-whitespace character. */
interface MessageFeedbackNoteBlank {
readonly code: 'note-blank'
}
/** A supplied note exceeds the configured UTF-8 byte limit. */
interface MessageFeedbackNoteTooLarge {
readonly code: 'note-too-large'
readonly maxBytes: number
readonly actualBytes: number
}
/** Failures shared by the public message-feedback operations. */
type MessageFeedbackFailure =
| MessageFeedbackSessionNotFound
| MessageFeedbackTargetNotFound
| MessageFeedbackVersionConflict
| MessageFeedbackNoteBlank
| MessageFeedbackNoteTooLarge
/** Successful public operation result. */
interface MessageFeedbackSuccess<T> {
readonly ok: true
readonly value: T
}
/** Rejected public operation result with a stable business failure. */
interface MessageFeedbackRejected<E extends MessageFeedbackFailure> {
readonly ok: false
readonly error: E
}
/** Result returned by the message-feedback `list` operation. */
type MessageFeedbackListResult =
| MessageFeedbackSuccess<MessageFeedbackListValue>
| MessageFeedbackRejected<MessageFeedbackSessionNotFound>
/** Result returned by the message-feedback `put` operation. */
type MessageFeedbackPutResult =
| MessageFeedbackSuccess<MessageFeedbackItem>
| MessageFeedbackRejected<
| MessageFeedbackSessionNotFound
| MessageFeedbackTargetNotFound
| MessageFeedbackVersionConflict
| MessageFeedbackNoteBlank
| MessageFeedbackNoteTooLarge
>
/** Result returned by the message-feedback `delete` operation. */
type MessageFeedbackDeleteResult =
| MessageFeedbackSuccess<MessageFeedbackDeleteValue>
| MessageFeedbackRejected<MessageFeedbackSessionNotFound | MessageFeedbackVersionConflict>
Current items are folded from canonical feedback events whose payload sessionId matches the owning Session. Each item carries a positive or negative rating, an optional note, Host-assigned createdAt/updatedAt timestamps, and its own opaque version. Versions are compared only for equality and only against the addressed message; callers do not order or synthesize them.
put uses strict optimistic concurrency: every request for an existing item must match its current ifVersion, including a no-op. A conflict returns the authoritative current item (or null), so a caller can reconcile a lost response or a concurrent edit without another read. Deleting an already absent item succeeds. A per-Session queue serializes reads and mutations; cold mutations hold a persistence write handle across read, comparison, append, and flush. Matching no-ops append no event.
A live owner's in-memory log supplies the target Session observation directly; cold reads use a SessionPersistence.open(id, 'read') handle, while mutations use a write handle. Neither path constructs a Session or Agent. A stat(id) preflight classifies definite absence; a read failure for a Session stat confirmed propagates as infrastructure failure. put accepts only a non-empty, append-origin assistant/message with the requested MessageId; replacement-origin, usage-only empty, and non-assistant records are not feedback targets.
Fork seeds can contain parent feedback events, but their payload retains the parent sessionId, so they do not become current feedback for the child. Deleting an item appends a tombstone; earlier ratings and notes remain in the log.
Successful message-feedback mutations await canonical persistence: live operations append through the owning Session and require a participating ctx.sessions.flush listener; cold operations append and flush through their write handle. Persistence failures propagate rather than reporting success. maxNoteBytes is required and bounds note text by UTF-8 bytes; the Web Host composition sets 8192. The package publishes the Host messageFeedback.list, messageFeedback.put, and messageFeedback.delete unary Remote contract through TypertRemoteService and @Remote; the generated Cordis API below is the method-level authority.
Plugin disposal closes operation admission and drains accepted per-Session queue work.
When explicitly enabled, session-log-deepseek carries feedback as part of the ordinary dsh_session_log suffix on subsequent eligible DeepSeek requests. Recording feedback does not trigger an LLM request or a separate dsh_feedback upload. For non-DeepSeek routes, the OTel backend can release the canonical prefix through recorded feedback. The command acknowledgement confirms recording and identifies the Session and anonymous user; it reports neither telemetry policy nor delivery.
@deepseek-ai/dsh-client-ui-message-feedback is the browser consumer. @deepseek-ai/dsh-api-remotes mounts the generated messageFeedback contribution, so the plugin calls ctx.remote.messageFeedback and never touches the transport.
The controls are the feedback entry (order 10) of the conversation.chat.assistant-actions list slot, which ui-conversation declares and renders inside the finalized assistant message's IconActions row. AssistantMessageNode carries the optional messageId from the assistant/message event. The field is absent on interruption-frozen partials, and the render site skips the slot when it is absent. The strip renders once per turn, on the closing assistant message: the Host accepts every append-origin step message as a target, but earlier steps of a multi-step turn render tool rows rather than a rateable body, so the UI exposes a narrower set than the Host contract allows.
One MessageFeedbackController per Session backs every message control in that Session: a single list read seeds the whole transcript, deferred to first hover or focus rather than fired on mount. Each mutation sends the version that controller last observed as ifVersion; a version-conflict reply carries the authoritative item, so the controller reconciles from the reply instead of refetching. Mutations serialize per Session so a queued operation compares against the committed version. A connection/reset refreshes only Sessions already read.
session-not-found; callers retry after retirement materialization.maxNoteBytes bounds only each note.messageId.maxNoteBytes; an oversized note fails on save with note-too-large rather than while typing.Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — the language sides differ only in locale-specific paired document paths. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.
ctx.messageFeedback — MessageFeedbackServiceSession-log service; cold operations never construct a Session or Agent.
/**
* Read current feedback from the canonical log.
* @param request - Session to inspect.
* @returns immutable items or a definite persistence miss.
*/
@Remote('list') list(request: MessageFeedbackListRequest): Promise<MessageFeedbackListResult>
/**
* Create or replace feedback after checking its current version.
* Matching no-ops retain the version and append no event.
* @param request - Target, desired value, and observed item version.
* @returns the durable item or an explicit business failure.
*/
@Remote('put') put(request: MessageFeedbackPutRequest): Promise<MessageFeedbackPutResult>
/**
* Delete one item after checking its version; absence succeeds without an event.
* @param request - Session, message, and observed item version.
* @returns the stable absent postcondition or an explicit failure.
*/
@Remote('delete') delete(request: MessageFeedbackDeleteRequest): Promise<MessageFeedbackDeleteResult>
Source: packages/feedback/message-feedback/src/index.ts
feedback/* eventsfeedback/committed — parallelObserve a durable cold feedback mutation without publishing a live Session. Observers run before write ownership is released and must not await another message-feedback operation for this Session. The payload is borrowed read-only; deep-clone it before transferring ownership (for example, to Session.fromRestore).
/**
* Observe a durable cold feedback mutation without publishing a live Session.
* Observers run before write ownership is released and must not await
* another message-feedback operation for this Session. The payload is borrowed
* read-only; deep-clone it before transferring ownership (for example, to Session.fromRestore).
* @param inspection - committed canonical prefix, including the feedback as its last event.
* @mode parallel
*/
'feedback/committed'(inspection: SessionInspection): void
Types: SessionInspection