mirror of
https://github.com/stablyai/orca.git
synced 2026-09-28 08:02:43 +00:00
fix(native-chat): restore rewind refusal delivery after merge
This commit is contained in:
@@ -86,7 +86,9 @@ vi.mock('./use-structured-agent-session', async () => {
|
||||
isWorking: false,
|
||||
isMonitoringBackgroundTasks: mocks.monitoringBackgroundTasks,
|
||||
supportsBackgroundTaskStop: mocks.supportsBackgroundTaskStop,
|
||||
supportsBackgroundTaskStopAll: mocks.supportsBackgroundTaskStopAll,
|
||||
backgroundTasksView: {
|
||||
supportsBackgroundTaskStopAll: mocks.supportsBackgroundTaskStopAll
|
||||
},
|
||||
backgroundTasks: mocks.backgroundTasks,
|
||||
turnId: null,
|
||||
cancel: vi.fn(),
|
||||
|
||||
@@ -7,7 +7,11 @@
|
||||
|
||||
import { useCallback, useRef, useState } from 'react'
|
||||
import * as conversationCommands from './structured-conversation-command-send'
|
||||
import type { AgentSessionMutationResult } from '../../../../shared/agent-session-wire'
|
||||
import type {
|
||||
AgentSessionMutationResult,
|
||||
AgentSessionWireRefusal
|
||||
} from '../../../../shared/agent-session-wire'
|
||||
import { hasRuntimeRpcErrorCode } from '../../../../shared/runtime-rpc-error-code'
|
||||
import { agentSessionRefusalOperationState } from '../../../../shared/agent-session-refusal-retry'
|
||||
import { structuredAgentSessionPayloadFingerprint } from '../../../../shared/structured-agent-session-mutation'
|
||||
import type { RuntimeClientTarget } from '@/runtime/runtime-rpc-client'
|
||||
@@ -18,7 +22,8 @@ export type StructuredAgentSessionMutate = <T>(
|
||||
method: string,
|
||||
fingerprintMethod: string,
|
||||
fields: Record<string, unknown>,
|
||||
operationIdOverride?: string | null
|
||||
operationIdOverride?: string | null,
|
||||
onFailure?: (refusal?: AgentSessionWireRefusal) => void
|
||||
) => Promise<T | null>
|
||||
|
||||
export function useStructuredAgentSessionMutate(args: {
|
||||
@@ -37,7 +42,8 @@ export function useStructuredAgentSessionMutate(args: {
|
||||
method: string,
|
||||
fingerprintMethod: string,
|
||||
fields: Record<string, unknown>,
|
||||
operationIdOverride?: string | null
|
||||
operationIdOverride?: string | null,
|
||||
onFailure?: (refusal?: AgentSessionWireRefusal) => void
|
||||
): Promise<T | null> => {
|
||||
if (stateRef.current.fence === null) {
|
||||
return null
|
||||
@@ -63,7 +69,17 @@ export function useStructuredAgentSessionMutate(args: {
|
||||
...fields
|
||||
})
|
||||
} catch (error) {
|
||||
if (stateRef.current.fence === targetFence) {
|
||||
if (onFailure) {
|
||||
onFailure(
|
||||
hasRuntimeRpcErrorCode(error, 'method_not_found')
|
||||
? {
|
||||
code: 'structured_agent_session_unsupported',
|
||||
message: '',
|
||||
rewindReason: 'unsupported'
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
} else if (stateRef.current.fence === targetFence) {
|
||||
setWriteError(error instanceof Error ? error.message : 'Request was not sent')
|
||||
}
|
||||
return null
|
||||
@@ -75,12 +91,14 @@ export function useStructuredAgentSessionMutate(args: {
|
||||
) {
|
||||
operationIds.current.delete(key)
|
||||
}
|
||||
if (stateRef.current.fence === targetFence) {
|
||||
if (onFailure) {
|
||||
onFailure(result.refusal)
|
||||
} else if (stateRef.current.fence === targetFence) {
|
||||
setWriteError(result.refusal.message)
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (stateRef.current.fence !== targetFence) {
|
||||
if (stateRef.current.fence !== targetFence && fingerprintMethod !== 'agentSession.rewind') {
|
||||
return null
|
||||
}
|
||||
if (!conversationCommands.isUnconfirmedConversationCommand(fingerprintMethod, result.value)) {
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
import { translate } from '@/i18n/i18n'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import type {
|
||||
AgentSessionMutationResult,
|
||||
AgentSessionWireRefusal
|
||||
} from '../../../../shared/agent-session-wire'
|
||||
import { hasRuntimeRpcErrorCode } from '../../../../shared/runtime-rpc-error-code'
|
||||
import { agentSessionRefusalOperationState } from '../../../../shared/agent-session-refusal-retry'
|
||||
import { structuredAgentSessionPayloadFingerprint } from '../../../../shared/structured-agent-session-mutation'
|
||||
import type { RuntimeClientTarget } from '@/runtime/runtime-rpc-client'
|
||||
import { callStructuredAgentSession } from '@/runtime/structured-agent-session-client'
|
||||
import { structuredSessionOperationId } from './use-structured-agent-session-outbox'
|
||||
import * as conversationCommands from './structured-conversation-command-send'
|
||||
|
||||
export function useStructuredAgentSessionMutation(
|
||||
sessionId: string,
|
||||
target: RuntimeClientTarget,
|
||||
fence: number | null
|
||||
) {
|
||||
const stateRef = useRef({ fence })
|
||||
const [writeError, setWriteError] = useState<string | null>(null)
|
||||
const operationIds = useRef(new Map<string, string>())
|
||||
useEffect(() => {
|
||||
stateRef.current = { fence }
|
||||
}, [fence])
|
||||
const mutate = useCallback(
|
||||
async <T>(
|
||||
method: string,
|
||||
fingerprintMethod: string,
|
||||
fields: Record<string, unknown>,
|
||||
operationIdOverride?: string | null,
|
||||
onFailure?: (refusal?: AgentSessionWireRefusal) => void
|
||||
): Promise<T | null> => {
|
||||
if (stateRef.current.fence === null) {
|
||||
return null
|
||||
}
|
||||
const targetFence = stateRef.current.fence
|
||||
const key = `${sessionId}:${fingerprintMethod}:${JSON.stringify(fields)}`
|
||||
const clientOperationId =
|
||||
operationIdOverride ?? operationIds.current.get(key) ?? structuredSessionOperationId()
|
||||
operationIds.current.set(key, clientOperationId)
|
||||
let result: AgentSessionMutationResult<T>
|
||||
try {
|
||||
result = await callStructuredAgentSession<AgentSessionMutationResult<T>>(target, method, {
|
||||
envelope: {
|
||||
sessionId,
|
||||
clientOperationId,
|
||||
expectedRuntimeFence: targetFence,
|
||||
payloadFingerprint: structuredAgentSessionPayloadFingerprint({
|
||||
method: fingerprintMethod,
|
||||
sessionId,
|
||||
fields
|
||||
})
|
||||
},
|
||||
...fields
|
||||
})
|
||||
} catch (error) {
|
||||
if (onFailure) {
|
||||
onFailure(
|
||||
hasRuntimeRpcErrorCode(error, 'method_not_found')
|
||||
? {
|
||||
code: 'structured_agent_session_unsupported',
|
||||
message: '',
|
||||
rewindReason: 'unsupported'
|
||||
}
|
||||
: undefined
|
||||
)
|
||||
} else if (stateRef.current.fence === targetFence) {
|
||||
setWriteError(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: translate('components.native-chat.requestNotSent', 'Request was not sent')
|
||||
)
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (!result.ok) {
|
||||
if (
|
||||
agentSessionRefusalOperationState(fingerprintMethod, result.refusal.code) ===
|
||||
'settled-rejected'
|
||||
) {
|
||||
operationIds.current.delete(key)
|
||||
}
|
||||
if (onFailure) {
|
||||
onFailure(result.refusal)
|
||||
} else if (stateRef.current.fence === targetFence) {
|
||||
setWriteError(result.refusal.message)
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (stateRef.current.fence !== targetFence && fingerprintMethod !== 'agentSession.rewind') {
|
||||
return null
|
||||
}
|
||||
if (!conversationCommands.isUnconfirmedConversationCommand(fingerprintMethod, result.value)) {
|
||||
operationIds.current.delete(key)
|
||||
}
|
||||
setWriteError(null)
|
||||
return result.value
|
||||
},
|
||||
[sessionId, target]
|
||||
)
|
||||
|
||||
return { mutate, writeError }
|
||||
}
|
||||
@@ -191,31 +191,44 @@ export function useStructuredAgentSession(args: {
|
||||
)
|
||||
|
||||
const prompts = pendingStructuredSessionPrompts(state.items)
|
||||
const rewind = useNativeChatRewind({
|
||||
sessionId,
|
||||
hostBlockedReason: summary?.rewindBlockedReason,
|
||||
state,
|
||||
support:
|
||||
conversationSupport?.sessionId === sessionId && conversationSupport.fence === state.fence
|
||||
? conversationSupport.rewind
|
||||
: undefined,
|
||||
supportResolved:
|
||||
conversationSupport?.sessionId === sessionId && conversationSupport.fence === state.fence,
|
||||
blocked: Boolean(
|
||||
turnId ||
|
||||
prompts.length ||
|
||||
isMonitoringBackgroundTasks ||
|
||||
outboxController.outbox.length ||
|
||||
commandPending.current
|
||||
),
|
||||
send: (fields) =>
|
||||
mutate<AgentSessionRewindResult>(
|
||||
'agentSession.rewind',
|
||||
'agentSession.rewind',
|
||||
fields,
|
||||
undefined
|
||||
)
|
||||
})
|
||||
const rewindSupportResolved =
|
||||
conversationSupport?.sessionId === sessionId && conversationSupport.fence === state.fence
|
||||
const rewindSupport = rewindSupportResolved ? conversationSupport.rewind : undefined
|
||||
const rewindBlocked = Boolean(
|
||||
turnId ||
|
||||
prompts.length ||
|
||||
isMonitoringBackgroundTasks ||
|
||||
outboxController.outbox.length ||
|
||||
commandPending.current
|
||||
)
|
||||
const rewindInput = useMemo<Parameters<typeof useNativeChatRewind>[0]>(
|
||||
() => ({
|
||||
sessionId,
|
||||
hostBlockedReason: summary?.rewindBlockedReason,
|
||||
state,
|
||||
support: rewindSupport,
|
||||
supportResolved: rewindSupportResolved,
|
||||
blocked: rewindBlocked,
|
||||
send: (fields, onFailure) =>
|
||||
mutate<AgentSessionRewindResult>(
|
||||
'agentSession.rewind',
|
||||
'agentSession.rewind',
|
||||
fields,
|
||||
undefined,
|
||||
onFailure
|
||||
)
|
||||
}),
|
||||
[
|
||||
sessionId,
|
||||
summary?.rewindBlockedReason,
|
||||
state,
|
||||
rewindSupport,
|
||||
rewindSupportResolved,
|
||||
rewindBlocked,
|
||||
mutate
|
||||
]
|
||||
)
|
||||
const rewind = useNativeChatRewind(rewindInput)
|
||||
const { outbox } = outboxController
|
||||
const messages = useStructuredAgentSessionMessages(state.items, outbox, state.submissions)
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user