fix(web): allow follow-ups to running subagents
This commit is contained in:
@@ -285,13 +285,14 @@ export function InputBar({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const ordinary = subagent === null
|
const ordinary = subagent === null
|
||||||
const primaryLabel = running && ordinary ? t('input.stop') : t('input.send')
|
const stopping = running && ordinary
|
||||||
|
const primaryLabel = stopping ? t('input.stop') : t('input.send')
|
||||||
const onPrimary = (): void => {
|
const onPrimary = (): void => {
|
||||||
if (inputActions === undefined || stop === undefined) return // absent machine: the button is disabled
|
if (stopping) {
|
||||||
if (running && ordinary) {
|
stop?.()
|
||||||
stop()
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (inputActions === undefined) return // absent machine: the button is disabled
|
||||||
/* v8 ignore next -- defensive: the primary button is disabled while empty||disabled, so a click cannot reach the false arm. */
|
/* v8 ignore next -- defensive: the primary button is disabled while empty||disabled, so a click cannot reach the false arm. */
|
||||||
if (!empty && !disabled && !machineBusy) inputActions.submit()
|
if (!empty && !disabled && !machineBusy) inputActions.submit()
|
||||||
}
|
}
|
||||||
@@ -467,11 +468,11 @@ export function InputBar({
|
|||||||
className={css.primary}
|
className={css.primary}
|
||||||
aria-label={primaryLabel}
|
aria-label={primaryLabel}
|
||||||
title={primaryLabel}
|
title={primaryLabel}
|
||||||
disabled={!running && (empty || disabled || machineBusy)}
|
disabled={stopping ? stop === undefined : empty || disabled || machineBusy}
|
||||||
onMouseDown={keepFocus}
|
onMouseDown={keepFocus}
|
||||||
onClick={onPrimary}
|
onClick={onPrimary}
|
||||||
>
|
>
|
||||||
{running ? (
|
{stopping ? (
|
||||||
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden>
|
<svg viewBox="0 0 16 16" width="16" height="16" aria-hidden>
|
||||||
<rect x="3" y="3" width="10" height="10" rx="3" fill="currentColor" />
|
<rect x="3" y="3" width="10" height="10" rx="3" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ interface BenchOptions {
|
|||||||
permissions?: { options: { value: string; name: string; description?: string }[]; currentValue: string }
|
permissions?: { options: { value: string; name: string; description?: string }[]; currentValue: string }
|
||||||
draft?: string
|
draft?: string
|
||||||
running?: boolean
|
running?: boolean
|
||||||
|
subagent?: Exclude<ConversationSnapshot['subagent'], null>
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
promptError?: ConversationSnapshot['promptError']
|
promptError?: ConversationSnapshot['promptError']
|
||||||
variant?: 'hero' | 'composer'
|
variant?: 'hero' | 'composer'
|
||||||
@@ -76,6 +77,7 @@ function bench(over?: BenchOptions) {
|
|||||||
if (over?.draft !== undefined && over.draft !== '') shell.setDraft(over.draft)
|
if (over?.draft !== undefined && over.draft !== '') shell.setDraft(over.draft)
|
||||||
const session = createSnapshotStore<ConversationSnapshot>(snapshotOf({
|
const session = createSnapshotStore<ConversationSnapshot>(snapshotOf({
|
||||||
running: over?.running ?? false,
|
running: over?.running ?? false,
|
||||||
|
subagent: over?.subagent ?? null,
|
||||||
removed: over?.disabled ?? false,
|
removed: over?.disabled ?? false,
|
||||||
promptError: over?.promptError ?? null,
|
promptError: over?.promptError ?? null,
|
||||||
}))
|
}))
|
||||||
@@ -124,8 +126,9 @@ function bench(over?: BenchOptions) {
|
|||||||
const view = render(<InputBar {...props} />)
|
const view = render(<InputBar {...props} />)
|
||||||
const textarea = view.container.querySelector('textarea')!
|
const textarea = view.container.querySelector('textarea')!
|
||||||
// aria-label (not role name): title carries the same label and would double-match.
|
// aria-label (not role name): title carries the same label and would double-match.
|
||||||
|
const stopping = over?.running === true && over.subagent === undefined
|
||||||
const button = view.container.querySelector<HTMLButtonElement>(
|
const button = view.container.querySelector<HTMLButtonElement>(
|
||||||
`button[aria-label="${over?.running === true ? '停止生成' : '发送消息'}"]`,
|
`button[aria-label="${stopping ? '停止生成' : '发送消息'}"]`,
|
||||||
)!
|
)!
|
||||||
return { view, textarea, button, props, sink, shell, wiring: shell, session, stop, slotCalls, menuLauncher }
|
return { view, textarea, button, props, sink, shell, wiring: shell, session, stop, slotCalls, menuLauncher }
|
||||||
}
|
}
|
||||||
@@ -208,6 +211,38 @@ describe('running and lock semantics (queue cut 1)', () => {
|
|||||||
expect(stop).toHaveBeenCalledTimes(1)
|
expect(stop).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('running subagent primary admits a follow-up instead of exposing Stop', () => {
|
||||||
|
const { button, sink, stop } = bench({
|
||||||
|
running: true,
|
||||||
|
draft: '后续消息',
|
||||||
|
subagent: {
|
||||||
|
address: {
|
||||||
|
parentSessionId: 'parent' as SessionId,
|
||||||
|
childSessionId: SID,
|
||||||
|
mode: 'continuable',
|
||||||
|
},
|
||||||
|
parentAvailable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(button.getAttribute('aria-label')).toBe('发送消息')
|
||||||
|
fireEvent.click(button)
|
||||||
|
expect(sink).toHaveBeenCalledWith('后续消息')
|
||||||
|
expect(stop).not.toHaveBeenCalled()
|
||||||
|
|
||||||
|
const empty = bench({
|
||||||
|
running: true,
|
||||||
|
subagent: {
|
||||||
|
address: {
|
||||||
|
parentSessionId: 'parent' as SessionId,
|
||||||
|
childSessionId: SID,
|
||||||
|
mode: 'continuable',
|
||||||
|
},
|
||||||
|
parentAvailable: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
expect(empty.button.disabled).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
it('disabled (session removed) locks the textarea and chrome', () => {
|
it('disabled (session removed) locks the textarea and chrome', () => {
|
||||||
const { textarea, view } = bench({ disabled: true })
|
const { textarea, view } = bench({ disabled: true })
|
||||||
expect(textarea.disabled).toBe(true)
|
expect(textarea.disabled).toBe(true)
|
||||||
|
|||||||
Reference in New Issue
Block a user