mirror of
https://github.com/stablyai/orca.git
synced 2026-10-30 16:47:34 +00:00
fix(native-chat): put Continue in the card that says the reply stopped (#27097)
* fix(native-chat): put Continue in the card that says Orca stopped the reply Continue sat in its own row above the message box, away from the reply it continues and often outside the chat column. It now sits inside the transcript's notice card that says why the reply stopped, as a filled primary button beside that sentence. * fix(native-chat): one Continue that the stop card never repeats - A paired server with no name is called "the remote server", so the card words the stop from its cause instead of the host's stored "You can continue in this conversation." beside the Continue button. - Continue is an xs primary button that fits the text row without a margin workaround. - continueNow is a plain useCallback: its inputs (target, session id, the composer error reporter) are already stable. - Test the real chain: a cut journal through the structured session shows exactly one Continue, inside the stop card.
This commit is contained in:
@@ -42,7 +42,7 @@ import type {
|
||||
} from '../../../../shared/agent-session-journal-types'
|
||||
import type { RuntimeClientTarget } from '@/runtime/runtime-client-target'
|
||||
import {
|
||||
NativeChatInterruptedContinue,
|
||||
NativeChatInterruptedContinueButton,
|
||||
useNativeChatInterruptedContinuation
|
||||
} from './NativeChatInterruptedContinue'
|
||||
|
||||
@@ -78,7 +78,7 @@ function Harness(props: Props): React.JSX.Element {
|
||||
// The chat's own composer error, as NativeChatStructuredSession holds it.
|
||||
const [composerError, setComposerError] = useState<string | null>(null)
|
||||
const continuation = useNativeChatInterruptedContinuation({
|
||||
composer: { clearError: () => setComposerError(null) },
|
||||
reportComposerError: setComposerError,
|
||||
target: props.target ?? PAIRED,
|
||||
sessionId: 'session-1',
|
||||
journalItems: props.journalItems ?? cutChat,
|
||||
@@ -87,14 +87,17 @@ function Harness(props: Props): React.JSX.Element {
|
||||
})
|
||||
return (
|
||||
<TooltipProvider delayDuration={0}>
|
||||
<span data-testid="offered">{continuation.offeredTurnItemId ?? 'none'}</span>
|
||||
<span data-testid="offered">{continuation.view.offeredTurnItemId ?? 'none'}</span>
|
||||
<span data-testid="available">{String(continuation.view.continueAvailable)}</span>
|
||||
<span data-testid="remote">{String(continuation.view.remoteHost)}</span>
|
||||
<span data-testid="error">{composerError ?? continuation.continueError?.text ?? 'none'}</span>
|
||||
<button type="button" onClick={() => setComposerError(ATTACHMENTS)}>
|
||||
compose
|
||||
</button>
|
||||
<NativeChatInterruptedContinue continuation={continuation} />
|
||||
{/* As the cut's own row shows it. */}
|
||||
{continuation.view.offeredTurnItemId === null ? null : (
|
||||
<NativeChatInterruptedContinueButton onContinue={continuation.view.continueNow} />
|
||||
)}
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useId, useMemo, useState } from 'react'
|
||||
import { useCallback, useId, useMemo, useState } from 'react'
|
||||
import { Play } from 'lucide-react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
@@ -21,14 +21,12 @@ import type { NativeChatComposerNoticeContent } from './native-chat-composer-not
|
||||
type ContinueAnswer = { outcome?: string }
|
||||
|
||||
export type NativeChatInterruptedContinuation = {
|
||||
/** Told to the chat's rows: the machine's name, whether it is remote, and if it can continue. */
|
||||
/** Told to the chat's rows: the machine's name, whether it is remote, if it can continue, and the
|
||||
* cut row that carries Continue. */
|
||||
view: NativeChatOrcaStopView
|
||||
/** The cut turn Continue is offered on right now, if any. */
|
||||
offeredTurnItemId: string | null
|
||||
/** A Continue that did not go through, for the composer's notice card: shown while the chat
|
||||
* still sits on that cut and gone once it is continued from anywhere. */
|
||||
continueError: (NativeChatComposerNoticeContent & { onDismiss: () => void }) | null
|
||||
continueNow: () => void
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,10 +40,10 @@ export function useNativeChatInterruptedContinuation(input: {
|
||||
journalItems: readonly AgentJournalRenderItem[]
|
||||
submissions: readonly Pick<AgentJournalSubmission, 'dispatchState'>[]
|
||||
isWorking: boolean
|
||||
/** The composer's own error; a Continue click is the user's newer action, so it clears it. */
|
||||
composer: { clearError: () => void }
|
||||
/** Stable; a Continue click is the user's newer action, so it clears the composer's error. */
|
||||
reportComposerError: (text: null) => void
|
||||
}): NativeChatInterruptedContinuation {
|
||||
const { target, sessionId } = input
|
||||
const { target, sessionId, reportComposerError } = input
|
||||
const hostLabel = useStructuredAgentSessionHostLabel(target)
|
||||
const capability = useStructuredAgentSessionHostCapabilityState(
|
||||
target,
|
||||
@@ -66,7 +64,7 @@ export function useNativeChatInterruptedContinuation(input: {
|
||||
capability === 'supported' && cut && !input.isWorking && !resuming && asked !== cut.turnItemId
|
||||
? cut.turnItemId
|
||||
: null
|
||||
const continueNow = (): void => {
|
||||
const continueNow = useCallback((): void => {
|
||||
if (offered === null) {
|
||||
return
|
||||
}
|
||||
@@ -78,19 +76,25 @@ export function useNativeChatInterruptedContinuation(input: {
|
||||
}
|
||||
setAsked(turnItemId)
|
||||
setFailedOn(null)
|
||||
input.composer.clearError()
|
||||
reportComposerError(null)
|
||||
void callStructuredAgentSession<ContinueAnswer>(target, 'agentSession.continueInterrupted', {
|
||||
sessionId,
|
||||
turnItemId
|
||||
}).then((answer) => (answer.outcome === 'refused' ? failed() : undefined), failed)
|
||||
}
|
||||
}, [offered, target, sessionId, reportComposerError])
|
||||
// Unknown counts as able: a host that writes cause rows has Continue, and the words stay put.
|
||||
const continueAvailable = capability !== 'unsupported'
|
||||
// One object per change, so the chat's rows re-render only when what they show changes.
|
||||
const remoteHost = target.kind === 'environment'
|
||||
const view = useMemo(
|
||||
() => ({ hostLabel, remoteHost, continueAvailable }),
|
||||
[hostLabel, remoteHost, continueAvailable]
|
||||
() => ({
|
||||
hostLabel,
|
||||
remoteHost,
|
||||
continueAvailable,
|
||||
offeredTurnItemId: offered,
|
||||
continueNow
|
||||
}),
|
||||
[hostLabel, remoteHost, continueAvailable, offered, continueNow]
|
||||
)
|
||||
const failedHere =
|
||||
failedOn !== null && cut?.turnItemId === failedOn
|
||||
@@ -101,37 +105,27 @@ export function useNativeChatInterruptedContinuation(input: {
|
||||
: null
|
||||
return {
|
||||
view,
|
||||
offeredTurnItemId: offered,
|
||||
continueError: failedHere ? { text: failedHere, onDismiss: () => setFailedOn(null) } : null,
|
||||
continueNow
|
||||
continueError: failedHere ? { text: failedHere, onDismiss: () => setFailedOn(null) } : null
|
||||
}
|
||||
}
|
||||
|
||||
export function NativeChatInterruptedContinue({
|
||||
continuation
|
||||
/** Continue, on the row that says Orca stopped this reply: the one action that picks it back up. */
|
||||
export function NativeChatInterruptedContinueButton({
|
||||
onContinue
|
||||
}: {
|
||||
continuation: NativeChatInterruptedContinuation
|
||||
}): React.JSX.Element | null {
|
||||
onContinue: () => void
|
||||
}): React.JSX.Element {
|
||||
const explanationId = useId()
|
||||
if (continuation.offeredTurnItemId === null) {
|
||||
return null
|
||||
}
|
||||
const explanation = translate(
|
||||
'components.native-chat.interruptedContinue.explanation',
|
||||
'Continue, and the agent first checks whether its last step finished.'
|
||||
)
|
||||
return (
|
||||
<div className="mx-auto flex w-full max-w-4xl items-center justify-end px-4 py-1">
|
||||
<>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="xs"
|
||||
aria-describedby={explanationId}
|
||||
onClick={continuation.continueNow}
|
||||
>
|
||||
<Play className="size-3" />
|
||||
<Button type="button" size="xs" aria-describedby={explanationId} onClick={onContinue}>
|
||||
<Play />
|
||||
{translate('components.native-chat.interruptedContinue.continue', 'Continue')}
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
@@ -142,6 +136,6 @@ export function NativeChatInterruptedContinue({
|
||||
<span id={explanationId} className="sr-only">
|
||||
{explanation}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// @vitest-environment happy-dom
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { act, cleanup, render, screen } from '@testing-library/react'
|
||||
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { projectStructuredItemsToNativeChat } from '../../../../shared/structured-agent-session-projection'
|
||||
import type { AgentJournalStatusItem } from '../../../../shared/agent-session-journal-types'
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { MessageRow } from './NativeChatMessageRow'
|
||||
import { NativeChatNoticeRow } from './NativeChatNoticeRow'
|
||||
import { i18n } from '@/i18n/i18n'
|
||||
@@ -32,17 +33,21 @@ it('keeps a skipped compaction warning truthful instead of showing the success s
|
||||
function orcaStopView(
|
||||
hostLabel: string | null,
|
||||
continueAvailable: boolean,
|
||||
remoteHost: boolean
|
||||
remoteHost: boolean,
|
||||
offer: Pick<NativeChatOrcaStopView, 'offeredTurnItemId' | 'continueNow'>
|
||||
): NativeChatOrcaStopView {
|
||||
return { hostLabel, remoteHost, continueAvailable }
|
||||
return { hostLabel, remoteHost, continueAvailable, ...offer }
|
||||
}
|
||||
|
||||
const NO_OFFER = { offeredTurnItemId: null, continueNow: () => {} }
|
||||
|
||||
function renderStatus(
|
||||
body: AgentJournalStatusItem,
|
||||
hostLabel: string | null = null,
|
||||
continueAvailable = false,
|
||||
remoteHost = false,
|
||||
agentName?: string
|
||||
agentName?: string,
|
||||
offer: Pick<NativeChatOrcaStopView, 'offeredTurnItemId' | 'continueNow'> = NO_OFFER
|
||||
) {
|
||||
const [message] = projectStructuredItemsToNativeChat([
|
||||
{
|
||||
@@ -54,16 +59,18 @@ function renderStatus(
|
||||
turnScope: { kind: 'turn', turnItemId: 'cut-turn' }
|
||||
}
|
||||
])
|
||||
const view = orcaStopView(hostLabel, continueAvailable, remoteHost)
|
||||
const view = orcaStopView(hostLabel, continueAvailable, remoteHost, offer)
|
||||
return render(
|
||||
<NativeChatOrcaStopContext.Provider value={view}>
|
||||
<MessageRow
|
||||
message={message!}
|
||||
agentName={agentName}
|
||||
expandSignal={false}
|
||||
onScrollMessageToTop={vi.fn()}
|
||||
/>
|
||||
</NativeChatOrcaStopContext.Provider>
|
||||
<TooltipProvider>
|
||||
<NativeChatOrcaStopContext.Provider value={view}>
|
||||
<MessageRow
|
||||
message={message!}
|
||||
agentName={agentName}
|
||||
expandSignal={false}
|
||||
onScrollMessageToTop={vi.fn()}
|
||||
/>
|
||||
</NativeChatOrcaStopContext.Provider>
|
||||
</TooltipProvider>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -101,6 +108,29 @@ describe('the row an Orca stop leaves', () => {
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('carries Continue on the cut it is offered for, and only there', () => {
|
||||
const continueNow = vi.fn()
|
||||
renderStatus(orcaStopRow('quit'), 'studio-mac', true, false, undefined, {
|
||||
offeredTurnItemId: 'cut-turn',
|
||||
continueNow
|
||||
})
|
||||
const sentence = screen.getByText(
|
||||
'Orca on studio-mac was closed while this response was in progress.'
|
||||
)
|
||||
const button = screen.getByRole('button', { name: 'Continue' })
|
||||
// Beside the words that say why, in the same card.
|
||||
expect(sentence.parentElement).toContainElement(button)
|
||||
fireEvent.click(button)
|
||||
expect(continueNow).toHaveBeenCalledOnce()
|
||||
cleanup()
|
||||
|
||||
renderStatus(orcaStopRow('quit'), 'studio-mac', true, false, undefined, {
|
||||
offeredTurnItemId: 'another-turn',
|
||||
continueNow
|
||||
})
|
||||
expect(screen.queryByRole('button', { name: 'Continue' })).toBeNull()
|
||||
})
|
||||
|
||||
it("says a remote host's Orca stopped, not that it was closed", () => {
|
||||
renderStatus(orcaStopRow('quit'), 'QA SSH', true, true)
|
||||
expect(
|
||||
@@ -161,6 +191,16 @@ describe('the row an Orca stop leaves', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('calls a paired server with no name the remote server, so its words match the button', () => {
|
||||
renderStatus(orcaStopRow('update'), null, true, true)
|
||||
expect(
|
||||
screen.getByText(
|
||||
'Orca on the remote server restarted for an update while this response was in progress.'
|
||||
)
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText(LEGACY_TEXT)).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps the host words for a stop Orca did not cause', () => {
|
||||
const { orcaStop: _orcaStop, presentation: _presentation, ...agentExit } = orcaStopRow('update')
|
||||
renderStatus(agentExit, 'studio-mac')
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from '../../../../shared/agent-session-host-status-rows'
|
||||
import type { NativeChatTextBlock } from '../../../../shared/native-chat-types'
|
||||
import { useNativeChatOrcaStopView } from './native-chat-orca-stop-context'
|
||||
import { NativeChatInterruptedContinueButton } from './NativeChatInterruptedContinue'
|
||||
import { nativeChatOrcaStopRowText } from './native-chat-orca-stop-words'
|
||||
import { AGENT_SESSION_ORCA_STOP_PRESENTATION } from '../../../../shared/agent-session-orca-stop'
|
||||
import { ProviderFrameRow } from './NativeChatTranscriptChrome'
|
||||
@@ -109,10 +110,18 @@ export function NativeChatNoticeRow({
|
||||
)
|
||||
}
|
||||
// The host's row about an Orca stop names the cause and the machine, muted: Orca stopped, not the
|
||||
// agent. With no machine to name it keeps the host's own words.
|
||||
// agent. Outside a chat that knows its machine it keeps the host's own words.
|
||||
const { orcaStop } = block
|
||||
const { hostLabel, remoteHost, continueAvailable } = orcaStopView
|
||||
const named = orcaStop !== undefined && hostLabel !== null
|
||||
// A paired server with no name to show is still a remote server; its id is not a name.
|
||||
const machine =
|
||||
hostLabel ??
|
||||
(remoteHost
|
||||
? translate('components.native-chat.notices.orcaStopUnnamedServer', 'the remote server')
|
||||
: null)
|
||||
const named = orcaStop !== undefined && machine !== null
|
||||
const continueOffered =
|
||||
orcaStop?.turnItemId !== undefined && orcaStop.turnItemId === orcaStopView.offeredTurnItemId
|
||||
const failure = readWholeAgentSessionFailureFact(block.failure)
|
||||
// Only reword auth text fully described by its fact; host text may also carry command advice.
|
||||
const authSurface =
|
||||
@@ -122,7 +131,7 @@ export function NativeChatNoticeRow({
|
||||
)
|
||||
: undefined
|
||||
const text = named
|
||||
? nativeChatOrcaStopRowText(orcaStop.cause, hostLabel, { continueAvailable, remoteHost })
|
||||
? nativeChatOrcaStopRowText(orcaStop.cause, machine, { continueAvailable, remoteHost })
|
||||
: failure && authSurface
|
||||
? agentSessionFailureSentence(
|
||||
failure,
|
||||
@@ -153,7 +162,10 @@ export function NativeChatNoticeRow({
|
||||
>
|
||||
<div className="flex items-start gap-2">
|
||||
{Icon ? <Icon aria-hidden="true" className="mt-0.5 size-4 shrink-0" /> : null}
|
||||
<p className="min-w-0 whitespace-pre-wrap break-words">{text}</p>
|
||||
<p className="min-w-0 flex-1 whitespace-pre-wrap break-words">{text}</p>
|
||||
{continueOffered ? (
|
||||
<NativeChatInterruptedContinueButton onContinue={orcaStopView.continueNow} />
|
||||
) : null}
|
||||
</div>
|
||||
{claudeSignIn && isClaudeSignInFailureKind(block.failure?.kind) ? (
|
||||
<Button
|
||||
|
||||
+126
@@ -0,0 +1,126 @@
|
||||
// @vitest-environment happy-dom
|
||||
import '@testing-library/jest-dom/vitest'
|
||||
import { cleanup, render, screen } from '@testing-library/react'
|
||||
import { useImperativeHandle } from 'react'
|
||||
import { afterEach, expect, it, vi } from 'vitest'
|
||||
import type * as HostCapabilityModule from '@/runtime/structured-agent-session-host-capability'
|
||||
|
||||
const { mocks, moduleFactories, resetStructuredSessionMocks } = await vi.hoisted(async () =>
|
||||
(await import('./NativeChatStructuredSession.test-harness')).createStructuredSessionMocks()
|
||||
)
|
||||
|
||||
vi.mock('@/lib/structured-agent-session-launch', () =>
|
||||
moduleFactories.structuredAgentSessionLaunch()
|
||||
)
|
||||
vi.mock('@/runtime/structured-agent-session-client', () =>
|
||||
moduleFactories.structuredAgentSessionClient()
|
||||
)
|
||||
vi.mock('./use-structured-agent-session', () => moduleFactories.useStructuredAgentSession())
|
||||
vi.mock('./use-native-chat-font-size', () => moduleFactories.useNativeChatFontSize())
|
||||
vi.mock('./use-native-chat-file-link-context', () => moduleFactories.useNativeChatFileLinkContext())
|
||||
vi.mock('./use-native-chat-file-link-click', () => moduleFactories.useNativeChatFileLinkClick())
|
||||
vi.mock('./NativeChatComposer', () => moduleFactories.nativeChatComposer())
|
||||
vi.mock('./NativeChatApprovalCard', () => moduleFactories.nativeChatApprovalCard())
|
||||
vi.mock('./NativeChatQuestionCard', () => moduleFactories.nativeChatQuestionCard())
|
||||
vi.mock('@/runtime/structured-agent-session-host-capability', async (importOriginal) => {
|
||||
const original = await importOriginal<typeof HostCapabilityModule>()
|
||||
const { AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } =
|
||||
await import('../../../../shared/agent-session-continue-interrupted-capability')
|
||||
return {
|
||||
...original,
|
||||
useStructuredAgentSessionHostCapabilityState: (
|
||||
...args: Parameters<typeof original.useStructuredAgentSessionHostCapabilityState>
|
||||
) =>
|
||||
args[1] === AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY
|
||||
? 'supported'
|
||||
: original.useStructuredAgentSessionHostCapabilityState(...args)
|
||||
}
|
||||
})
|
||||
// The transcript's real rows, projected from the journal; only the list's windowing is left out.
|
||||
vi.mock('./NativeChatMessageList', async () => {
|
||||
const { MessageRow } = await import('./NativeChatMessageRow')
|
||||
const { projectStructuredItemsToNativeChat } =
|
||||
await import('../../../../shared/structured-agent-session-projection')
|
||||
return {
|
||||
NativeChatMessageList: (props: {
|
||||
ref?: React.Ref<unknown>
|
||||
journalItems?: Parameters<typeof projectStructuredItemsToNativeChat>[0]
|
||||
}) => {
|
||||
useImperativeHandle(props.ref, () => ({ revealLatest: () => {}, revealFindMatch: () => {} }))
|
||||
return (
|
||||
<div data-testid="transcript">
|
||||
{projectStructuredItemsToNativeChat(props.journalItems ?? []).map((message) => (
|
||||
<MessageRow
|
||||
key={message.id}
|
||||
message={message}
|
||||
expandSignal={false}
|
||||
onScrollMessageToTop={() => {}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
import { TooltipProvider } from '@/components/ui/tooltip'
|
||||
import { NativeChatStructuredSession } from './NativeChatStructuredSession'
|
||||
import { agentJournalItemKey } from '../../../../shared/agent-session-journal-item-key'
|
||||
import type { AgentJournalRenderItem } from '../../../../shared/agent-session-journal-types'
|
||||
|
||||
const TURN = agentJournalItemKey({ provider: 'codex', threadId: 't', turnId: 'cut', ordinal: 1 })
|
||||
|
||||
const cutChat: AgentJournalRenderItem[] = [
|
||||
{
|
||||
itemId: TURN,
|
||||
revision: 2,
|
||||
sequence: 1,
|
||||
observedAt: 1,
|
||||
body: { kind: 'turn', turnId: 'cut', state: 'interrupted', startedAt: 1, completedAt: 5 }
|
||||
},
|
||||
{
|
||||
itemId: agentJournalItemKey({ provider: 'orca', clientMessageId: 'stale-session:s:death-1-5' }),
|
||||
revision: 1,
|
||||
sequence: 2,
|
||||
observedAt: 6,
|
||||
turnScope: { kind: 'turn', turnItemId: TURN },
|
||||
body: {
|
||||
kind: 'status',
|
||||
text: 'Codex stopped while this response was in progress. You can continue in this conversation.',
|
||||
tone: 'error',
|
||||
presentation: 'orca-stop',
|
||||
orcaStop: { cause: 'crash' }
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
afterEach(() => {
|
||||
cleanup()
|
||||
resetStructuredSessionMocks()
|
||||
})
|
||||
|
||||
it('offers one Continue, in the card that says Orca stopped, and the card does not say it again', () => {
|
||||
mocks.journalItems = cutChat
|
||||
render(
|
||||
<TooltipProvider>
|
||||
<NativeChatStructuredSession
|
||||
isVisible
|
||||
isFocusedGroup
|
||||
tabId="orca-stop-tab"
|
||||
sessionId="orca-stop-session"
|
||||
// A paired server with no name to show.
|
||||
target={{ kind: 'environment', environmentId: 'env-unnamed' }}
|
||||
agent="codex"
|
||||
/>
|
||||
</TooltipProvider>
|
||||
)
|
||||
|
||||
// Throws on a second Continue anywhere in the pane, such as the old row above the composer.
|
||||
const button = screen.getByRole('button', { name: 'Continue' })
|
||||
const sentence = screen.getByText(
|
||||
'Orca on the remote server stopped unexpectedly while this response was in progress.'
|
||||
)
|
||||
expect(sentence.parentElement).toContainElement(button)
|
||||
expect(screen.getByTestId('transcript')).toContainElement(button)
|
||||
expect(screen.queryByText(/You can continue in this conversation/)).toBeNull()
|
||||
})
|
||||
@@ -31,10 +31,7 @@ import { useStructuredNativeChatPaneCommands } from './use-structured-native-cha
|
||||
import type { NativeChatStructuredViewProps } from './native-chat-view-types'
|
||||
import { NativeChatStructuredSessionStatus } from './NativeChatStructuredSessionStatus'
|
||||
import { useNativeChatLaunchDraftSignal } from './use-native-chat-launch-draft-adoption'
|
||||
import {
|
||||
NativeChatInterruptedContinue,
|
||||
useNativeChatInterruptedContinuation
|
||||
} from './NativeChatInterruptedContinue'
|
||||
import { useNativeChatInterruptedContinuation } from './NativeChatInterruptedContinue'
|
||||
import { NativeChatOrcaStopContext } from './native-chat-orca-stop-context'
|
||||
import { structuredSessionNotices } from './native-chat-structured-session-notices'
|
||||
import { NativeChatPromptSlotNotices } from './NativeChatComposerNotices'
|
||||
@@ -142,7 +139,7 @@ export function NativeChatStructuredSession(
|
||||
journalItems: controller.journalItems,
|
||||
submissions: controller.submissions,
|
||||
isWorking: controller.isWorking,
|
||||
composer: { clearError: () => reportComposerError(null) }
|
||||
reportComposerError
|
||||
})
|
||||
const needsFailureFacts =
|
||||
submits.queuedMessages.cards.some((card) => card.state === 'returned') ||
|
||||
@@ -319,7 +316,6 @@ export function NativeChatStructuredSession(
|
||||
</div>
|
||||
{readFailedFinally ? null : (
|
||||
<>
|
||||
<NativeChatInterruptedContinue continuation={continuation} />
|
||||
{/* Host-held drafts, never transcript rows. Above the status area, so running shells and agents sit next to the composer. */}
|
||||
<NativeChatQueuedMessageList
|
||||
controller={submits.queuedMessages}
|
||||
|
||||
@@ -8,12 +8,17 @@ export type NativeChatOrcaStopView = {
|
||||
hostLabel: string | null
|
||||
remoteHost: boolean
|
||||
continueAvailable: boolean
|
||||
/** The cut turn whose row carries Continue right now, if any. */
|
||||
offeredTurnItemId: string | null
|
||||
continueNow: () => void
|
||||
}
|
||||
|
||||
export const NativeChatOrcaStopContext = createContext<NativeChatOrcaStopView>({
|
||||
hostLabel: null,
|
||||
remoteHost: false,
|
||||
continueAvailable: false
|
||||
continueAvailable: false,
|
||||
offeredTurnItemId: null,
|
||||
continueNow: () => {}
|
||||
})
|
||||
|
||||
export function useNativeChatOrcaStopView(): NativeChatOrcaStopView {
|
||||
|
||||
@@ -18224,7 +18224,8 @@
|
||||
"orcaStopQuit": "Orca on {{machine}} was closed while this response was in progress.",
|
||||
"orcaStopCrash": "Orca on {{machine}} stopped unexpectedly while this response was in progress.",
|
||||
"orcaStopRemote": "Orca on {{machine}} stopped while this response was in progress.",
|
||||
"orcaStopCanContinue": "You can continue in this conversation."
|
||||
"orcaStopCanContinue": "You can continue in this conversation.",
|
||||
"orcaStopUnnamedServer": "the remote server"
|
||||
},
|
||||
"composer": {
|
||||
"imageUnsupported": "Image paste is not supported for this agent.",
|
||||
|
||||
@@ -18328,7 +18328,8 @@
|
||||
"orcaStopQuit": "Orca en {{machine}} se cerró mientras esta respuesta estaba en curso.",
|
||||
"orcaStopCrash": "Orca en {{machine}} se detuvo inesperadamente mientras esta respuesta estaba en curso.",
|
||||
"orcaStopRemote": "Orca en {{machine}} se detuvo mientras esta respuesta estaba en curso.",
|
||||
"orcaStopCanContinue": "Puedes continuar en esta conversación."
|
||||
"orcaStopCanContinue": "Puedes continuar en esta conversación.",
|
||||
"orcaStopUnnamedServer": "el servidor remoto"
|
||||
},
|
||||
"providerFrame": {
|
||||
"byteLength": "{{value0}} bytes"
|
||||
|
||||
@@ -18305,7 +18305,8 @@
|
||||
"orcaStopQuit": "Orca sur {{machine}} a été fermé pendant que cette réponse était en cours.",
|
||||
"orcaStopCrash": "Orca sur {{machine}} s'est arrêté de manière inattendue pendant que cette réponse était en cours.",
|
||||
"orcaStopRemote": "Orca sur {{machine}} s'est arrêté pendant que cette réponse était en cours.",
|
||||
"orcaStopCanContinue": "Vous pouvez continuer dans cette conversation."
|
||||
"orcaStopCanContinue": "Vous pouvez continuer dans cette conversation.",
|
||||
"orcaStopUnnamedServer": "le serveur distant"
|
||||
},
|
||||
"providerFrame": {
|
||||
"byteLength": "{{value0}} octets"
|
||||
|
||||
@@ -18238,7 +18238,8 @@
|
||||
"orcaStopQuit": "この応答の途中で、{{machine}} の Orca が終了しました。",
|
||||
"orcaStopCrash": "この応答の途中で、{{machine}} の Orca が予期せず停止しました。",
|
||||
"orcaStopRemote": "この応答の途中で、{{machine}} の Orca が停止しました。",
|
||||
"orcaStopCanContinue": "この会話で続行できます。"
|
||||
"orcaStopCanContinue": "この会話で続行できます。",
|
||||
"orcaStopUnnamedServer": "リモートサーバー"
|
||||
},
|
||||
"providerFrame": {
|
||||
"byteLength": "{{value0}}バイト"
|
||||
|
||||
@@ -18238,7 +18238,8 @@
|
||||
"orcaStopQuit": "이 응답이 진행되는 동안 {{machine}}의 Orca가 종료되었습니다.",
|
||||
"orcaStopCrash": "이 응답이 진행되는 동안 {{machine}}의 Orca가 예기치 않게 중지되었습니다.",
|
||||
"orcaStopRemote": "이 응답이 진행되는 동안 {{machine}}의 Orca가 중지되었습니다.",
|
||||
"orcaStopCanContinue": "이 대화에서 계속할 수 있습니다."
|
||||
"orcaStopCanContinue": "이 대화에서 계속할 수 있습니다.",
|
||||
"orcaStopUnnamedServer": "원격 서버"
|
||||
},
|
||||
"providerFrame": {
|
||||
"byteLength": "{{value0}}바이트"
|
||||
|
||||
@@ -18217,7 +18217,8 @@
|
||||
"orcaStopQuit": "此回复进行期间,{{machine}} 上的 Orca 已关闭。",
|
||||
"orcaStopCrash": "此回复进行期间,{{machine}} 上的 Orca 意外停止。",
|
||||
"orcaStopRemote": "此回复进行期间,{{machine}} 上的 Orca 已停止。",
|
||||
"orcaStopCanContinue": "你可以在此对话中继续。"
|
||||
"orcaStopCanContinue": "你可以在此对话中继续。",
|
||||
"orcaStopUnnamedServer": "远程服务器"
|
||||
},
|
||||
"providerFrame": {
|
||||
"byteLength": "{{value0}} 字节"
|
||||
|
||||
Reference in New Issue
Block a user