From b4a93955e32514ce376457d3b7923eeec840bc78 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sun, 8 Feb 2026 09:15:39 +0700 Subject: [PATCH] Update files --- packages/app/src/components/message.tsx | 46 ++++++++----------- .../server/src/server/agent/agent-manager.ts | 19 +++++++- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/packages/app/src/components/message.tsx b/packages/app/src/components/message.tsx index 857cae330..4333e542c 100644 --- a/packages/app/src/components/message.tsx +++ b/packages/app/src/components/message.tsx @@ -2,7 +2,6 @@ import { View, Text, Pressable, - Animated, ActivityIndicator, StyleProp, ViewStyle, @@ -19,6 +18,14 @@ import { useContext, } from "react"; import type { ReactNode, ComponentType } from "react"; +import Animated, { + useSharedValue, + useAnimatedStyle, + withRepeat, + withTiming, + Easing, + cancelAnimation, +} from "react-native-reanimated"; import Markdown, { MarkdownIt } from "react-native-markdown-display"; import * as Linking from "expo-linking"; import { @@ -980,38 +987,23 @@ const ExpandableBadge = memo(function ExpandableBadge({ const hasDetails = Boolean(renderDetails); const detailContent = hasDetails && isExpanded ? renderDetails?.() : null; - const spinAnim = useRef(new Animated.Value(0)).current; + const rotation = useSharedValue(0); useEffect(() => { - let loop: Animated.CompositeAnimation | null = null; if (isLoading) { - spinAnim.setValue(0); - loop = Animated.loop( - Animated.timing(spinAnim, { - toValue: 1, - duration: 1400, - useNativeDriver: true, - easing: (t) => t, - }) + rotation.value = 0; + rotation.value = withRepeat( + withTiming(360, { duration: 1400, easing: Easing.linear }), + -1 ); - loop.start(); } else { - spinAnim.stopAnimation(); + cancelAnimation(rotation); } + }, [isLoading]); - return () => { - loop?.stop(); - }; - }, [isLoading, spinAnim]); - - const spin = useMemo( - () => - spinAnim.interpolate({ - inputRange: [0, 1], - outputRange: ["0deg", "360deg"], - }), - [spinAnim] - ); + const spinStyle = useAnimatedStyle(() => ({ + transform: [{ rotate: `${rotation.value}deg` }], + })); const IconComponent = icon; const iconColor = isError @@ -1021,7 +1013,7 @@ const ExpandableBadge = memo(function ExpandableBadge({ let iconNode: ReactNode = null; if (isLoading) { iconNode = ( - + ); diff --git a/packages/server/src/server/agent/agent-manager.ts b/packages/server/src/server/agent/agent-manager.ts index ef59f355a..d96561d4a 100644 --- a/packages/server/src/server/agent/agent-manager.ts +++ b/packages/server/src/server/agent/agent-manager.ts @@ -799,7 +799,6 @@ export class AgentManager { // Await the generator's .return() to ensure the finally block runs // and pendingRun is properly cleared before we return. await pendingRun.return(undefined as unknown as AgentStreamEvent); - return true; } catch (error) { this.logger.error( { err: error, agentId }, @@ -807,6 +806,24 @@ export class AgentManager { ); throw error; } + + // Clear any pending permissions that weren't cleaned up by handleStreamEvent. + // Due to microtask ordering, .return() may force the generator to its finally + // block before it consumes the turn_canceled event, skipping our cleanup code. + if (agent.pendingPermissions.size > 0) { + for (const [requestId] of agent.pendingPermissions) { + this.dispatchStream(agent.id, { + type: "permission_resolved", + provider: agent.provider, + requestId, + resolution: { behavior: "deny", message: "Interrupted" }, + }); + } + agent.pendingPermissions.clear(); + this.emitState(agent); + } + + return true; } getPendingPermissions(agentId: string): AgentPermissionRequest[] {