mirror of
https://github.com/stablyai/orca.git
synced 2026-09-29 16:02:50 +00:00
fix(native-chat): clear abandoned launch outbox
This commit is contained in:
@@ -58,6 +58,10 @@ export function enqueueStructuredAgentSessionLaunchPrompt(
|
||||
return writeOutbox(sessionId, [...readOutbox(sessionId), entry]) ? entry : null
|
||||
}
|
||||
|
||||
export function discardStructuredAgentSessionLaunchOutbox(sessionId: string): void {
|
||||
writeOutbox(sessionId, [])
|
||||
}
|
||||
|
||||
export function mutateStructuredAgentSessionLaunchPrompt(
|
||||
sessionId: string,
|
||||
clientMessageId: string,
|
||||
|
||||
@@ -49,6 +49,7 @@ import {
|
||||
cancelStructuredCodexLaunch,
|
||||
startStructuredCodexLaunch
|
||||
} from './structured-agent-session-launch'
|
||||
import { readOutbox } from '@/components/native-chat/structured-agent-session-outbox-storage'
|
||||
|
||||
function launchIntent(
|
||||
worktreeId: string,
|
||||
@@ -314,6 +315,29 @@ describe('startStructuredCodexLaunch', () => {
|
||||
storageFailure.mockRestore()
|
||||
})
|
||||
|
||||
it('discards every coalesced prompt when the launch is definitively refused', async () => {
|
||||
const worktreeId = 'wt-refused-coalesced-prompts'
|
||||
const intent = launchIntent(worktreeId)
|
||||
let rejectLaunch!: (error: unknown) => void
|
||||
mocks.createIntent.mockReturnValueOnce(intent)
|
||||
mocks.launch.mockImplementationOnce(
|
||||
() => new Promise((_resolve, reject) => (rejectLaunch = reject))
|
||||
)
|
||||
|
||||
const first = startStructuredCodexLaunch(worktreeId, { prompt: 'first prompt' })
|
||||
startStructuredCodexLaunch(worktreeId, { prompt: 'second prompt' })
|
||||
const fallback = first.claimDefinitiveRefusalFallback(vi.fn())
|
||||
expect(readOutbox(intent.sessionId)).toHaveLength(2)
|
||||
|
||||
rejectLaunch(new StructuredAgentSessionCreateRefusalError('unsupported'))
|
||||
await expect(first.launchResult).rejects.toBeInstanceOf(
|
||||
StructuredAgentSessionCreateRefusalError
|
||||
)
|
||||
await expect(fallback).resolves.toBe(true)
|
||||
|
||||
expect(readOutbox(intent.sessionId)).toEqual([])
|
||||
})
|
||||
|
||||
it('cancels a close-racing launch without retrying or toasting', async () => {
|
||||
const worktreeId = 'wt-close-race'
|
||||
const intent = launchIntent(worktreeId, 'session-close-race')
|
||||
@@ -335,6 +359,27 @@ describe('startStructuredCodexLaunch', () => {
|
||||
expect(toast.error).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('discards every coalesced prompt when a close cancels the launch', async () => {
|
||||
const worktreeId = 'wt-close-coalesced-prompts'
|
||||
const intent = launchIntent(worktreeId)
|
||||
let resolveRefresh!: (snapshots: RuntimeMobileSessionTabsResult[]) => void
|
||||
mocks.createIntent.mockReturnValueOnce(intent)
|
||||
mocks.launch.mockResolvedValueOnce({ sessionId: intent.sessionId, fence: 1 })
|
||||
vi.mocked(refreshLocalStructuredSessionTabs).mockImplementationOnce(
|
||||
() => new Promise((resolve) => (resolveRefresh = resolve))
|
||||
)
|
||||
|
||||
startStructuredCodexLaunch(worktreeId, { prompt: 'first prompt' })
|
||||
startStructuredCodexLaunch(worktreeId, { prompt: 'second prompt' })
|
||||
await vi.waitFor(() => expect(refreshLocalStructuredSessionTabs).toHaveBeenCalledOnce())
|
||||
expect(readOutbox(intent.sessionId)).toHaveLength(2)
|
||||
|
||||
expect(cancelStructuredCodexLaunch(worktreeId, intent.sessionId)).toBe(true)
|
||||
expect(readOutbox(intent.sessionId)).toEqual([])
|
||||
resolveRefresh([])
|
||||
await flushLaunchSettlement()
|
||||
})
|
||||
|
||||
it('suppresses a close that races the retry verification catch', async () => {
|
||||
const worktreeId = 'wt-retry-close-race'
|
||||
const intent = launchIntent(worktreeId, 'session-retry-close-race')
|
||||
|
||||
@@ -6,8 +6,8 @@ import {
|
||||
StructuredAgentSessionCreateRefusalError
|
||||
} from '@/lib/launch-structured-codex-session'
|
||||
import {
|
||||
enqueueStructuredAgentSessionLaunchPrompt,
|
||||
mutateStructuredAgentSessionLaunchPrompt
|
||||
discardStructuredAgentSessionLaunchOutbox,
|
||||
enqueueStructuredAgentSessionLaunchPrompt
|
||||
} from '@/components/native-chat/structured-agent-session-outbox-storage'
|
||||
import {
|
||||
launchAndReconcile,
|
||||
@@ -30,7 +30,6 @@ type StructuredRefusalFallback = () =>
|
||||
|
||||
type StructuredLaunchState = StructuredLaunchRecoveryState & {
|
||||
identity: string
|
||||
stagedPromptId: string | null
|
||||
promptDeliveryResult?: Promise<StructuredPromptDeliveryResult>
|
||||
refusalFallback: {
|
||||
callback: StructuredRefusalFallback | null
|
||||
@@ -67,14 +66,6 @@ function launchIdentity(worktreeId: string): string {
|
||||
return worktreeId
|
||||
}
|
||||
|
||||
function discardStagedPrompt(state: StructuredLaunchState): void {
|
||||
if (!state.stagedPromptId) {
|
||||
return
|
||||
}
|
||||
mutateStructuredAgentSessionLaunchPrompt(state.intent.sessionId, state.stagedPromptId, () => null)
|
||||
state.stagedPromptId = null
|
||||
}
|
||||
|
||||
function cleanupLaunchState(state: StructuredLaunchState): void {
|
||||
if (pendingStructuredLaunchesByIdentity.get(state.identity) === state) {
|
||||
pendingStructuredLaunchesByIdentity.delete(state.identity)
|
||||
@@ -87,7 +78,7 @@ function settleDefinitiveRefusalFallback(state: StructuredLaunchState): void {
|
||||
}
|
||||
state.refusalFallback.started = true
|
||||
abandonStructuredAgentSessionLaunchIntent(state.intent)
|
||||
discardStagedPrompt(state)
|
||||
discardStructuredAgentSessionLaunchOutbox(state.intent.sessionId)
|
||||
const fallback = state.refusalFallback.callback
|
||||
if (!fallback) {
|
||||
state.refusalFallback.resolve(false)
|
||||
@@ -214,7 +205,6 @@ function structuredCodexLaunchState(
|
||||
promise: Promise.resolve({ sessionId: '', fence: 0 }),
|
||||
visibilityUnknown: false,
|
||||
cancelled: false,
|
||||
stagedPromptId: stagedPrompt?.clientMessageId ?? null,
|
||||
refusalFallback: {
|
||||
callback: null,
|
||||
promise: fallback.promise,
|
||||
@@ -268,7 +258,7 @@ export function cancelStructuredCodexLaunch(worktreeId: string, sessionId: strin
|
||||
}
|
||||
state.cancelled = true
|
||||
cleanupLaunchState(state)
|
||||
discardStagedPrompt(state)
|
||||
discardStructuredAgentSessionLaunchOutbox(state.intent.sessionId)
|
||||
abandonStructuredAgentSessionLaunchIntent(state.intent)
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user