mirror of
https://github.com/stablyai/orca.git
synced 2026-10-07 16:02:29 +00:00
fix(native-chat): read an older host's restart answer as recovered when sending, too
Hosts from before the recovered marker publish only the restart reason. The working-state rule already read that reason as recovered; the send answer now uses the same predicate, so both agree on those hosts.
This commit is contained in:
@@ -411,6 +411,33 @@ describe('ambiguous operation refusals', () => {
|
||||
expect(disposition.entries).toMatchObject([{ clientMessageId: 'fresh-id', state: 'rejected' }])
|
||||
})
|
||||
|
||||
it("parks an older host's restart answer that carries the reason but not the recovered marker", () => {
|
||||
const result = rejectedWith(null)
|
||||
if (!result.ok || !('submission' in result.value)) {
|
||||
throw new Error('expected a send result')
|
||||
}
|
||||
result.value.submission = {
|
||||
...result.value.submission,
|
||||
dispatchState: 'unknown',
|
||||
reason: 'host_restarted_before_acknowledgement'
|
||||
}
|
||||
|
||||
const disposition = disposeStructuredAgentSessionSendResult({
|
||||
entries: [entry],
|
||||
entry,
|
||||
result,
|
||||
createOperationId: () => 'unused'
|
||||
})
|
||||
|
||||
expect(disposition.entries).toMatchObject([
|
||||
{
|
||||
clientMessageId: entry.clientMessageId,
|
||||
state: 'unconfirmed',
|
||||
retryAfterUnknownSubmittedAt: -1
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
it('parks a recovered missing submission without polling forever', () => {
|
||||
const result = rejectedWith(null)
|
||||
if (!result.ok || !('submission' in result.value)) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
import type { AgentSessionFailureFact } from './agent-session-failure'
|
||||
import type { AgentSessionFailureWordsContext } from './agent-session-failure-words'
|
||||
import { classifyDispatchRejection } from './structured-agent-session-dispatch-rejection'
|
||||
import { isRecoveredStructuredAgentSessionSubmission } from './structured-agent-session-unanswered-dispatch'
|
||||
import {
|
||||
classifyStructuredAgentSessionSendFailure,
|
||||
requeueStructuredAgentSessionSendRefusal,
|
||||
@@ -277,7 +278,7 @@ export function disposeStructuredAgentSessionSendResult(
|
||||
error: null
|
||||
}
|
||||
}
|
||||
if (submission.dispatchState === 'unknown' && submission.recovered) {
|
||||
if (isRecoveredStructuredAgentSessionSubmission(submission)) {
|
||||
return {
|
||||
entries: input.entries.map((candidate) =>
|
||||
candidate.clientMessageId === input.entry.clientMessageId
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { AgentJournalSubmission } from './agent-session-journal-types'
|
||||
import {
|
||||
isRecoveredStructuredAgentSessionSubmission,
|
||||
isUnansweredStructuredAgentSessionDispatch
|
||||
} from './structured-agent-session-unanswered-dispatch'
|
||||
|
||||
function submission(patch: Partial<AgentJournalSubmission>): AgentJournalSubmission {
|
||||
return {
|
||||
clientMessageId: 'client-1',
|
||||
fence: 1,
|
||||
payloadFingerprint: 'fingerprint',
|
||||
dispatchState: 'unknown',
|
||||
providerItemId: null,
|
||||
reason: null,
|
||||
submittedAt: 1,
|
||||
resolvedAt: 1,
|
||||
...patch
|
||||
}
|
||||
}
|
||||
|
||||
describe('a send whose outcome the host lost for good', () => {
|
||||
it('is one the host marked recovered', () => {
|
||||
const row = submission({ recovered: true, reason: 'provider_exited_before_acknowledgement' })
|
||||
expect(isRecoveredStructuredAgentSessionSubmission(row)).toBe(true)
|
||||
expect(isUnansweredStructuredAgentSessionDispatch(row)).toBe(false)
|
||||
})
|
||||
|
||||
it("is an older host's restart row, which carries the reason without the marker", () => {
|
||||
const row = submission({ reason: 'host_restarted_before_acknowledgement' })
|
||||
expect(isRecoveredStructuredAgentSessionSubmission(row)).toBe(true)
|
||||
expect(isUnansweredStructuredAgentSessionDispatch(row)).toBe(false)
|
||||
})
|
||||
|
||||
it('is never a live unknown, which something still running may answer', () => {
|
||||
const row = submission({ reason: 'provider_ack_ambiguous' })
|
||||
expect(isRecoveredStructuredAgentSessionSubmission(row)).toBe(false)
|
||||
expect(isUnansweredStructuredAgentSessionDispatch(row)).toBe(true)
|
||||
})
|
||||
|
||||
it('is never a settled send, whatever its reason says', () => {
|
||||
expect(
|
||||
isRecoveredStructuredAgentSessionSubmission(
|
||||
submission({ dispatchState: 'rejected', reason: 'host_restarted_before_acknowledgement' })
|
||||
)
|
||||
).toBe(false)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,19 @@
|
||||
import type { AgentJournalSubmission } from './agent-session-journal-types'
|
||||
import { isQueuedAgentJournalSubmission } from './agent-session-queued-submission'
|
||||
|
||||
/** A send whose outcome the host lost for good when the process that sent it went away (a restart,
|
||||
* a provider exit, an idle release): nothing still running can answer it. */
|
||||
export function isRecoveredStructuredAgentSessionSubmission(
|
||||
submission: Pick<AgentJournalSubmission, 'dispatchState' | 'recovered' | 'reason'>
|
||||
): boolean {
|
||||
return (
|
||||
submission.dispatchState === 'unknown' &&
|
||||
(submission.recovered === true ||
|
||||
// Older hosts publish the recovery reason but omit the optional marker.
|
||||
submission.reason === 'host_restarted_before_acknowledgement')
|
||||
)
|
||||
}
|
||||
|
||||
/** One send the provider has neither opened a turn for nor refused; the rule is explained on
|
||||
* `hasUnansweredStructuredAgentSessionDispatch`, which asks it of every send. */
|
||||
export function isUnansweredStructuredAgentSessionDispatch(
|
||||
@@ -15,9 +28,7 @@ export function isUnansweredStructuredAgentSessionDispatch(
|
||||
(currentFence == null || submission.fence >= currentFence) &&
|
||||
(submission.dispatchState === 'pending' ||
|
||||
(submission.dispatchState === 'unknown' &&
|
||||
submission.recovered !== true &&
|
||||
// Older hosts publish the recovery reason but omit the optional marker.
|
||||
submission.reason !== 'host_restarted_before_acknowledgement'))
|
||||
!isRecoveredStructuredAgentSessionSubmission(submission)))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user