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:
creatixchu
2026-08-10 12:33:36 +08:00
331 changed files with 16610 additions and 557 deletions

View File

@@ -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) },

View File

@@ -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'>

View File

@@ -119,6 +119,7 @@ export function ConversationRoot({
},
onClose: () => { setPickerOpen(false) },
})}
{renderSlot('conversation.hero.agentPreset', {})}
</div>
)

View File

@@ -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()
})

View File

@@ -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()
})

View File

@@ -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)', () => {