mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 00:02:41 +00:00
fix(native-chat): rotate immediately rejected retries
This commit is contained in:
+2
-2
@@ -643,7 +643,7 @@ describe('useStructuredAgentSessionOutbox', () => {
|
||||
11
|
||||
)
|
||||
)
|
||||
const { result, rerender } = renderHook(
|
||||
const { result } = renderHook(
|
||||
({ submissions }: { submissions: readonly AgentJournalSubmission[] }) =>
|
||||
useStructuredAgentSessionOutbox({
|
||||
sessionId: 'session-1',
|
||||
@@ -669,7 +669,7 @@ describe('useStructuredAgentSessionOutbox', () => {
|
||||
expect(result.current.outbox[0]?.state).toBe('queued')
|
||||
expect(result.current.blockedClientMessageId).toBe(firstId)
|
||||
|
||||
rerender({ submissions: [writeFailed(firstId)] })
|
||||
// Retry immediately, before the journal subscription can publish the rejected row.
|
||||
act(() => result.current.retry(firstId))
|
||||
await waitFor(() => expect(result.current.outbox).toHaveLength(0))
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export function useStructuredAgentSessionOutbox(args: {
|
||||
const dispatchingRef = useRef(false)
|
||||
const dispatchGenerationRef = useRef(0)
|
||||
const blockedIdRef = useRef<string | null>(null)
|
||||
const retryWithFreshClientMessageIdRef = useRef<string | null>(null)
|
||||
const probeAttemptsRef = useRef({ id: null as string | null, attempts: 0 })
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [errorSession, setErrorSession] = useState(sessionId)
|
||||
@@ -70,6 +71,7 @@ export function useStructuredAgentSessionOutbox(args: {
|
||||
dispatchGenerationRef.current += 1
|
||||
dispatchingRef.current = false
|
||||
blockedIdRef.current = null
|
||||
retryWithFreshClientMessageIdRef.current = null
|
||||
probeAttemptsRef.current = { id: null, attempts: 0 }
|
||||
}, [fence, sessionId, targetKey])
|
||||
|
||||
@@ -125,6 +127,7 @@ export function useStructuredAgentSessionOutbox(args: {
|
||||
const applyDisposition = useCallback(
|
||||
(disposition: StructuredAgentSessionSendDisposition): void => {
|
||||
blockedIdRef.current = disposition.blockedClientMessageId
|
||||
retryWithFreshClientMessageIdRef.current = disposition.retryWithFreshClientMessageId
|
||||
setError(disposition.error)
|
||||
outboxRef.current = disposition.entries
|
||||
setOutbox(disposition.entries)
|
||||
@@ -275,7 +278,12 @@ export function useStructuredAgentSessionOutbox(args: {
|
||||
// A provider-history reconciliation can settle an earlier unknown as
|
||||
// rejected before the user presses Retry. Reusing that operation id only
|
||||
// replays the settled rejection forever, so rotate the id for a safe resend.
|
||||
if (current && submission?.dispatchState === 'rejected') {
|
||||
if (
|
||||
current &&
|
||||
(submission?.dispatchState === 'rejected' ||
|
||||
retryWithFreshClientMessageIdRef.current === clientMessageId)
|
||||
) {
|
||||
retryWithFreshClientMessageIdRef.current = null
|
||||
const rotated = outboxRef.current.map((entry) =>
|
||||
entry.clientMessageId === clientMessageId
|
||||
? {
|
||||
|
||||
@@ -24,6 +24,8 @@ export type StructuredAgentSessionSendDisposition = {
|
||||
/** The entry the queue is stuck on, or null when nothing blocks it. Always the
|
||||
* next value, never "unchanged": the caller assigns it verbatim. */
|
||||
blockedClientMessageId: string | null
|
||||
/** A rejected result arrived before the journal snapshot; Retry must rotate this id. */
|
||||
retryWithFreshClientMessageId: string | null
|
||||
}
|
||||
|
||||
type SendDispositionInput = {
|
||||
@@ -118,7 +120,8 @@ export function disposeStructuredAgentSessionSendResult(
|
||||
return {
|
||||
entries,
|
||||
error: result.refusal.message,
|
||||
blockedClientMessageId: entries[0]?.clientMessageId ?? null
|
||||
blockedClientMessageId: entries[0]?.clientMessageId ?? null,
|
||||
retryWithFreshClientMessageId: null
|
||||
}
|
||||
}
|
||||
const submission = result.value.submission
|
||||
@@ -126,21 +129,24 @@ export function disposeStructuredAgentSessionSendResult(
|
||||
return {
|
||||
entries: dropEntry(input),
|
||||
error: 'Message delivery is unconfirmed and Orca will not send it again',
|
||||
blockedClientMessageId: input.blockedClientMessageId
|
||||
blockedClientMessageId: input.blockedClientMessageId,
|
||||
retryWithFreshClientMessageId: null
|
||||
}
|
||||
}
|
||||
if (submission.dispatchState === 'accepted') {
|
||||
return {
|
||||
entries: dropEntry(input),
|
||||
error: null,
|
||||
blockedClientMessageId: input.blockedClientMessageId
|
||||
blockedClientMessageId: input.blockedClientMessageId,
|
||||
retryWithFreshClientMessageId: null
|
||||
}
|
||||
}
|
||||
if (submission.dispatchState === 'rejected') {
|
||||
return {
|
||||
entries: replaceEntryState(input, 'queued'),
|
||||
error: rejectionNotice(submission.reason),
|
||||
blockedClientMessageId: input.entry.clientMessageId
|
||||
blockedClientMessageId: input.entry.clientMessageId,
|
||||
retryWithFreshClientMessageId: input.entry.clientMessageId
|
||||
}
|
||||
}
|
||||
// `pending` is the host saying the message was written and is awaiting the
|
||||
@@ -153,7 +159,8 @@ export function disposeStructuredAgentSessionSendResult(
|
||||
submission.dispatchState === 'unknown' ? 'unconfirmed' : 'dispatching'
|
||||
),
|
||||
error: null,
|
||||
blockedClientMessageId: input.blockedClientMessageId
|
||||
blockedClientMessageId: input.blockedClientMessageId,
|
||||
retryWithFreshClientMessageId: null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,6 +177,7 @@ export function disposeStructuredAgentSessionSendFailure(
|
||||
error: deliveryUnknown ? 'Message delivery is unconfirmed' : String(input.cause),
|
||||
blockedClientMessageId: deliveryUnknown
|
||||
? input.blockedClientMessageId
|
||||
: input.entry.clientMessageId
|
||||
: input.entry.clientMessageId,
|
||||
retryWithFreshClientMessageId: null
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user