diff --git a/src/renderer/src/runtime/structured-agent-session-host-capability-scaling.test.tsx b/src/renderer/src/runtime/structured-agent-session-host-capability-scaling.test.tsx index a41dd2bdd0f..ae75125406b 100644 --- a/src/renderer/src/runtime/structured-agent-session-host-capability-scaling.test.tsx +++ b/src/renderer/src/runtime/structured-agent-session-host-capability-scaling.test.tsx @@ -25,6 +25,7 @@ import { AGENT_SESSION_PERMISSION_OWN_RUNTIME_CAPABILITY } from '../../../shared import { createCompatibleRuntimeStatusResponse } from './runtime-compatibility-test-fixture' import { clearRuntimeCompatibilityCacheForTests } from './runtime-rpc-client' import { RuntimeHostStatusOwner } from '../../../shared/runtime-host-status-owner' +import type { RuntimeEnvironmentStatus } from '../../../shared/runtime-host-status' import { useStructuredAgentSessionOwnPermissionState } from './structured-agent-session-host-capability' const owners: RuntimeHostStatusOwner[] = [] @@ -145,3 +146,53 @@ it.each([10, 100])( expect(call).not.toHaveBeenCalled() } ) + +it('does not rerender local or other-host consumers when an unrelated host status changes', () => { + const unrelated = createCompatibleRuntimeStatusResponse('other') + const own = createCompatibleRuntimeStatusResponse('host') + if (!unrelated.ok || !own.ok) { + throw new Error('expected successful fixture') + } + const statuses = (otherStatus: typeof unrelated.result | null) => + new Map([ + ['host', { status: own.result, checkedAt: 1 }], + ['other', { status: otherStatus, checkedAt: 1 }] + ]) + useAppStore.setState({ runtimeStatusByEnvironmentId: statuses(null) }) + let renders = 0 + const targets = [{ kind: 'local' }, { kind: 'environment', environmentId: 'host' }] as const + const views = targets.map((target) => + renderHook(() => { + renders += 1 + return useStructuredAgentSessionOwnPermissionState(target) + }) + ) + const before = renders + for (let flip = 0; flip < 10; flip += 1) { + act(() => + useAppStore.setState({ + runtimeStatusByEnvironmentId: statuses(flip % 2 === 0 ? unrelated.result : null) + }) + ) + } + expect(renders - before).toBe(0) + const hostState = views[1].result.current + act(() => + useAppStore.setState({ + runtimeStatusByEnvironmentId: new Map([ + [ + 'host', + { + status: { + ...own.result, + capabilities: + hostState === 'supported' ? [] : [AGENT_SESSION_PERMISSION_OWN_RUNTIME_CAPABILITY] + }, + checkedAt: 2 + } + ] + ]) + }) + ) + expect(views[1].result.current).toBe(hostState === 'supported' ? 'unsupported' : 'supported') +}) diff --git a/src/renderer/src/runtime/structured-agent-session-host-capability.ts b/src/renderer/src/runtime/structured-agent-session-host-capability.ts index 4107600a8e8..99592766aa5 100644 --- a/src/renderer/src/runtime/structured-agent-session-host-capability.ts +++ b/src/renderer/src/runtime/structured-agent-session-host-capability.ts @@ -1,5 +1,5 @@ import { AGENT_SESSION_PERMISSION_OWN_RUNTIME_CAPABILITY } from '../../../shared/agent-session-permission-modes-capability' -import { useEffect, useMemo, useState, useSyncExternalStore } from 'react' +import { useEffect, useState, useSyncExternalStore } from 'react' import { useAppStore } from '@/store' import { AGENT_SESSION_CONVERSATION_STOP_RUNTIME_CAPABILITY, @@ -37,19 +37,19 @@ export function useStructuredAgentSessionOwnPermissionState( ): StructuredAgentSessionHostCapabilityState { const capability = AGENT_SESSION_PERMISSION_OWN_RUNTIME_CAPABILITY const environmentId = target?.kind === 'environment' ? target.environmentId : null - const statuses = useAppStore((state) => state.runtimeStatusByEnvironmentId) - const remote = useMemo((): StructuredAgentSessionHostCapabilityState => { + // Select the verdict, not the map: unrelated host flips must not rerender every chat. + const remote = useAppStore((state): StructuredAgentSessionHostCapabilityState => { if (environmentId === null) { return 'unknown' } - const entry = statuses.get(environmentId) + const entry = state.runtimeStatusByEnvironmentId.get(environmentId) const status = isRuntimeHostContactRevoked(entry) ? null : lastVerifiedRuntimeStatus(entry) return status ? status.capabilities?.includes(capability) ? 'supported' : 'unsupported' : 'unknown' - }, [statuses, environmentId, capability]) + }) const local = useSyncExternalStore( subscribeLocalRuntimeCapabilityObservation, () => localCapabilityState(capability),