mirror of
https://github.com/stablyai/orca.git
synced 2026-10-31 00:57:45 +00:00
fix(mobile): keep chat-agent create when the capability check spans a lan-to-relay switch (#33697)
This commit is contained in:
@@ -3,6 +3,7 @@ import { act, create, type ReactTestRenderer } from 'react-test-renderer'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import type { RpcClient } from '../transport/rpc-client'
|
||||
import { FakeSession } from '../transport/mobile-endpoint-supervisor-test-fakes'
|
||||
import { createStableLogicalRpcClient } from '../transport/stable-logical-rpc-client'
|
||||
import { useNewWorktreeRuntimeCapabilities } from './worktree-create-capability'
|
||||
|
||||
function connection() {
|
||||
@@ -113,6 +114,34 @@ describe('capability probe recovery', () => {
|
||||
expect(current?.tasksSupported).toBe(true)
|
||||
expect(host.sendRequest).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
it('keeps a pending create probe when the same host switches from LAN to relay', async () => {
|
||||
const lan = new FakeSession('connected')
|
||||
lan.getLastConnectedAt = () => 1
|
||||
let dropLan: ((reason: Error) => void) | undefined
|
||||
lan.sendRequest.mockImplementation(
|
||||
() =>
|
||||
new Promise((_resolve, reject) => {
|
||||
dropLan = reject
|
||||
})
|
||||
)
|
||||
const relay = connection()
|
||||
relay.sendRequest.mockResolvedValue({
|
||||
id: 'relay',
|
||||
ok: true,
|
||||
result: { capabilities: ['agent.launch.v2', 'worktree.create-idempotency.v1'] },
|
||||
_meta: { runtimeId: 'test' }
|
||||
})
|
||||
const logical = createStableLogicalRpcClient(lan, 'lan')
|
||||
await render(logical)
|
||||
const launch = current?.getAgentLaunchSupport()
|
||||
const idempotency = current?.getWorktreeCreateCutoverSupport()
|
||||
await act(async () => {
|
||||
await logical.migrateTo(relay.client, 'relay')
|
||||
dropLan?.(new Error('socket closed'))
|
||||
})
|
||||
await expect(launch).resolves.toEqual({ replay: false })
|
||||
await expect(idempotency).resolves.toBeTruthy()
|
||||
})
|
||||
it.each(['before', 'after'])(
|
||||
'ignores a prior connection reply resolved %s the current reply',
|
||||
async (order) => {
|
||||
|
||||
@@ -89,37 +89,43 @@ export function useNewWorktreeRuntimeCapabilities(
|
||||
promise: Promise<NewWorktreeRuntimeCapabilities | null>
|
||||
} | null>(null)
|
||||
const getCapabilities = useCallback(async (): Promise<NewWorktreeRuntimeCapabilities | null> => {
|
||||
const generation = client?.getGeneration?.()
|
||||
const connectedAt = client?.getLastConnectedAt()
|
||||
let probe = capabilityProbeRef.current
|
||||
if (
|
||||
!probe ||
|
||||
probe.client !== client ||
|
||||
probe.generation !== generation ||
|
||||
probe.connectedAt !== connectedAt
|
||||
) {
|
||||
// Queued creates can precede passive effects; share the same status barrier.
|
||||
probe = {
|
||||
client,
|
||||
generation,
|
||||
connectedAt,
|
||||
promise: client ? readNewWorktreeRuntimeCapabilities(client) : Promise.resolve(null)
|
||||
for (;;) {
|
||||
const generation = client?.getGeneration?.()
|
||||
const connectedAt = client?.getLastConnectedAt()
|
||||
let probe = capabilityProbeRef.current
|
||||
if (
|
||||
!probe ||
|
||||
probe.client !== client ||
|
||||
probe.generation !== generation ||
|
||||
probe.connectedAt !== connectedAt
|
||||
) {
|
||||
// Queued creates can precede passive effects; share the same status barrier.
|
||||
probe = {
|
||||
client,
|
||||
generation,
|
||||
connectedAt,
|
||||
promise: client ? readNewWorktreeRuntimeCapabilities(client) : Promise.resolve(null)
|
||||
}
|
||||
capabilityProbeRef.current = probe
|
||||
}
|
||||
const capabilities = await probe.promise
|
||||
const current = capabilityProbeRef.current
|
||||
if (
|
||||
current === probe &&
|
||||
client?.getGeneration?.() === generation &&
|
||||
client?.getLastConnectedAt() === connectedAt
|
||||
) {
|
||||
if (!capabilities) {
|
||||
// Probe failure does not establish missing host support.
|
||||
capabilityProbeRef.current = null
|
||||
}
|
||||
return capabilities
|
||||
}
|
||||
// A reconnect or path switch keeps the host; wait for the current connection's answer.
|
||||
if (current?.client !== client) {
|
||||
return null
|
||||
}
|
||||
capabilityProbeRef.current = probe
|
||||
}
|
||||
const capabilities = await probe.promise
|
||||
if (
|
||||
capabilityProbeRef.current !== probe ||
|
||||
client?.getGeneration?.() !== generation ||
|
||||
client?.getLastConnectedAt() !== connectedAt
|
||||
) {
|
||||
return null
|
||||
}
|
||||
if (!capabilities && capabilityProbeRef.current === probe) {
|
||||
// Probe failure does not establish missing host support.
|
||||
capabilityProbeRef.current = null
|
||||
}
|
||||
return capabilities
|
||||
}, [client])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Reference in New Issue
Block a user