Merge branch 'stack/agent-profiles-5-web-ui' into stack/agent-profiles-8-authoring

# Conflicts:
#	apps/cli/tests/web-agent-presets.e2e.ts
#	packages/client/connection/README.i18n.yaml
#	packages/client/connection/README.md
#	packages/client/connection/README.zh.md
#	packages/client/ui-conversation/src/client/contract/slots.ts
#	packages/client/ui-primitives/tests/icons.spec.tsx
#	packages/client/ui-settings/src/client/contract/slots.ts
#	packages/host/apiproxy/tests/api-proxy-agent-preset.spec.ts
This commit is contained in:
Yichen Jiang
2026-08-09 23:02:37 +08:00
825 changed files with 2990 additions and 2066 deletions

View File

@@ -160,7 +160,7 @@ export function apply(ctx: Context): void {
// way: this package must not import the plugins that would know.
const composerBlocks = new ComposerBlockRegistry()
// Decision 19/20: the input machine feeds every session-scope slot
// The input machine feeds every session-scope slot
// component through the standard provide channel — the 'input' hook plus
// the two public actions. Materialization is the shell creation trigger
// (per-session lazy; scope disposer tears down).
@@ -242,7 +242,7 @@ export function apply(ctx: Context): void {
}, ConversationSessionHeader)
// The default composer body: its own single slot inside the composer
// chain's fallback (decision 20). Public machine surface arrives via the
// chain's fallback. Public machine surface arrives via the
// provide channel above; the keyboard command face and the stop/retry
// verbs ride this inject (package-internal — hub and bar are one plugin).
// Session-maybe: with no current session the machine faces are absent and
@@ -253,7 +253,7 @@ export function apply(ctx: Context): void {
locale: NS,
// The two named control seats in the bar's tool row (plan beside the
// access control, model right); empty until their owning plugins
// register (B ruling).
// register.
children: {
'conversation.input.plan': { kind: 'single', scope: 'session' },
'conversation.input.model': { kind: 'single', scope: 'session' },
@@ -383,7 +383,7 @@ export function apply(ctx: Context): void {
// The plan strip rides the input dock above the queue rows (same posture).
ctx.plugin(todoDockEntry)
// The read-only queue dock entry (T9 file territory) rides the same
// The read-only queue dock entry rides the same
// registration path into the input dock declared above.
ctx.plugin(queueDockEntry)

View File

@@ -180,7 +180,7 @@ export function ChatView({
const lastKeyRef = useRef<string | null>(null)
const lastSteeringIdRef = useRef<string | null>(null)
/** Flow tip signature — follow-scroll only when this moves, never on a
* scroll-driven at-bottom chrome re-render (that was snapping inertial
* scroll-driven at-bottom chrome re-render (which would snap inertial
* scrolls the rest of the way to the floor). */
const followSigRef = useRef<string | null>(null)

View File

@@ -123,8 +123,8 @@ function TurnErrorItem({ node, t }: {
* Display projection of reference forms in a user bubble (free geometry — no
* textarea alignment constraint here); everything else stays plain text. The
* logged model text remains the single truth; this is presentation only.
* Plain-text `/name` / `@name` word-boundary tokens decorate (decision 21:
* the sent text IS the reference — the bubble uses the same plainest token
* Plain-text `/name` / `@name` word-boundary tokens decorate (the sent text
* IS the reference — the bubble uses the same plainest token
* scan as the composer, minus the lexicon: sent tokens were validated at
* compose time, so shape alone decorates).
*/

View File

@@ -85,14 +85,14 @@ 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 (dedup ruling: the
// dependency direction is the hard constraint — ui-slash cannot import
// 'conversation.input.overlay' merges in ui-slash (the dependency
// direction is the hard constraint — ui-slash cannot import
// this package, while this package's input contract already imports
// ui-slash, 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;
* design §6 MIX evidence: entries coexist in fixed order).
* entries coexist in fixed order).
*/
'conversation.input.dock': { kind: 'list'; scope: 'session'; owner: InputZone }
/** The band under the composer card (stats line family), rendered inside the bar's width column via the `footer` owner prop. */
@@ -103,7 +103,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
'conversation.input.right': { kind: 'list'; scope: 'session'; owner: InputZone }
/**
* The default composer body: a single slot rendered as the composer
* chain's fallback (decision 20 — a real entry, not a chain rider, so a
* chain's fallback (a real entry, not a chain rider, so a
* takeover election hides rather than unmounts it and the textarea DOM
* survives). Session-maybe: the bar stays mounted across the
* no-session/session transition — the no-workspace hero renders the SAME
@@ -117,7 +117,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
/**
* The Plan-mode status seat in the composer tool row (left group,
* right of the access-mode control). Declared by the composer-bar
* entry; empty until a plan plugin registers (B ruling: no placeholder
* entry; empty until a plan plugin registers (no placeholder
* fallback).
*/
'conversation.input.plan': { kind: 'single'; scope: 'session'; owner: InputControlOwnerProps }
@@ -130,7 +130,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
/**
* ui-conversation's members of the session standard kit, provided through
* `sessions.provide` (decision 19/20): every session-scope slot component
* `sessions.provide`: every session-scope slot component
* receives the input machine's state hook and the two public actions.
*/
interface SessionStandardProps {
@@ -173,7 +173,7 @@ export interface ConversationSessionOwnerProps {
export interface ConversationHeaderActionOwnerProps {}
/**
* The input-region slot currency (plan §1.4): dock/left/right entries read
* The input-region slot currency: dock/left/right entries read
* the conversation snapshot and the live input state as owner props (both
* are point-in-time snapshots — the dispatching skeleton re-renders on
* either store's change, so entries stay current without subscribing).
@@ -381,7 +381,7 @@ export interface ComposerBarOwnerProps {
/** Injected share of the composer-bar entry (package-internal faces). */
export interface ComposerBarInjected {
/** The InputBar-exclusive keyboard/DOM command face (decision 20 private plane); absent with the session. */
/** The InputBar-exclusive keyboard/DOM command face (private plane); absent with the session. */
keyboard: ComposerKeyboard | undefined
/** Resolve one keyboard submission gesture against the current running state and persisted preference. */
resolveSubmitMode: (
@@ -408,7 +408,8 @@ export interface ComposerBarInjected {
hooks: {
/** Latest surfaced notice (null after none; seq keys re-render of repeats). */
notices: ObservableSnapshot<InputNotice | null>
/** Hot plain-text reference lexicon for the decoration scan (decision 21). */
/** Hot plain-text reference lexicon for the decoration scan (plain-text-reference decision;
* see .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md). */
lexicon: ObservableSnapshot<ReadonlyMap<'/' | '@', readonly string[]>>
/** Source name opened by the programmatic menu launcher, or null. */
menuLauncher: ObservableSnapshot<string | null>

View File

@@ -1,5 +1,5 @@
/**
* Frozen input-machine contract (design §9.1, eng. plan §3.9-3.12). Types
* Frozen input-machine contract. Types
* only. Three-tier visibility: business packages see InputState via the
* InputZone currency; the scoped input events carry the mutation verbs; the
* conversation wiring layer alone sees the full SessionInput. InputMachine
@@ -56,7 +56,7 @@ export interface InputService {
/**
* The public input action face provided to every session-scope slot
* component (decision 20): two stable-identity void callbacks, mirroring the
* component: two stable-identity void callbacks, mirroring the
* useStore+actions convention. Command-style handles (track/arbitrate/space/
* undo/paste/…) stay InputBar-private and never ride this face.
*/
@@ -75,7 +75,7 @@ export interface InputNotice {
}
/**
* The InputBar-exclusive keyboard/DOM command face (decision 20): synchronous
* The InputBar-exclusive keyboard/DOM command face: synchronous
* returns and event-handler semantics that must not enter the public provide
* channel. Handed to the composer-bar entry through its own inject —
* package-internal, never across a plugin boundary. The session shell
@@ -128,7 +128,7 @@ export interface EditRange extends EditSelection {
/**
* One reference chip occurrence, backing exactly one U+FFFC placeholder in
* the draft (design §9.1 底层表示). Identity is occurrenceId — same-named
* the draft. Identity is occurrenceId — same-named
* references stay independently addressable. label/clipboardText are the
* owner's insert-time projections, cached so the chip survives owner loss
* (invalid flips instead of dropping the occurrence).
@@ -157,7 +157,7 @@ export interface PasteComponent extends EditSelection {
/**
* Live paste-match attempt published while async matching may still upgrade
* pasted tokens (design §9.1 剪贴板 round-trip). Any non-paste transaction,
* pasted tokens (the clipboard round-trip). Any non-paste transaction,
* submit start, invalidate-paste, or release ends it; a paste-upgrade keeps
* it current (later tokens re-CAS against the advanced draftRev).
*/

View File

@@ -1,5 +1,5 @@
/**
* Draft decoration pure core (design §9.1: chips render from the occurrence
* Draft decoration pure core (chips render from the occurrence
* table at placeholder offsets; the claim token renders as a mirror-layer
* highlight, the claim hint as ghost text). Zero React — the skeleton renders
* the instructions; tests drive this directly.
@@ -24,7 +24,9 @@ export interface ChipRender {
}
/**
* One plain-text reference range (decision 21): a `/name` or `@name` token
* One plain-text reference range (the plain-text-reference decision;
* see .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md):
* a `/name` or `@name` token
* whose name is on the trigger's lexicon. Pure derivation — editing the text
* out of match shape simply drops the range next scan.
*/
@@ -50,8 +52,8 @@ export interface DraftDecorations {
const TEXT_REF_RE = /(^|\s)([/@])([\w-]+)/g
/**
* Scan the draft for plain-text reference tokens against the hot lexicons
* (decision 21). Word-boundary discipline: the trigger must sit at the draft
* Scan the draft for plain-text reference tokens against the hot lexicons.
* Word-boundary discipline: the trigger must sit at the draft
* start or after whitespace ('x/name' never matches); the name must be an
* exact lexicon member.
* @param draft - draft text.
@@ -82,7 +84,7 @@ const EMPTY_LEXICON: ReadonlyMap<'/' | '@', readonly string[]> = new Map()
/**
* Derive the mirror-layer decorations from the input state.
* @param state - published input state.
* @param lexicon - optional per-trigger reference lexicons (decision 21 scan).
* @param lexicon - optional per-trigger reference lexicons (plain-text-reference scan).
* @returns token range, chip instructions, text-ref ranges, and the ghost hint.
*/
export function deriveDecorations(

View File

@@ -66,7 +66,7 @@ export class SessionInputShell implements SessionInput {
readonly state: SnapshotStore<InputState>
/** Latest surfaced notice (null after clear); the wiring renders it beside the error strip. */
readonly notices: SnapshotStore<InputNotice | null> = createSnapshotStore<InputNotice | null>(null)
/** The public provide-channel action face (one stable identity per session — decision 20). */
/** The public provide-channel action face (one stable identity per session). */
readonly actions: InputActions = {
setDraft: (text) => { this.setDraft(text) },
submit: () => { this.submit('queue') },
@@ -198,7 +198,9 @@ export class SessionInputShell implements SessionInput {
/**
* Hot plain-text reference lexicon source for the decoration scan
* (decision 21): delegates to the controller's aggregated store. Stable
* (the plain-text-reference decision;
* see .agents/notes/implemented/architecture/2026-07-25-web-input-machine-and-slash-pipeline.md):
* delegates to the controller's aggregated store. Stable
* identity per shell; without a pipeline the snapshot is the empty Map and
* subscribers never fire.
*/
@@ -253,7 +255,8 @@ export class SessionInputShell implements SessionInput {
/**
* Insert plain reference text over the pick-time span (scoped insert-text
* event listener body, decision 21). Same CAS-then-splice shape as the
* event listener body; plain-text-reference decision, web-input-machine
* note). Same CAS-then-splice shape as the
* consume-token span branch: the machine sees an ordinary draft-changed
* transaction (one undo step), no occurrence is minted — the chip look is
* a scan-derived decoration, never state.
@@ -339,7 +342,7 @@ export class SessionInputShell implements SessionInput {
}
/**
* Prompt serialization before the sink (design §3.12): expand each
* Prompt serialization before the sink: expand each
* placeholder to its owner's model form via the session controller's
* codec routing. Owner missing / serialize failure / disposal blocks the
* send — notice + draft and chips retained, never a silent downgrade to

View File

@@ -1,7 +1,7 @@
/**
* InputHub: the InputService implementation (`ctx.conversation.input`) — one
* SessionInputShell per session, created inside the sessions provide
* materialization (decision 19: the 'input' standard-kit entry IS the
* 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
@@ -61,7 +61,7 @@ export class InputHub implements InputService {
})
this.shells.set(id, shell)
// The one teardown axis: listeners, shell, and map entries all ride the
// scope fiber (decision 12 — nothing here outlives the scope).
// scope fiber (nothing here outlives the scope).
actx.effect(() => {
const offs = [
actx.on('slash/input-begin-command', req =>
@@ -97,7 +97,7 @@ export class InputHub implements InputService {
}
/**
* The InputBar-exclusive keyboard command face (decision 20): the shell
* The InputBar-exclusive keyboard command face: the shell
* satisfies it structurally; package-internal — handed through the
* composer-bar entry's inject, never across a plugin boundary.
* @param id - session id.

View File

@@ -1,6 +1,6 @@
/**
* InputMachine: the pure per-session input state machine (design §9.1, eng.
* plan §3.9-3.12). Events in, effects out; zero React / DOM / cordis / ambient
* InputMachine: the pure per-session input state machine.
* Events in, effects out; zero React / DOM / cordis / ambient
* clock. Package-private — the SessionInput shell is the only caller and the
* sole executor of the returned effects.
*
@@ -23,10 +23,10 @@ import type {
/** The object-replacement character backing every chip occurrence in the draft. */
export const PLACEHOLDER = ''
/** The machine never writes the queue; the wiring layer overlays the T9 store projection. */
/** The machine never writes the queue; the wiring layer overlays the queue store's projection. */
const EMPTY_QUEUE: InputState['queue'] = []
/** Undo ring depth (design §9.1: bounded self-managed transaction log). */
/** Undo ring depth (bounded self-managed transaction log). */
const LOG_LIMIT = 100
/** Exhaustiveness backstop for the closed InputEvent / guard unions. */
@@ -68,7 +68,7 @@ function diffEdit(prev: string, next: string): EditRange {
/**
* Expand the draft's placeholders into their occurrences' clipboard text
* (decision 16: the persistence mirror and clipboard both write this
* (the persistence mirror and clipboard both write this
* projection — U+FFFC never leaves the machine). Table order is offset
* order, so one linear walk pairs placeholders with entries.
* @param state - published input state.
@@ -194,7 +194,7 @@ export class InputMachine {
/**
* Reconcile the occurrence table with one edit (old-draft coordinates):
* entries past the range shift by the length delta; entries whose
* placeholder sits inside the replaced range go away whole (design §9.1: a
* placeholder sits inside the replaced range go away whole (a
* deletion/replacement intersecting a placeholder acts on the whole chip).
*/
private reconcile(range: EditRange): void {
@@ -338,7 +338,7 @@ export class InputMachine {
/**
* Owner-resolution style bits: exactly the listed occurrences render
* invalid. Not a transaction — the draft, revision, and undo log are
* untouched (design §9.1: invalidation never deletes or rewrites chips).
* untouched (invalidation never deletes or rewrites chips).
*/
private onSetInvalid(invalidIds: readonly number[]): InputEffect[] {
const ids = new Set(invalidIds)

View File

@@ -10,7 +10,7 @@ import type { QueuedMessage } from '../input/contract.ts'
/**
* Project a session's transient inbox rows as a bare observable (subscribe/getSnapshot).
* The wiring layer (T5) overlays this onto InputState.queue; the runtime
* The wiring layer overlays this onto InputState.queue; the runtime
* QueuedMessage and the input-contract QueuedMessage are structurally
* identical.
* @param session - the resident session face.

View File

@@ -57,7 +57,7 @@ export interface IConversation {
/** Scope-addressed conversation service (root singleton, provided as `conversation`). */
export class ConversationService extends Service implements IConversation {
/** The per-session input machine registry (InputService face, design §5.2). */
/** The per-session input machine registry (InputService face). */
readonly input: InputService
/** The per-session composer-block registry. */
readonly blocks: ComposerBlocks

View File

@@ -10,8 +10,7 @@
// buttons must be reachable no matter how long the command is.
// One-shot: the buttons disable
// after a click and the panel leaves (the InputBar returns) on the broadcast
// resolved frame. The draft's "Always allow this type" is deferred with
// grant storage.
// resolved frame.
import { useMemo, useState } from 'react'
import { Button } from '@deepseek-ai/dsh-client-ui-primitives'

View File

@@ -1,8 +1,8 @@
/** Composer context-occupancy meter: a ring beside the send button fed by the
* `contextPressure` projection, with a click-open panel of the heuristic
* `contextBreakdown` composition (system prompt, tools, conversation).
* Renders nothing until a provider reports both pressure and a route capacity
* (same gate as the stats row used). */
* Renders nothing until a provider reports both pressure and a route
* capacity. */
import { useEffect, useRef, useState } from 'react'
import type { UseProjection } from '@deepseek-ai/dsh-client-runtime/client'

View File

@@ -81,7 +81,6 @@ export function ConversationRoot({
const zone: InputZone | undefined =
session === undefined || inputState === undefined ? undefined : { session, input: inputState }
// Flow optimization — worth a close PR review for code/boundary issues.
// The chip is a selector; label resolution walks the flow top-down:
// 1. a just-picked workspace (pending) → its title;
// 2. cold start, no session yet → placeholder ("Choose workspace");

View File

@@ -1,4 +1,4 @@
/* Details third column, minimal P-I fill: header (name + close) over a
/* Details third column, minimal fill: header (name + close) over a
scrolling body of Input/Output code sections. Panel width/squeeze belongs
to layout; this fills whatever the column gives. */

View File

@@ -1,7 +1,6 @@
// DetailsPanel, P-I minimal form: close button + the selected call's args and
// DetailsPanel: close button + the selected call's args and
// result — args as JSON, the result raw except for a terminal-card call, whose
// Output section is the command's terminal card. The three-段 Switch /
// Prev-Next stepping / See-in-trajectory are deferred (ledger). Reads the
// Output section is the command's terminal card. Reads the
// selection from the shared chat
// store (conversation writes, this panel reads — the cross-registration
// share the store seat exists for) and derives the call material from the

View File

@@ -225,9 +225,9 @@
so by construction now that all three sit INSIDE .scroll — a scrollbar
that consumes layout space narrows the scrollport, which is their shared
containing block, so it costs all three the same width on every engine.
Scrolling the textarea itself is what used to break this, and no property
fixed it: WebKit reserved gutter space for the overflow-y:auto textarea
and not for the overflow:hidden layers beside it, leaving them 8px apart
A textarea that scrolls itself would break this, and no property fixes
it: WebKit reserves gutter space for an overflow-y:auto textarea and not
for the overflow:hidden layers beside it, leaving them 8px apart
(768 against 776) — worth 2 to 5 wrapped lines on a long draft. */
}
@@ -395,7 +395,7 @@
cursor: pointer;
}
/* Plain-text reference highlight (decision 21): a pure range mark over the
/* Plain-text reference highlight: a pure range mark over the
draft's own glyphs — advance untouched, so the two layers cannot drift.
Chip family colors; clone keeps rounded ends on soft-wrap fragments. */
.textRef {

View File

@@ -1,5 +1,5 @@
/** The default composer body: the 'conversation.composer.bar' slot entry
* (decision 20). Machine state arrives through the standard provide channel
/** The default composer body: the 'conversation.composer.bar' slot entry.
* Machine state arrives through the standard provide channel
* (useInput + inputActions); the keyboard/DOM command face and stop arrive
* through this entry's own inject, whose hooks compartment binds
* useNotices/useLexicon; layout-phase inputs (variant, placeholder,
@@ -87,7 +87,7 @@ export function InputBar({
// but its independent Stop below stays available while it runs.
const continuable = subagent?.address.mode === 'continuable'
const parentOffline = continuable && !subagent.parentAvailable
// Queue cut 1: running input stays free; locked = session removed, the
// Running input stays free; locked = session removed, the
// inert no-workspace state, the machine faces absent (no session), or a
// parent-offline continuable child. An owner block also disables input;
// adjudicating and submitting render read-only so the draft stays visible.
@@ -385,7 +385,7 @@ export function InputBar({
const backdrop: ReactNode[] = []
{
// Segment boundaries: the token range end, every chip offset, and every
// text-ref range (decision 21) — merged in draft order (the sources never
// text-ref range — merged in draft order (the sources never
// overlap: chips sit on placeholders, text-refs on plain tokens, the
// claim token only leads).
let cursor = 0
@@ -430,7 +430,7 @@ export function InputBar({
)
cursor = chip.offset + 1 // the placeholder char the chip stands for
} else {
// Plain-range highlight (decision 21): the glyphs stay the
// Plain-range highlight: the glyphs stay the
// textarea's (advance untouched); the mark paints the chip look.
backdrop.push(
<mark key={`ref-${b.ref.start}`} className={css.textRef} data-decoration="text-ref">
@@ -529,7 +529,6 @@ export function InputBar({
{rightItems}
{renderSlot('conversation.input.model', { locked: modelSeatLocked })}
<ContextMeter useProjection={useProjection} t={t} />
{/* {machineBusy && <span className={css.pending} data-input-pending aria-label="处理中" />} */}
{interruptible && (
<Tooltip label={t('input.stop')} side="top" delayMs={500}>
<button