refactor: apply repository naming contract

Apply the accepted pre-release package, service, type, directory, and role renames as one repository-wide change.
This commit is contained in:
Tianyi Cui
2026-08-13 00:36:22 +08:00
parent 101df7cf58
commit a2d0f7f411
3281 changed files with 21730 additions and 21592 deletions

View File

@@ -18,7 +18,7 @@ import type {
} from './contract/slots.ts'
import type { InputNotice } from './input/contract.ts'
import { createChatStore } from './stores.ts'
import { ConversationService, UnsupportedImageMediaTypeError } from './service.ts'
import { ConversationController, UnsupportedImageMediaTypeError } from './service.ts'
import type { IConversation } from './service.ts'
import { ComposerBlockRegistry } from './input/blocks.ts'
import type { ComposerBlock } from './input/blocks.ts'
@@ -98,8 +98,8 @@ function scopedConversation(sessions: ISessions, id: SessionId): IConversation {
}
/** Resolve package-internal attachment operations from the public service registration. */
function concreteConversation(ctx: Context): ConversationService {
const conversation = ctx.get('conversation') as ConversationService | undefined
function concreteConversation(ctx: Context): ConversationController {
const conversation = ctx.get('conversation') as ConversationController | undefined
if (conversation === undefined) throw new Error('ui-conversation: conversation service unavailable')
return conversation
}
@@ -165,12 +165,12 @@ export function apply(ctx: Context): void {
version: () => slots.getVersion('conversation.view'),
}
// The per-session input machine registry (InputService face; published as
// The per-session input machine registry (SessionInputResolver face; published as
// ctx.conversation.input by the service below sharing this one instance).
const inputHub = new InputHub(ctx, t)
// The composer-block registry: a plugin that knows a session cannot send —
// ui-model, when no adapter serves the session's route — raises a block
// ui-model-selection, when no adapter serves the session's route — raises a block
// here, and the bar reads its own session's store. It cannot flow the other
// way: this package must not import the plugins that would know.
const composerBlocks = new ComposerBlockRegistry()
@@ -302,7 +302,7 @@ export function apply(ctx: Context): void {
}
const conversation = concreteConversation(ctx)
const shell = inputHub.shell(sessionId)
const slash = inputHub.slash(sessionId)
const inputTriggers = inputHub.inputTriggers(sessionId)
return {
keyboard: shell,
addImages: (files) => {
@@ -328,12 +328,12 @@ export function apply(ctx: Context): void {
draftImages: ids => conversation.draftImages(ids),
resolveSubmitMode: (running, gesture, steeringAvailable) =>
submissionPolicy.resolve(running, gesture, steeringAvailable),
toggleCommandMenu: slash === undefined
toggleCommandMenu: inputTriggers === undefined
? undefined
: (selection) => {
shell.dismissPopup()
const snapshot = shell.snapshot
slash.toggleSource('command', {
inputTriggers.toggleSource('command', {
trigger: '/',
query: '',
position: snapshot.draft.slice(0, selection.start).trim() === '' ? 'leading' : 'inline',
@@ -354,14 +354,14 @@ export function apply(ctx: Context): void {
hooks: {
notices: shell.notices,
lexicon: shell.lexicon,
menuLauncher: slash?.launcher ?? ABSENT_MENU_LAUNCHER,
menuLauncher: inputTriggers?.launcher ?? ABSENT_MENU_LAUNCHER,
},
}
},
}, InputBar)
// The approval takeover: a selector-routed entry of the chain this package
// just declared (the ui-question registration pattern; the entry lives here
// just declared (the ui-user-questions registration pattern; the entry lives here
// because approval answering is core conversation UX, not an optional tool).
// Zero business face — data and verbs both ride the matched carrier.
// priority 1: question takeovers (default 0) win when both kinds are
@@ -432,7 +432,7 @@ export function apply(ctx: Context): void {
// registers itself as `conversation` and lives on its own child fiber.
// Presentation registrants depend directly on their slot declarations;
// this service remains only where conversation actions are required.
ctx.plugin(ConversationService, { input: inputHub, blocks: composerBlocks })
ctx.plugin(ConversationController, { input: inputHub, blocks: composerBlocks })
// The plan strip rides the input dock above the queue rows (same posture).
ctx.plugin(todoDockEntry)

View File

@@ -395,7 +395,7 @@ export function ChatView({
t={t}
/>
))}
{/* No pending placeholders: questions (ui-question) and approvals
{/* No pending placeholders: questions (ui-user-questions) and approvals
(ApprovalPanel) both take over the composer, so a flow card would
double-render the same wait. */}
{/* Turn-level loading signal: rides the whole running turn (first-token

View File

@@ -3,7 +3,7 @@
// marker reports where the model stopped seeing that history — it never
// replaces it. The framed checkpoint payload is written for the model and is
// not rendered; the disclosure shows the summary from the checkpoint's own
// cited `compact/summary` event, and a window cut that left that event outside makes the row
// cited `compaction/summary` event, and a window cut that left that event outside makes the row
// non-expandable rather than empty.
import { memo, useState } from 'react'

View File

@@ -118,10 +118,10 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
* the next one rather than applied to a current one.
*/
'conversation.hero.agentPreset': { kind: 'single'; scope: 'root'; owner: HeroAgentPresetOwnerProps }
// 'conversation.input.overlay' merges in ui-slash (the dependency
// direction is the hard constraint — ui-slash cannot import
// 'conversation.input.overlay' merges in ui-input-trigger (the dependency
// direction is the hard constraint — ui-input-trigger cannot import
// this package, while this package's input contract already imports
// ui-slash, so the type arrives transitively). The runtime declaration
// ui-input-trigger, so the type arrives transitively). The runtime declaration
// (children table in apply.ts) stays here with the other input slots.
/**
* Stacked strip above the input (queue rows / GoalBar / attachments;
@@ -446,7 +446,7 @@ export interface ComposerBarInjected {
gesture: ComposerSubmitGesture,
steeringAvailable: boolean,
) => InputSubmitMode
/** Toggle the shared slash menu with only its command source; absent without ui-slash or a session. */
/** Toggle the shared slash menu with only its command source; absent without ui-input-trigger or a session. */
toggleCommandMenu: ((selection: EditSelection) => void) | undefined
/** Cancel the in-flight turn; absent with the session. */
stop: (() => void) | undefined
@@ -539,7 +539,7 @@ export type ConversationSessionHeaderSlotProps =
export type ApprovalWait = PendingWait<'approval'>
/**
* Approval domain face over the carrier (the ui-question PendingQuestion
* Approval domain face over the carrier (the ui-user-questions PendingQuestion
* pattern): render identity and question material forwarded transparently;
* answer owns the wire encoding — the ApprovalResponsePayload value shape
* with the audit correlation the host reconciles — and turns a rejected

View File

@@ -4,8 +4,8 @@ import type {
ConversationNodeDefinition,
} from '@deepseek-ai/dsh-client-runtime/client'
import { isReplacementSurfaceEvent } from '@deepseek-ai/dsh-client-runtime/client'
import type { CompactCheckpointSource } from '@deepseek-ai/dsh-compact/checkpoint'
import type {} from '@deepseek-ai/dsh-compact/types'
import type { CompactionCheckpointSource } from '@deepseek-ai/dsh-compaction/checkpoint'
import type {} from '@deepseek-ai/dsh-compaction/types'
import type {} from '@deepseek-ai/dsh-commands/types'
import type { ManualCompactionChatData } from '../contract/chat-nodes.ts'
import { chatNode } from './common.ts'
@@ -21,7 +21,7 @@ declare module '@deepseek-ai/dsh-client-ui-conversation/client' {
type CommandId = CommandNode['commandId']
const COMPACT_PLUGIN: CompactCheckpointSource['plugin'] = 'compact'
const COMPACT_PLUGIN: CompactionCheckpointSource['plugin'] = 'compact'
interface CommandState {
readonly command: CommandNode
@@ -96,7 +96,7 @@ function compactSource(event: Parameters<ConversationNodeDefinition['match']>[0]
/**
* Build the visible summary marker from optional lifecycle evidence.
* @param match - compact/summary Match, when loaded.
* @param match - compaction/summary Match, when loaded.
* @param checkpoint - replacement checkpoint Match.
* @returns final compaction summary Node data.
*/
@@ -104,7 +104,7 @@ function compactSummary(match: ConversationMatch | undefined, checkpoint: Conver
let summary: string | null = null
let shadowedItemCount: number | null = null
let shadowedTokenCount: number | null = null
if (match?.event.type === 'compact/summary') {
if (match?.event.type === 'compaction/summary') {
const data = match.event.data
if (Array.isArray(data.summary)) {
const text = data.summary
@@ -135,7 +135,7 @@ function compactSummary(match: ConversationMatch | undefined, checkpoint: Conver
function fallbackState(context: ConversationNodeContext<CommandState>): CommandState | undefined {
const done = context.matches.find(match => match.event.type === 'command/done')
const checkpoint = context.matches.find(match => compactSource(match.event) !== undefined)
const summary = context.matches.find(match => match.event.type === 'compact/summary')
const summary = context.matches.find(match => match.event.type === 'compaction/summary')
if (checkpoint === undefined) return done === undefined ? undefined : { command: commandFromDone(done) }
const source = compactSource(checkpoint.event)
if (source?.sourceCommandId === undefined) return done === undefined ? undefined : { command: commandFromDone(done) }
@@ -167,7 +167,7 @@ export function updateCompactionState<State extends CompactionEvidence>(
state: State,
match: ConversationMatch,
): State {
if (match.event.type === 'compact/summary') return { ...state, summary: match }
if (match.event.type === 'compaction/summary') return { ...state, summary: match }
if (compactSource(match.event) !== undefined) return { ...state, checkpoint: match }
return state
}
@@ -187,9 +187,9 @@ export const commandDefinition: ConversationNodeDefinition<CommandState> = {
if (checkpoint?.sourceCommandId !== undefined) {
return { id: String(checkpoint.sourceCommandId), role: 'update' }
}
if (event.type === 'compact/start'
|| event.type === 'compact/summary'
|| event.type === 'compact/end') {
if (event.type === 'compaction/start'
|| event.type === 'compaction/summary'
|| event.type === 'compaction/end') {
if (event.data.sourceCommandId !== undefined) {
return { id: String(event.data.sourceCommandId), role: 'update' }
}

View File

@@ -2,7 +2,7 @@ import type { Context } from '@deepseek-ai/cordis'
import type {
CompactionSummaryNode, ConversationMatch, ConversationNodeContext, ConversationNodeDefinition,
} from '@deepseek-ai/dsh-client-runtime/client'
import type {} from '@deepseek-ai/dsh-compact/types'
import type {} from '@deepseek-ai/dsh-compaction/types'
import { chatNode } from './common.ts'
import { compactSource, compactSummary, updateCompactionState } from './command.ts'
@@ -19,7 +19,7 @@ interface CompactionState {
}
function fallbackState(context: ConversationNodeContext<CompactionState>): CompactionState {
const summary = context.matches.find(match => match.event.type === 'compact/summary')
const summary = context.matches.find(match => match.event.type === 'compaction/summary')
const checkpoint = context.matches.find(match => compactSource(match.event) !== undefined)
return {
...summary === undefined ? {} : { summary },
@@ -36,13 +36,13 @@ export const compactionDefinition: ConversationNodeDefinition<CompactionState> =
if (checkpoint !== undefined && checkpoint.sourceCommandId === undefined) {
return { id: checkpoint.compactionId, role: 'update' }
}
if (event.type === 'compact/start'
|| event.type === 'compact/summary'
|| event.type === 'compact/end') {
if (event.type === 'compaction/start'
|| event.type === 'compaction/summary'
|| event.type === 'compaction/end') {
if (event.data.sourceCommandId !== undefined) return null
const compactionId: unknown = event.data.compactionId
if (typeof compactionId !== 'string' || compactionId === '') return null
return { id: compactionId, role: event.type === 'compact/start' ? 'start' : 'update' }
return { id: compactionId, role: event.type === 'compaction/start' ? 'start' : 'update' }
}
return null
},

View File

@@ -15,7 +15,7 @@ export type {} from './conversation-nodes/turn-max-tokens.ts'
export type {} from './conversation-nodes/turn-tail.ts'
export { apply, inject } from './apply.ts'
export { ConversationService } from './service.ts'
export { ConversationController } from './service.ts'
export type { IConversation } from './service.ts'
export type { DraftAttachmentId } from './input/contract.ts'

View File

@@ -2,7 +2,7 @@
* Composer blocks: the one way another plugin stops a session's input.
*
* The composer cannot read the plugins that would know — the dependency runs
* ui-model → ui-conversation, never back — so a blocker pushes here and the
* ui-model-selection → ui-conversation, never back — so a blocker pushes here and the
* bar reads its own session's store. A block carries the localized reason it
* exists, because the plugin that raised it owns that copy; the composer only
* knows how to render an inert textarea with a placeholder, exactly as it

View File

@@ -10,7 +10,7 @@ import type { Branded } from '@deepseek-ai/dsh-brand'
import type {
ArbitrateKey, ArbitrateOutcome, CommandClaim, ConsumeTokenRequest, PickOutcome,
ReferenceInsert, SubmitOutcome, TokenSpan,
} from '@deepseek-ai/dsh-client-ui-slash/client'
} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
import type { QueueRow } from '../contract/queue.ts'
import type { InputSubmitMode } from '../contract/composer-submission.ts'
@@ -47,7 +47,7 @@ export interface SessionInput extends InputTarget {
/**
* Surface a notice outside the machine's own effect stream: detached
* command results and business notifications render through here.
* Session-routed — resolving the facade via InputService.for(actx) lands
* Session-routed — resolving the facade via SessionInputResolver.for(actx) lands
* the notice on that session's composer, so a result arriving after a
* session switch still reaches its own session.
* @param level - severity tier.
@@ -59,7 +59,7 @@ export interface SessionInput extends InputTarget {
}
/** Session-addressed access to the per-session input facade. */
export interface InputService {
export interface SessionInputResolver {
/** Resolve the facade for one session-scope ctx. */
for(actx: ClientContext): SessionInput
}
@@ -208,7 +208,7 @@ export interface InputMachineOptions {
/** Published input state (the currency; per-session). */
export interface InputState {
readonly draft: string
/** Ordered runtime-only image ids; bytes and URLs stay in ConversationService. */
/** Ordered runtime-only image ids; bytes and URLs stay in ConversationController. */
readonly imageIds: readonly DraftAttachmentId[]
/** Monotonic draft revision (span CAS compares against this). */
readonly draftRev: number

View File

@@ -2,7 +2,7 @@
* SessionInput shell over the pure input machine: the sole machine caller
* and effect executor. Owns the InputState store (machine state + the queue
* overlay), the notice channel, and the submit transaction plumbing
* (adjudicate via the session's SlashController; claim.submit; default
* (adjudicate via the session's InputTriggerController; claim.submit; default
* sink). Package-private; the hub alone constructs it and wires the scoped
* event listeners onto it.
*/
@@ -10,8 +10,8 @@ import type { ClientContext, ObservableSnapshot, SnapshotStore } from '@deepseek
import { createSnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
import type {
ArbitrateKey, ArbitrateOutcome, CommandClaim, ConsumeTokenRequest, PickOutcome,
ReferenceInsert, SlashController, TokenSpan,
} from '@deepseek-ai/dsh-client-ui-slash/client'
ReferenceInsert, InputTriggerController, TokenSpan,
} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
import type {
DraftAttachmentId, EditRange, EditSelection, InputActions, InputEffect, InputNotice, InputState,
PasteComponent, QueuedMessage, SessionInput, SubmitAttempt,
@@ -34,7 +34,7 @@ export interface SessionInputDeps {
/** Session-scope ctx handed to claim.submit transactions. */
actx: ClientContext
/** Enter adjudication face resolver; absent/undefined answer = every '/' line falls to the default sink. */
slash?: (() => SlashController | undefined) | undefined
inputTriggers?: (() => InputTriggerController | undefined) | undefined
/** PopupSelect shell face resolver (dismissal on submit lock / escape). */
popup?: (() => PopupDismissFace | undefined) | undefined
/** Queue read face; overlaid onto InputState.queue (absent = empty). */
@@ -204,7 +204,7 @@ export class SessionInputShell implements SessionInput {
const phase = this.snapshot.phase
if (phase === 'adjudicating' || phase === 'submitting') {
this.deps.popup?.()?.dismiss()
this.deps.slash?.()?.track(this.snapshot.draft, 0, { tier: 'frozen' }, this.snapshot.draftRev)
this.deps.inputTriggers?.()?.track(this.snapshot.draft, 0, { tier: 'frozen' }, this.snapshot.draftRev)
}
}
@@ -215,7 +215,7 @@ export class SessionInputShell implements SessionInput {
* @param caret - caret position in draft coordinates.
*/
track(draft: string, caret: number): void {
this.deps.slash?.()?.track(draft, caret, { tier: guardOf(this.snapshot.phase) }, this.snapshot.draftRev)
this.deps.inputTriggers?.()?.track(draft, caret, { tier: guardOf(this.snapshot.phase) }, this.snapshot.draftRev)
}
/**
@@ -225,7 +225,7 @@ export class SessionInputShell implements SessionInput {
* @returns the menu's verdict; 'pass' when no pipeline is mounted.
*/
arbitrate(key: ArbitrateKey, composing: boolean): ArbitrateOutcome {
return this.deps.slash?.()?.arbitrate(key, composing) ?? 'pass'
return this.deps.inputTriggers?.()?.arbitrate(key, composing) ?? 'pass'
}
/**
@@ -243,15 +243,15 @@ export class SessionInputShell implements SessionInput {
* @returns true = a claim/insert was applied — the caller preventDefaults.
*/
space(): boolean {
const slash = this.deps.slash?.()
if (slash === undefined) return false
const consumed = slash.onSpace()
const inputTriggers = this.deps.inputTriggers?.()
if (inputTriggers === undefined) return false
const consumed = inputTriggers.onSpace()
// Machine-driven draft replacement never passes through onChange, so
// re-track: the caret lands after the token, where detection sees
// whitespace and closes the menu.
if (consumed) {
const next = this.snapshot
slash.track(next.draft, next.draft.length, { tier: guardOf(next.phase) }, next.draftRev)
inputTriggers.track(next.draft, next.draft.length, { tier: guardOf(next.phase) }, next.draftRev)
}
return consumed
}
@@ -270,8 +270,8 @@ export class SessionInputShell implements SessionInput {
* subscribers never fire.
*/
readonly lexicon: ObservableSnapshot<ReadonlyMap<'/' | '@', readonly string[]>> = {
getSnapshot: () => this.deps.slash?.()?.lexicon.getSnapshot() ?? EMPTY_LEXICON,
subscribe: fn => this.deps.slash?.()?.lexicon.subscribe(fn) ?? (() => {}),
getSnapshot: () => this.deps.inputTriggers?.()?.lexicon.getSnapshot() ?? EMPTY_LEXICON,
subscribe: fn => this.deps.inputTriggers?.()?.lexicon.subscribe(fn) ?? (() => {}),
}
/**
@@ -420,11 +420,11 @@ export class SessionInputShell implements SessionInput {
this.deps.defaultSink(draft.trim(), imageIds, mode)
return
}
const slash = this.deps.slash?.()
const inputTriggers = this.deps.inputTriggers?.()
const controller = new AbortController()
void Promise.all(occurrences.map(async (o) => {
if (slash === undefined) throw new Error(`no serializer for reference source "${o.source}"`)
return { offset: o.offset, text: await slash.serializeReference(o.source, o.ref, controller.signal) }
if (inputTriggers === undefined) throw new Error(`no serializer for reference source "${o.source}"`)
return { offset: o.offset, text: await inputTriggers.serializeReference(o.source, o.ref, controller.signal) }
})).then(
(parts) => {
if (this.disposed) return
@@ -450,13 +450,13 @@ export class SessionInputShell implements SessionInput {
/** Enter adjudication: poll the session controller; failure = notice + draft retained (never a silent downgrade). */
private adjudicate(attempt: SubmitAttempt, draft: string): void {
const slash = this.deps.slash?.()
if (slash === undefined) {
const inputTriggers = this.deps.inputTriggers?.()
if (inputTriggers === undefined) {
// No pipeline mounted: the '/' line is an ordinary message.
this.run(this.core.dispatch({ type: 'adjudicated', attempt, outcome: undefined }))
return
}
slash.adjudicate(draft.trim(), attempt.signal).then(
inputTriggers.adjudicate(draft.trim(), attempt.signal).then(
(outcome: PickOutcome) => {
if (this.dead(attempt)) return
this.run(this.core.dispatch({ type: 'adjudicated', attempt, outcome }))

View File

@@ -1,18 +1,18 @@
/**
* InputHub: the InputService implementation (`ctx.conversation.input`) — one
* InputHub: the SessionInputResolver implementation (`ctx.conversation.input`) — one
* SessionInputShell per session, created inside the sessions provide
* materialization (the 'input' standard-kit entry IS the
* creation trigger) and torn down by the scope disposer (instance-and-scope
* share one lifecycle). The hub registers the three scoped input-mutation
* listeners on each session's actx (the sole consumer side of the ui-slash
* listeners on each session's actx (the sole consumer side of the ui-input-trigger
* bail events) and owns the default-sink choreography: every session is a
* real host entity, so the sink is one unconditional prompt path.
*/
import type { ClientContext, ISessions, SessionBinding, SessionFace, SessionId } from '@deepseek-ai/dsh-client-runtime/client'
import type { SlashController } from '@deepseek-ai/dsh-client-ui-slash/client'
import type { InputTriggerController } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
import type { TranslateNS } from '@deepseek-ai/dsh-client-locale/client'
import { queueReadFaceOf } from '../queue/store.ts'
import type { ComposerKeyboard, DraftAttachmentId, InputService, SessionInput } from './contract.ts'
import type { ComposerKeyboard, DraftAttachmentId, SessionInputResolver, SessionInput } from './contract.ts'
import type { InputSubmitMode } from '../contract/composer-submission.ts'
import type { PopupDismissFace } from './facade.ts'
import { SessionInputShell } from './facade.ts'
@@ -33,8 +33,8 @@ interface ConversationAttachmentFace {
releaseDraftImage(id: DraftAttachmentId): void
}
/** Session-addressed input facade registry (InputService face + composer-layer extras). */
export class InputHub implements InputService {
/** Session-addressed input facade registry (SessionInputResolver face + composer-layer extras). */
export class InputHub implements SessionInputResolver {
private readonly shells = new Map<SessionId, SessionInputShell>()
/**
@@ -47,7 +47,7 @@ export class InputHub implements InputService {
) {}
/**
* Resolve the facade for one session-scope ctx (InputService face).
* Resolve the facade for one session-scope ctx (SessionInputResolver face).
* @param actx - session-scope context.
* @returns the resident per-session facade.
*/
@@ -72,7 +72,7 @@ export class InputHub implements InputService {
const { sessionId: id, session, ctx: actx } = binding
const shell = new SessionInputShell({
actx,
slash: () => this.controller(actx),
inputTriggers: () => this.controller(actx),
popup: () => this.popup(actx),
queue: queueReadFaceOf(session),
defaultSink: (text, imageIds, mode) => { this.sink(session, text, imageIds, mode) },
@@ -133,9 +133,9 @@ export class InputHub implements InputService {
* Resolve the optional slash controller for composer chrome that launches
* the shared candidate menu without typing a trigger.
* @param id - session id.
* @returns the resident controller, or undefined when ui-slash is absent.
* @returns the resident controller, or undefined when ui-input-trigger is absent.
*/
slash(id: SessionId): SlashController | undefined {
inputTriggers(id: SessionId): InputTriggerController | undefined {
const actx = this.sessions().scope(id)
return actx === undefined ? undefined : this.controller(actx)
}
@@ -191,13 +191,13 @@ export class InputHub implements InputService {
}
}
private controller(actx: ClientContext): SlashController | undefined {
const slash = this.rootCtx.get('slash')
return slash?.sessionOf(actx)
private controller(actx: ClientContext): InputTriggerController | undefined {
const inputTriggers = this.rootCtx.get('inputTriggers')
return inputTriggers?.sessionOf(actx)
}
private popup(actx: ClientContext): PopupDismissFace | undefined {
const command = this.rootCtx.get('command') as CommandFace | undefined
const command = this.rootCtx.get('commandUi') as CommandFace | undefined
return command?.popupFor(actx)
}

View File

@@ -13,7 +13,7 @@
* as a draftRev advance (begin-command / insert-ref / consume-token /
* paste-upgrade all answer their bail events this way).
*/
import type { CommandClaim, ReferenceInsert, TokenSpan } from '@deepseek-ai/dsh-client-ui-slash/client'
import type { CommandClaim, ReferenceInsert, TokenSpan } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
import type { InputSubmitMode } from '../contract/composer-submission.ts'
import type {
ConsumeTokenGuard, EditRange, EditSelection, InputEffect, InputEvent, InputMachineOptions,

View File

@@ -17,7 +17,7 @@ import type { ImageAttachmentRef, ImageMediaType } from '@deepseek-ai/dsh-attach
import type { ComposerAttachment } from './contract/slots.ts'
import type { QueueAction, QueueItemId } from './contract/queue.ts'
import type { ComposerBlocks } from './input/blocks.ts'
import type { DraftAttachmentId, InputService } from './input/contract.ts'
import type { DraftAttachmentId, SessionInputResolver } from './input/contract.ts'
import type { InputSubmitMode } from './contract/composer-submission.ts'
/**
@@ -26,8 +26,8 @@ import type { InputSubmitMode } from './contract/composer-submission.ts'
* test fake must supply.
*/
export interface IConversation {
/** The per-session input machine registry (InputService face). */
readonly input: InputService
/** The per-session input machine registry (SessionInputResolver face). */
readonly input: SessionInputResolver
/**
* The per-session composer-block registry: how a plugin the composer
* cannot import makes a session's input inert with its own reason.
@@ -88,9 +88,9 @@ export class UnsupportedImageMediaTypeError extends Error {
}
/** Scope-addressed conversation service (root singleton, provided as `conversation`). */
export class ConversationService extends Service implements IConversation {
/** The per-session input machine registry (InputService face). */
readonly input: InputService
export class ConversationController extends Service implements IConversation {
/** The per-session input machine registry (SessionInputResolver face). */
readonly input: SessionInputResolver
/** The per-session composer-block registry. */
readonly blocks: ComposerBlocks
private readonly draftAttachments = new Map<DraftAttachmentId, ComposerAttachment>()
@@ -102,11 +102,11 @@ export class ConversationService extends Service implements IConversation {
/**
* @param ctx - owning root context (the plugin apply context; the service
* registers itself and follows that fiber's lifetime).
* @param config - carries the InputService and composer-block registry
* @param config - carries the SessionInputResolver and composer-block registry
* constructed by the plugin apply (the same instances the slot inject
* factories close over).
*/
constructor(ctx: Context, config: { input: InputService; blocks: ComposerBlocks }) {
constructor(ctx: Context, config: { input: SessionInputResolver; blocks: ComposerBlocks }) {
super(ctx, 'conversation')
this.input = config.input
this.blocks = config.blocks

View File

@@ -24,7 +24,7 @@ export function ConversationRoot({
const cwd = useSessions(s => sessionId === undefined ? undefined : s.byId[sessionId]?.cwd)
const summaryBlank = useSessions(s => sessionId === undefined ? undefined : s.byId[sessionId]?.blank)
const workspaces = useWorkspaces(s => s)
// A plugin this package cannot import (ui-model) says this session cannot
// A plugin this package cannot import (ui-model-selection) says this session cannot
// send; its reason is already localized by whoever raised it.
const composerBlock = useComposerBlock(block => block)

View File

@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react'
import type { ReactNode } from 'react'
import clsx from 'clsx'
import type { PermissionSelect as PermissionSelectValue } from '@deepseek-ai/dsh-permission/client'
import type { PermissionSelect as PermissionSelectValue } from '@deepseek-ai/dsh-permission-presets/client'
import { IconChevronDownOutline14, Menu, RiskConfirmation } from '@deepseek-ai/dsh-client-ui-primitives'
import type { MenuEntry } from '@deepseek-ai/dsh-client-ui-primitives'
import type { ComposerBarProps } from '../contract/slots.ts'