From f9a01653c8575b93affda8240d9ad50dcfcd3d31 Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Fri, 9 Oct 2026 18:52:26 -0700
Subject: [PATCH 1/2] 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.
---
.../NativeChatInterruptedContinue.test.tsx | 9 ++-
.../NativeChatInterruptedContinue.tsx | 54 ++++++++++--------
.../native-chat/NativeChatNoticeRow.test.tsx | 56 ++++++++++++++-----
.../native-chat/NativeChatNoticeRow.tsx | 8 ++-
.../NativeChatStructuredSession.tsx | 6 +-
.../native-chat-orca-stop-context.ts | 7 ++-
6 files changed, 93 insertions(+), 47 deletions(-)
diff --git a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
index 6c35294a4ffe..9e94c0e04791 100644
--- a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
@@ -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'
@@ -87,14 +87,17 @@ function Harness(props: Props): React.JSX.Element {
})
return (
- {continuation.offeredTurnItemId ?? 'none'}
+ {continuation.view.offeredTurnItemId ?? 'none'}
{String(continuation.view.continueAvailable)}
{String(continuation.view.remoteHost)}
{composerError ?? continuation.continueError?.text ?? 'none'}
setComposerError(ATTACHMENTS)}>
compose
-
+ {/* As the cut's own row shows it. */}
+ {continuation.view.offeredTurnItemId === null ? null : (
+
+ )}
)
}
diff --git a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
index ae8a9a503adb..5d70c6a2b6eb 100644
--- a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
@@ -1,4 +1,4 @@
-import { useId, useMemo, useState } from 'react'
+import { useCallback, useId, useLayoutEffect, useMemo, useRef, 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
}
/**
@@ -86,11 +84,23 @@ export function useNativeChatInterruptedContinuation(input: {
}
// 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.
+ // One object per change, so the chat's rows re-render only when what they show changes; the rows
+ // get a stable handler that runs the latest continueNow.
+ const latestContinueNow = useRef(continueNow)
+ useLayoutEffect(() => {
+ latestContinueNow.current = continueNow
+ })
+ const stableContinueNow = useCallback(() => latestContinueNow.current(), [])
const remoteHost = target.kind === 'environment'
const view = useMemo(
- () => ({ hostLabel, remoteHost, continueAvailable }),
- [hostLabel, remoteHost, continueAvailable]
+ () => ({
+ hostLabel,
+ remoteHost,
+ continueAvailable,
+ offeredTurnItemId: offered,
+ continueNow: stableContinueNow
+ }),
+ [hostLabel, remoteHost, continueAvailable, offered, stableContinueNow]
)
const failedHere =
failedOn !== null && cut?.turnItemId === failedOn
@@ -101,37 +111,33 @@ 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 (
-
+ <>
-
+
{translate('components.native-chat.interruptedContinue.continue', 'Continue')}
@@ -142,6 +148,6 @@ export function NativeChatInterruptedContinue({
{explanation}
-
+ >
)
}
diff --git a/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx b/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
index 388b60b20c06..9046de17b8a4 100644
--- a/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
@@ -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 {
- 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 = 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(
-
-
-
+
+
+
+
+
)
}
@@ -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(
diff --git a/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx b/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
index 56a01d93563f..e63805e3d9c4 100644
--- a/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
@@ -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'
@@ -113,6 +114,8 @@ export function NativeChatNoticeRow({
const { orcaStop } = block
const { hostLabel, remoteHost, continueAvailable } = orcaStopView
const named = orcaStop !== undefined && hostLabel !== 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 =
@@ -153,7 +156,10 @@ export function NativeChatNoticeRow({
>
{Icon ?
: null}
-
{text}
+
{text}
+ {continueOffered ? (
+
+ ) : null}
{claudeSignIn && isClaudeSignInFailureKind(block.failure?.kind) ? (
{readFailedFinally ? null : (
<>
-
{/* Host-held drafts, never transcript rows. Above the status area, so running shells and agents sit next to the composer. */}
void
}
export const NativeChatOrcaStopContext = createContext({
hostLabel: null,
remoteHost: false,
- continueAvailable: false
+ continueAvailable: false,
+ offeredTurnItemId: null,
+ continueNow: () => {}
})
export function useNativeChatOrcaStopView(): NativeChatOrcaStopView {
From e3bba0041fcf4899f247cdea91d45c4b3a0a50f5 Mon Sep 17 00:00:00 2001
From: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
Date: Sat, 10 Oct 2026 02:23:11 -0700
Subject: [PATCH 2/2] 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.
---
.../NativeChatInterruptedContinue.test.tsx | 2 +-
.../NativeChatInterruptedContinue.tsx | 34 ++---
.../native-chat/NativeChatNoticeRow.test.tsx | 10 ++
.../native-chat/NativeChatNoticeRow.tsx | 12 +-
...ucturedSession.orca-stop-continue.test.tsx | 126 ++++++++++++++++++
.../NativeChatStructuredSession.tsx | 2 +-
src/renderer/src/i18n/locales/en.json | 3 +-
src/renderer/src/i18n/locales/es.json | 3 +-
src/renderer/src/i18n/locales/fr.json | 3 +-
src/renderer/src/i18n/locales/ja.json | 3 +-
src/renderer/src/i18n/locales/ko.json | 3 +-
src/renderer/src/i18n/locales/zh.json | 3 +-
12 files changed, 170 insertions(+), 34 deletions(-)
create mode 100644 src/renderer/src/components/native-chat/NativeChatStructuredSession.orca-stop-continue.test.tsx
diff --git a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
index 9e94c0e04791..126e3f7da04b 100644
--- a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.test.tsx
@@ -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(null)
const continuation = useNativeChatInterruptedContinuation({
- composer: { clearError: () => setComposerError(null) },
+ reportComposerError: setComposerError,
target: props.target ?? PAIRED,
sessionId: 'session-1',
journalItems: props.journalItems ?? cutChat,
diff --git a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
index 5d70c6a2b6eb..bd37b1e06703 100644
--- a/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatInterruptedContinue.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useId, useLayoutEffect, useMemo, useRef, 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'
@@ -40,10 +40,10 @@ export function useNativeChatInterruptedContinuation(input: {
journalItems: readonly AgentJournalRenderItem[]
submissions: readonly Pick[]
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,
@@ -64,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
}
@@ -76,21 +76,15 @@ export function useNativeChatInterruptedContinuation(input: {
}
setAsked(turnItemId)
setFailedOn(null)
- input.composer.clearError()
+ reportComposerError(null)
void callStructuredAgentSession(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; the rows
- // get a stable handler that runs the latest continueNow.
- const latestContinueNow = useRef(continueNow)
- useLayoutEffect(() => {
- latestContinueNow.current = continueNow
- })
- const stableContinueNow = useCallback(() => latestContinueNow.current(), [])
+ // 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(
() => ({
@@ -98,9 +92,9 @@ export function useNativeChatInterruptedContinuation(input: {
remoteHost,
continueAvailable,
offeredTurnItemId: offered,
- continueNow: stableContinueNow
+ continueNow
}),
- [hostLabel, remoteHost, continueAvailable, offered, stableContinueNow]
+ [hostLabel, remoteHost, continueAvailable, offered, continueNow]
)
const failedHere =
failedOn !== null && cut?.turnItemId === failedOn
@@ -130,13 +124,7 @@ export function NativeChatInterruptedContinueButton({
<>
-
+
{translate('components.native-chat.interruptedContinue.continue', 'Continue')}
diff --git a/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx b/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
index 9046de17b8a4..ecf4a2c5731e 100644
--- a/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatNoticeRow.test.tsx
@@ -191,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')
diff --git a/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx b/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
index e63805e3d9c4..454ba816cc1f 100644
--- a/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatNoticeRow.tsx
@@ -110,10 +110,16 @@ 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)
@@ -125,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,
diff --git a/src/renderer/src/components/native-chat/NativeChatStructuredSession.orca-stop-continue.test.tsx b/src/renderer/src/components/native-chat/NativeChatStructuredSession.orca-stop-continue.test.tsx
new file mode 100644
index 000000000000..86fceb7065a4
--- /dev/null
+++ b/src/renderer/src/components/native-chat/NativeChatStructuredSession.orca-stop-continue.test.tsx
@@ -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()
+ const { AGENT_SESSION_CONTINUE_INTERRUPTED_RUNTIME_CAPABILITY } =
+ await import('../../../../shared/agent-session-continue-interrupted-capability')
+ return {
+ ...original,
+ useStructuredAgentSessionHostCapabilityState: (
+ ...args: Parameters
+ ) =>
+ 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
+ journalItems?: Parameters[0]
+ }) => {
+ useImperativeHandle(props.ref, () => ({ revealLatest: () => {}, revealFindMatch: () => {} }))
+ return (
+
+ {projectStructuredItemsToNativeChat(props.journalItems ?? []).map((message) => (
+ {}}
+ />
+ ))}
+
+ )
+ }
+ }
+})
+
+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(
+
+
+
+ )
+
+ // 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()
+})
diff --git a/src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx b/src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
index 67cc018633a3..b3efd9fa4c65 100644
--- a/src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
+++ b/src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
@@ -139,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') ||
diff --git a/src/renderer/src/i18n/locales/en.json b/src/renderer/src/i18n/locales/en.json
index 7a9ad4a4a46f..f7b3a7a2e1b9 100644
--- a/src/renderer/src/i18n/locales/en.json
+++ b/src/renderer/src/i18n/locales/en.json
@@ -18226,7 +18226,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.",
diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json
index cc49e7c4ea7a..40a1ed864972 100644
--- a/src/renderer/src/i18n/locales/es.json
+++ b/src/renderer/src/i18n/locales/es.json
@@ -18379,7 +18379,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"
diff --git a/src/renderer/src/i18n/locales/fr.json b/src/renderer/src/i18n/locales/fr.json
index 50517f06569f..a9a232a78592 100644
--- a/src/renderer/src/i18n/locales/fr.json
+++ b/src/renderer/src/i18n/locales/fr.json
@@ -18356,7 +18356,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"
diff --git a/src/renderer/src/i18n/locales/ja.json b/src/renderer/src/i18n/locales/ja.json
index 020e3c48d8a0..21dca6d6d5fe 100644
--- a/src/renderer/src/i18n/locales/ja.json
+++ b/src/renderer/src/i18n/locales/ja.json
@@ -18289,7 +18289,8 @@
"orcaStopQuit": "この応答の途中で、{{machine}} の Orca が終了しました。",
"orcaStopCrash": "この応答の途中で、{{machine}} の Orca が予期せず停止しました。",
"orcaStopRemote": "この応答の途中で、{{machine}} の Orca が停止しました。",
- "orcaStopCanContinue": "この会話で続行できます。"
+ "orcaStopCanContinue": "この会話で続行できます。",
+ "orcaStopUnnamedServer": "リモートサーバー"
},
"providerFrame": {
"byteLength": "{{value0}}バイト"
diff --git a/src/renderer/src/i18n/locales/ko.json b/src/renderer/src/i18n/locales/ko.json
index fccd5a553a43..e50e6863da7d 100644
--- a/src/renderer/src/i18n/locales/ko.json
+++ b/src/renderer/src/i18n/locales/ko.json
@@ -18289,7 +18289,8 @@
"orcaStopQuit": "이 응답이 진행되는 동안 {{machine}}의 Orca가 종료되었습니다.",
"orcaStopCrash": "이 응답이 진행되는 동안 {{machine}}의 Orca가 예기치 않게 중지되었습니다.",
"orcaStopRemote": "이 응답이 진행되는 동안 {{machine}}의 Orca가 중지되었습니다.",
- "orcaStopCanContinue": "이 대화에서 계속할 수 있습니다."
+ "orcaStopCanContinue": "이 대화에서 계속할 수 있습니다.",
+ "orcaStopUnnamedServer": "원격 서버"
},
"providerFrame": {
"byteLength": "{{value0}}바이트"
diff --git a/src/renderer/src/i18n/locales/zh.json b/src/renderer/src/i18n/locales/zh.json
index b543ca0359c7..0435b6f421d2 100644
--- a/src/renderer/src/i18n/locales/zh.json
+++ b/src/renderer/src/i18n/locales/zh.json
@@ -18254,7 +18254,8 @@
"orcaStopQuit": "此回复进行期间,{{machine}} 上的 Orca 已关闭。",
"orcaStopCrash": "此回复进行期间,{{machine}} 上的 Orca 意外停止。",
"orcaStopRemote": "此回复进行期间,{{machine}} 上的 Orca 已停止。",
- "orcaStopCanContinue": "你可以在此对话中继续。"
+ "orcaStopCanContinue": "你可以在此对话中继续。",
+ "orcaStopUnnamedServer": "远程服务器"
},
"providerFrame": {
"byteLength": "{{value0}} 字节"