diff --git a/docs/subsystems/slots.md b/docs/subsystems/slots.md
index 12a8d89b8f..0a08dc4f9a 100644
--- a/docs/subsystems/slots.md
+++ b/docs/subsystems/slots.md
@@ -171,7 +171,8 @@ Factory: conversation.content [ConversationContent]
├─ conversation.hero.brand.mark
├─ conversation.hero.workspace
│ └─ conversation.hero.workspace.directoryFlow
-└─ conversation.hero.agentPreset
+├─ conversation.hero.agentPreset
+└─ conversation.hero.options
Factory: conversation.chat.user.content [UserContent]
├─ conversation.chat.user.images [MessageImages]
diff --git a/docs/subsystems/slots.zh.md b/docs/subsystems/slots.zh.md
index 77f3717f36..372f01773c 100644
--- a/docs/subsystems/slots.zh.md
+++ b/docs/subsystems/slots.zh.md
@@ -173,7 +173,8 @@ Factory: conversation.content [ConversationContent]
├─ conversation.hero.brand.mark
├─ conversation.hero.workspace
│ └─ conversation.hero.workspace.directoryFlow
-└─ conversation.hero.agentPreset
+├─ conversation.hero.agentPreset
+└─ conversation.hero.options
Factory: conversation.chat.user.content [UserContent]
├─ conversation.chat.user.images [MessageImages]
diff --git a/packages/client/ui-conversation/src/client/apply.ts b/packages/client/ui-conversation/src/client/apply.ts
index 3e34d83b4b..e567bd1a50 100644
--- a/packages/client/ui-conversation/src/client/apply.ts
+++ b/packages/client/ui-conversation/src/client/apply.ts
@@ -345,6 +345,7 @@ export function apply(ctx: Context, config: Config = Config({})): void {
'conversation.hero.brand.mark': { kind: 'single', scope: 'root' },
'conversation.hero.workspace': { kind: 'single', scope: 'root' },
'conversation.hero.agentPreset': { kind: 'single', scope: 'session-maybe' },
+ 'conversation.hero.options': { kind: 'list', scope: 'session' },
},
slots: {
views: { scope: 'session' },
diff --git a/packages/client/ui-conversation/src/client/contract/slots.ts b/packages/client/ui-conversation/src/client/contract/slots.ts
index 9abf5afb1c..a02f5aa662 100644
--- a/packages/client/ui-conversation/src/client/contract/slots.ts
+++ b/packages/client/ui-conversation/src/client/contract/slots.ts
@@ -264,6 +264,8 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
'conversation.hero.brand.mark': { kind: 'single'; scope: 'root'; owner: HeroBrandMarkOwnerProps }
/** Agent-preset control staged for a New Session. */
'conversation.hero.agentPreset': { kind: 'single'; scope: 'session-maybe'; owner: HeroAgentPresetOwnerProps }
+ /** Compact options right-aligned in the New Session row after the workspace and preset controls; rendered only with a Session. */
+ 'conversation.hero.options': { kind: 'list'; scope: 'session' }
/** Full-width entries above the composer card. */
'conversation.input.dock': { kind: 'list'; scope: 'session'; owner: InputZone }
/** Floating entries rendered inside the resident composer card. */
@@ -309,6 +311,7 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
'conversation.hero.brand.mark': { kind: 'single'; scope: 'root' }
'conversation.hero.workspace': { kind: 'single'; scope: 'root' }
'conversation.hero.agentPreset': { kind: 'single'; scope: 'session-maybe' }
+ 'conversation.hero.options': { kind: 'list'; scope: 'session' }
}
inject: ConversationInjected
locale: 'conversation'
diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx b/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx
index 15bfdd8732..218082ae31 100644
--- a/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx
+++ b/packages/client/ui-conversation/src/client/skeleton/ConversationContent.tsx
@@ -130,6 +130,7 @@ export function ConversationContent(props: ConversationContentProps) {
onClose: () => { setPickerOpen(false) },
})}
{renderSlot('conversation.hero.agentPreset', {})}
+ {sessionId === undefined ? null :
{renderSlot('conversation.hero.options', {})}
}
)
diff --git a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css
index 3aa0a9d1f9..070cb94a75 100644
--- a/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css
+++ b/packages/client/ui-conversation/src/client/skeleton/ConversationRoot.module.css
@@ -529,6 +529,20 @@ button.crumb:hover {
padding: 0 16px 0 20px;
}
+/* Plugin options share the row, pushed to its right edge. */
+.heroOptions {
+ display: flex;
+ flex: none;
+ align-items: center;
+ gap: 2px;
+ min-width: 0;
+ margin-left: auto;
+}
+
+.heroOptions:empty {
+ display: none;
+}
+
/* Hero: the composer sits inside the session scroll body; center there so
the tree seat matches active (sticky footer) without a Root remount. */
.root[data-phase='hero'] .scrollBody,
diff --git a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx
index 00a784ac96..bcd1005a30 100644
--- a/packages/client/ui-conversation/tests/skeleton.client.spec.tsx
+++ b/packages/client/ui-conversation/tests/skeleton.client.spec.tsx
@@ -2,7 +2,7 @@
import type { GlobalStandardProps, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { createContext, useContext, type ReactNode } from 'react'
-import { act, cleanup, fireEvent, render } from '@testing-library/react'
+import { act, cleanup, fireEvent, render, within } from '@testing-library/react'
import type { Context } from '@deepseek-ai/cordis'
import type { SessionListState, SessionSnapshot } from '@deepseek-ai/dsh-api-session-controller/client'
import type { WorkspaceSnapshot, WorkspaceView } from '@deepseek-ai/dsh-api-workspace-controller/client'
@@ -689,6 +689,18 @@ describe('ConversationRoot resident composer', () => {
// The agent-preset chip sits in the same row, for the same reason: both
// choices are only open before the first message.
expect(b.slotCalls).toContain('conversation.hero.agentPreset')
+ expect(b.slotCalls).toContain('conversation.hero.options')
+ })
+
+ it('seats the New Session options after the agent-preset chip, and only for a Session', () => {
+ const b = mount(sessionSnapshotOf({ blank: true }))
+ const preset = within(b.view.container).getByTestId('view-conversation.hero.agentPreset')
+ const options = within(b.view.container).getByTestId('view-conversation.hero.options')
+ expect(options.parentElement?.previousElementSibling).toBe(preset)
+
+ const cold = mount(sessionSnapshotOf(), [], undefined, { sessionId: undefined })
+ expect(cold.slotCalls).toContain('conversation.hero.agentPreset')
+ expect(cold.slotCalls).not.toContain('conversation.hero.options')
})
it('prompt failure renders the promptError strip (ordinary failure, no transaction UI)', () => {
diff --git a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
index 13795cd4df..671acc8985 100644
--- a/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
+++ b/packages/extensions/cordis-client-runner/src/client/slot-catalog.ts
@@ -649,7 +649,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'shadows-shipped-ui',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.composer.bar\', () => ctx.slots.register(\n { name: \'conversation.composer.bar\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:280',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:282',
},
{
key: 'conversation.composer.dock',
@@ -709,7 +709,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.composer.dock\', () => ctx.slots.register(\n { name: \'conversation.composer.dock\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:272',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:274',
},
{
key: 'conversation.header',
@@ -851,6 +851,63 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.hero.brand.mark\', () => ctx.slots.register(\n { name: \'conversation.hero.brand.mark\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
source: 'packages/client/ui-conversation/src/client/contract/slots.ts:264',
},
+ {
+ key: 'conversation.hero.options',
+ kind: 'list',
+ scope: 'session',
+ summary: 'Compact options right-aligned in the New Session row after the workspace and preset controls; rendered only with a Session.',
+ doc: 'Compact options right-aligned in the New Session row after the workspace and preset controls; rendered only with a Session.',
+ registerOptions: [
+ {
+ name: 'id',
+ requirement: 'required',
+ type: 'string',
+ doc: 'Your cell key. Use an id of your own: a fresh id is added beside the shipped entries, while reusing a shipped id puts you in THAT cell and replaces it. Owners that filter by id address you by it.',
+ },
+ {
+ name: 'order',
+ requirement: 'optional',
+ type: 'number',
+ doc: 'Position among the entries, ascending (default 0).',
+ },
+ {
+ name: 'label',
+ requirement: 'optional',
+ type: 'string | (() => string)',
+ doc: 'Display text where the owner projects one (nav rows, tabs). A thunk is re-read on every projection, so localized text follows the active locale without re-registering.',
+ },
+ ],
+ ownerProps: [],
+ ownerPropsReferences: [],
+ standardProps: [
+ 'useRemoteAccess: (action: RemoteEndpoint) => boolean',
+ 'useResource: UseResource',
+ 'useWorkspaces: SnapshotSelectorHook',
+ 'usePanelInfo: UsePanelInfo',
+ 'useSessions: UseSessions',
+ 'useSessionStatus: UseSessionStatus',
+ 'useSessionRetainInfo: UseSessionRetainInfo',
+ 'useSessionAccess: (sessionId: SessionId, action: RemoteEndpoint) => boolean',
+ 'useWorkspaceAccess: (workspaceId: WorkspaceId, action: RemoteEndpoint) => boolean',
+ 'useChat: UseChat',
+ 'useConversation: UseConversation',
+ 'useInput: SnapshotSelectorHook',
+ 'inputActions: InputActions',
+ 'useSession: SessionSnapshotSelector',
+ 'sessionId: SessionId',
+ 'useProjection: UseProjection',
+ 'useAccess: (action: RemoteEndpoint) => boolean',
+ 'useTrajectory: UseTrajectory',
+ ],
+ keyDomain: '',
+ hookContext: '',
+ slotInject: '',
+ declaredBy: 'factory \'conversation.content\' (client-ui-conversation), so it exists while that definition is registered',
+ occupants: [],
+ replaceRisk: 'none',
+ example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.hero.options\', () => ctx.slots.register(\n { name: \'conversation.hero.options\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:268',
+ },
{
key: 'conversation.hero.workspace',
kind: 'single',
@@ -962,7 +1019,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'shadows-shipped-ui',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.activity\', () => ctx.slots.register(\n { name: \'conversation.input.activity\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:278',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:280',
},
{
key: 'conversation.input.attachments',
@@ -1006,7 +1063,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'shadows-shipped-ui',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.attachments\', () => ctx.slots.register(\n { name: \'conversation.input.attachments\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:282',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:284',
},
{
key: 'conversation.input.dock',
@@ -1073,7 +1130,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.dock\', () => ctx.slots.register(\n { name: \'conversation.input.dock\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:268',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:270',
},
{
key: 'conversation.input.left',
@@ -1130,7 +1187,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
occupants: [],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.left\', () => ctx.slots.register(\n { name: \'conversation.input.left\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:274',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:276',
},
{
key: 'conversation.input.model',
@@ -1172,7 +1229,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'shadows-shipped-ui',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.model\', () => ctx.slots.register(\n { name: \'conversation.input.model\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:296',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:298',
},
{
key: 'conversation.input.overlay',
@@ -1233,7 +1290,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.overlay\', () => ctx.slots.register(\n { name: \'conversation.input.overlay\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:270',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:272',
},
{
key: 'conversation.input.permission',
@@ -1275,7 +1332,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'shadows-shipped-ui',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.permission\', () => ctx.slots.register(\n { name: \'conversation.input.permission\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:290',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:292',
},
{
key: 'conversation.input.plan',
@@ -1317,7 +1374,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
],
replaceRisk: 'shadows-shipped-ui',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.plan\', () => ctx.slots.register(\n { name: \'conversation.input.plan\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:288',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:290',
},
{
key: 'conversation.input.right',
@@ -1374,7 +1431,7 @@ export const CLIENT_SLOT_API: readonly ClientSlotEntry[] = [
occupants: [],
replaceRisk: 'none',
example: 'return {\n inject: [\'slots\'],\n apply(ctx) {\n ctx.slots.inject(\'conversation.input.right\', () => ctx.slots.register(\n { name: \'conversation.input.right\', id: \'my-entry\', order: 100, label: \'My entry\' },\n () => React.createElement(\'div\', null, \'hello\'),\n ))\n },\n}',
- source: 'packages/client/ui-conversation/src/client/contract/slots.ts:276',
+ source: 'packages/client/ui-conversation/src/client/contract/slots.ts:278',
},
{
key: 'conversation.plan-review.actions',