Merge remote-tracking branch 'origin/master' into codex/pr-555-ci-fix
# Conflicts: # docs/config-catalog.i18n.yaml # docs/config-catalog.md # docs/config-catalog.zh.md # docs/module-graph.i18n.yaml # docs/module-graph.md # docs/module-graph.zh.md # examples/acp-agent/tests/snapshots/cordis-inspect-jsdoc/session.jsonl # packages/client/connection/src/client/fixture.ts # packages/host/apiproxy/src/api-proxy.ts
This commit is contained in:
@@ -199,6 +199,7 @@ export function apply(ctx: Context): void {
|
||||
'conversation.input.left': { kind: 'list', scope: 'session' },
|
||||
'conversation.input.right': { kind: 'list', scope: 'session' },
|
||||
'conversation.hero.workspace': { kind: 'single', scope: 'root' },
|
||||
'conversation.hero.agentPreset': { kind: 'single', scope: 'root' },
|
||||
},
|
||||
inject: (sessionId: SessionId | undefined): ConversationInjected => ({
|
||||
hooks: { composerBlock: sessionId === undefined ? ABSENT_BLOCK : composerBlocks.storeFor(sessionId) },
|
||||
|
||||
@@ -90,6 +90,12 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
* reads the global workspace list.
|
||||
*/
|
||||
'conversation.hero.workspace': { kind: 'single'; scope: 'root'; owner: EmptyWorkspaceOwnerProps }
|
||||
/**
|
||||
* The agent-preset chip beside the workspace picker on the new-session
|
||||
* screen. Root scope: no session exists yet, so the choice is staged for
|
||||
* 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
|
||||
// this package, while this package's input contract already imports
|
||||
@@ -152,6 +158,28 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
|
||||
}
|
||||
}
|
||||
|
||||
/** Owner share of the hero agent-preset chip: the shell supplies nothing. */
|
||||
export interface HeroAgentPresetOwnerProps {
|
||||
/** Marker field: the chip owns its own roster, staging, and menu state. */
|
||||
children?: never
|
||||
}
|
||||
|
||||
/** Owner share of the strict session content seat. */
|
||||
export interface ConversationSessionOwnerProps {
|
||||
/**
|
||||
* Wrap the view ring in the transcript scrollport that also hosts the
|
||||
* sticky composer seat (whole `'conversation.composer'` chain output).
|
||||
* Supplied for every real session (hero/settling/active) so the composer
|
||||
* keeps one tree seat across the blank → active flip; the header stays
|
||||
* outside that wrapper as ordinary column chrome (`flex: none`), while
|
||||
* active CSS sticks the seat to the bottom of the same scrollport so wheel
|
||||
* over the footer scrolls the flow.
|
||||
* @param view - the session view-ring content (null while blank chrome is hidden).
|
||||
* @returns the scrollport containing `view` and the sticky composer seat.
|
||||
*/
|
||||
wrapActiveBody?: (view: ReactNode) => ReactNode
|
||||
}
|
||||
|
||||
/** Header actions derive their state from the standard session/global kit. */
|
||||
export interface ConversationHeaderActionOwnerProps {}
|
||||
|
||||
@@ -451,6 +479,7 @@ export type ConversationSlotProps =
|
||||
| 'conversation.input.dock' | 'conversation.composer.dock'
|
||||
| 'conversation.input.left' | 'conversation.input.right'
|
||||
| 'conversation.hero.workspace'
|
||||
| 'conversation.hero.agentPreset'
|
||||
>
|
||||
& InjectFace<ConversationInjected>
|
||||
& PropsLocale<'conversation'>
|
||||
|
||||
@@ -119,6 +119,7 @@ export function ConversationRoot({
|
||||
},
|
||||
onClose: () => { setPickerOpen(false) },
|
||||
})}
|
||||
{renderSlot('conversation.hero.agentPreset', {})}
|
||||
</div>
|
||||
)
|
||||
|
||||
|
||||
@@ -84,9 +84,11 @@ describe('apply wiring', () => {
|
||||
expect(conversationHeader?.store).toBe(conversationSession?.store)
|
||||
expect(details?.store).toBe(conversationSession?.store)
|
||||
expect(chatView?.store).toBe(conversationSession?.store)
|
||||
// The hero workspace picker hole rides the conversation entry's children
|
||||
// declaration (the empty-state occupant is gone).
|
||||
// The hero holes ride the conversation entry's children declaration (the
|
||||
// empty-state occupant is gone). Both are root-scoped: the new-session
|
||||
// screen precedes the session either would belong to.
|
||||
expect(b.slots.spec('conversation.hero.workspace')).toEqual({ kind: 'single', scope: 'root' })
|
||||
expect(b.slots.spec('conversation.hero.agentPreset')).toEqual({ kind: 'single', scope: 'root' })
|
||||
expect(b.slots.entries('settings.general.item').map(entry => entry.options.id)).toEqual(['composer-enter'])
|
||||
await b.runtime.dispose()
|
||||
})
|
||||
|
||||
@@ -788,13 +788,15 @@ describe('strips and variants', () => {
|
||||
})
|
||||
|
||||
describe('command launcher chrome and control seats', () => {
|
||||
it('renders the command launcher; the Access chip is absent without the permissions projection; plan/model seats render EMPTY without entries', () => {
|
||||
it('renders the command launcher; the Access chip is absent without the permissions projection; the control seats render EMPTY without entries', () => {
|
||||
const { view, slotCalls } = bench()
|
||||
expect(view.getByLabelText('命令')).toBeTruthy()
|
||||
// Capability absent (no projection value): the chip renders nothing.
|
||||
expect(view.queryByLabelText(/^访问模式/)).toBeNull()
|
||||
// Both seats dispatched, nothing rendered.
|
||||
expect(slotCalls.map(c => c.key)).toEqual(['conversation.input.plan', 'conversation.input.model'])
|
||||
// Every seat dispatched, nothing rendered.
|
||||
expect(slotCalls.map(c => c.key)).toEqual([
|
||||
'conversation.input.plan', 'conversation.input.model',
|
||||
])
|
||||
expect(view.queryByLabelText('Plan mode')).toBeNull()
|
||||
expect(view.queryByLabelText('Model')).toBeNull()
|
||||
})
|
||||
|
||||
@@ -457,6 +457,9 @@ describe('ConversationRoot resident composer', () => {
|
||||
const chip = b.view.getByRole('button', { name: '选择工作区' })
|
||||
expect((chip as HTMLButtonElement).disabled).toBe(false)
|
||||
expect(b.slotCalls).toContain('conversation.hero.workspace')
|
||||
// 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')
|
||||
})
|
||||
|
||||
it('prompt failure renders the promptError strip (ordinary failure, no transaction UI)', () => {
|
||||
|
||||
Reference in New Issue
Block a user