mirror of
https://github.com/stablyai/orca.git
synced 2026-10-03 00:02:19 +00:00
fix(native-chat): a chat the user closed while its agent started is not a failed start
A still-starting child the user's close cut counted as a failed start, since only 'user-stop' was excluded: a start-failure row, and queued messages rejected as a provider failure. Whether an ending fails its start is now one exhaustive switch, shared by the failed-start read and the delivery loop's handover: a user's stop or close never does; an exit, a failed attach and the host's own stops still do.
This commit is contained in:
@@ -26,7 +26,10 @@ import {
|
||||
oldestQueuedSubmission,
|
||||
recordStructuredAgentSessionStartFailure
|
||||
} from './structured-agent-session-start-failure-row'
|
||||
import { failedProviderChildStart } from './structured-agent-session-provider-child'
|
||||
import {
|
||||
childEndFailsItsStart,
|
||||
failedProviderChildStart
|
||||
} from './structured-agent-session-provider-child'
|
||||
import { handOverSubmission } from './structured-agent-session-turns'
|
||||
|
||||
export type StructuredAgentSessionDeliveryLoopDeps = {
|
||||
@@ -171,9 +174,9 @@ export class StructuredAgentSessionDeliveryLoop {
|
||||
if (!awaitedChild || (awaitedChild.phase === 'starting' && startFailure !== null)) {
|
||||
// The child waited on is gone, replaced by another, or settled its start without proving it.
|
||||
const ended = awaitedChild ? undefined : session.lastEndedChild
|
||||
// A user's Stop is not a failure: the next step starts, or waits on, a child for what is
|
||||
// queued. A host stop is: its cause is why the start did not land.
|
||||
if (ended?.cause === 'user-stop') {
|
||||
// A user's Stop or close is not a failure: the next step starts, or waits on, a child for
|
||||
// what is queued. A host stop is: its cause is why the start did not land.
|
||||
if (ended && !childEndFailsItsStart(ended.cause)) {
|
||||
return 'continue'
|
||||
}
|
||||
return this.fail(sessionId, {
|
||||
|
||||
+16
@@ -672,6 +672,22 @@ describe('how a stopped child ends the start its loop was waiting on', () => {
|
||||
expect(statusRows()).toEqual([])
|
||||
})
|
||||
|
||||
it('records no failure when the user closed the chat while its child started', async () => {
|
||||
// The close's stop alone: a close that aborts after it leaves the conversation indexed.
|
||||
const second = await stoppedWhileStarting(() =>
|
||||
host['serialize'](SESSION, () =>
|
||||
stopStructuredAgentSessionAgentUnderSerialize(host['lifetimeContext'](), SESSION, {
|
||||
cause: 'user-close'
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
await settleLoop()
|
||||
expect(conversation()?.lastEndedChild).toMatchObject({ cause: 'user-close' })
|
||||
expect(submission(second)?.dispatchState).not.toBe('rejected')
|
||||
expect(statusRows()).toEqual([])
|
||||
})
|
||||
|
||||
it('fails the start after a host stop, with the stop as the reason (R2)', async () => {
|
||||
const reason = 'Claude never finished starting, so Orca stopped it.'
|
||||
const second = await stoppedWhileStarting(() =>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import type { AgentSessionRecordStore } from '../../runtime/agent-session-record-store'
|
||||
import type { AgentSessionJournal } from '../agent-session-journal/journal-store'
|
||||
import type {
|
||||
StructuredAgentSessionChildEndCause,
|
||||
StructuredAgentSessionEndedChild,
|
||||
StructuredAgentSessionHostSession,
|
||||
StructuredAgentSessionProviderChild,
|
||||
@@ -64,7 +65,22 @@ export function failedProviderChildStart(
|
||||
session: Pick<ChildBearer, 'child' | 'lastEndedChild'>
|
||||
): StructuredAgentSessionEndedChild | null {
|
||||
const ended = session.lastEndedChild
|
||||
return !session.child && ended?.duringStartup && ended.cause !== 'user-stop' ? ended : null
|
||||
return !session.child && ended?.duringStartup && childEndFailsItsStart(ended.cause) ? ended : null
|
||||
}
|
||||
|
||||
/** Whether a child that ended this way, still starting, failed its start. A stop the user aimed at
|
||||
* this chat is their cancellation, never a failure; a host's own stop is why the start did not land. */
|
||||
export function childEndFailsItsStart(cause: StructuredAgentSessionChildEndCause): boolean {
|
||||
switch (cause) {
|
||||
case 'user-stop':
|
||||
case 'user-close':
|
||||
return false
|
||||
case 'exit':
|
||||
case 'attach-failed':
|
||||
case 'host-stop':
|
||||
case 'evict':
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function matchingChild(
|
||||
|
||||
Reference in New Issue
Block a user