From 4f49644af2a0d0ae3fddaa7a3942b1641bb4f906 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gabriele=20Cin=C3=A0?= Date: Tue, 16 Dec 2025 12:05:19 +0000 Subject: [PATCH] feat(ui): implement cursor-based pagination for messages - Add useInfiniteTaskMessages hook with cursor pagination - Implement infinite scroll with IntersectionObserver in task-messages.tsx - Add scroll position preservation when loading older messages - Update SDK to 0.1.0-alpha.11 with listPaginated support - Add spinner component for loading indicator --- .../task-messages/task-messages.tsx | 158 ++++++++++++++- agentex-ui/components/ui/spinner.tsx | 30 +++ .../hooks/use-infinite-task-messages.ts | 84 ++++++++ agentex-ui/hooks/use-task-messages.ts | 190 ++++++++++++------ agentex-ui/hooks/use-task-subscription.ts | 85 +++++--- agentex-ui/package-lock.json | 8 +- agentex-ui/package.json | 2 +- 7 files changed, 449 insertions(+), 108 deletions(-) create mode 100644 agentex-ui/components/ui/spinner.tsx create mode 100644 agentex-ui/hooks/use-infinite-task-messages.ts diff --git a/agentex-ui/components/task-messages/task-messages.tsx b/agentex-ui/components/task-messages/task-messages.tsx index 6733083..85e9d1f 100644 --- a/agentex-ui/components/task-messages/task-messages.tsx +++ b/agentex-ui/components/task-messages/task-messages.tsx @@ -1,4 +1,12 @@ -import { Fragment, memo, useEffect, useMemo, useRef, useState } from 'react'; +import { + Fragment, + memo, + useEffect, + useLayoutEffect, + useMemo, + useRef, + useState, +} from 'react'; import { AnimatePresence, motion } from 'framer-motion'; @@ -9,7 +17,8 @@ import { TaskMessageScrollContainer } from '@/components/task-messages/task-mess import { TaskMessageTextContent } from '@/components/task-messages/task-message-text-content'; import { TaskMessageToolPair } from '@/components/task-messages/task-message-tool-pair'; import { ShimmeringText } from '@/components/ui/shimmering-text'; -import { useTaskMessages } from '@/hooks/use-task-messages'; +import { Spinner } from '@/components/ui/spinner'; +import { useInfiniteTaskMessages } from '@/hooks/use-infinite-task-messages'; import type { TaskMessage, @@ -30,13 +39,42 @@ type MessagePair = { function TaskMessagesImpl({ taskId, headerRef }: TaskMessagesProps) { const lastPairRef = useRef(null); const containerRef = useRef(null); + const loadMoreRef = useRef(null); + const scrollContainerRef = useRef(null); const [containerHeight, setContainerHeight] = useState(0); + // Prevent IntersectionObserver from triggering during initial load + const [isInitialScrollComplete, setIsInitialScrollComplete] = useState(false); + const hasScrolledToBottomRef = useRef(false); + // Track which taskId we last scrolled for (to handle task switching) + const lastScrolledTaskIdRef = useRef(null); + // For scroll position preservation when loading older messages + const previousScrollHeightRef = useRef(0); + const wasFetchingRef = useRef(false); const { agentexClient } = useAgentexClient(); - const { data: queryData } = useTaskMessages({ agentexClient, taskId }); + const { + data: queryData, + fetchNextPage, + hasNextPage, + isFetchingNextPage, + } = useInfiniteTaskMessages({ agentexClient, taskId }); const messages = useMemo(() => queryData?.messages ?? [], [queryData]); + + // Reset scroll state when switching tasks + useEffect(() => { + hasScrolledToBottomRef.current = false; + setIsInitialScrollComplete(false); + previousMessageCountRef.current = 0; + // Don't reset lastScrolledTaskIdRef here - it's used to detect task changes + }, [taskId]); + + // Check if any message is currently streaming + const hasStreamingMessage = useMemo( + () => messages.some(msg => msg.streaming_status === 'IN_PROGRESS'), + [messages] + ); const previousMessageCountRef = useRef(messages.length); const toolCallIdToResponseMap = useMemo< @@ -101,12 +139,43 @@ function TaskMessagesImpl({ taskId, headerRef }: TaskMessagesProps) { const lastPair = messagePairs[messagePairs.length - 1]!; const hasNoAgentMessages = lastPair.agentMessages.length === 0; - const rpcStatus = queryData?.rpcStatus; + const lastUserMessageIsRecent = + lastPair.userMessage.content.author === 'user'; + // Show thinking only when: + // - User sent the last message AND no agent response yet AND nothing is streaming + // Once streaming starts or agent responds, hide the thinking indicator return ( - hasNoAgentMessages && (rpcStatus === 'pending' || rpcStatus === 'success') + hasNoAgentMessages && lastUserMessageIsRecent && !hasStreamingMessage + ); + }, [messagePairs, hasStreamingMessage]); + + // Use IntersectionObserver to load more when sentinel becomes visible + // Only enable after initial scroll to bottom is complete to avoid unwanted fetches + useEffect(() => { + const sentinel = loadMoreRef.current; + if (!sentinel || !isInitialScrollComplete) { + return; + } + + const observer = new IntersectionObserver( + entries => { + const entry = entries[0]; + if (entry?.isIntersecting && hasNextPage && !isFetchingNextPage) { + // Save scroll height BEFORE fetching so we can restore position after + if (scrollContainerRef.current) { + previousScrollHeightRef.current = + scrollContainerRef.current.scrollHeight; + } + fetchNextPage(); + } + }, + { threshold: 0.1, rootMargin: '200px 0px 0px 0px' } ); - }, [messagePairs, queryData?.rpcStatus]); + + observer.observe(sentinel); + return () => observer.disconnect(); + }, [hasNextPage, isFetchingNextPage, fetchNextPage, isInitialScrollComplete]); useEffect(() => { const measureHeight = () => { @@ -115,6 +184,8 @@ function TaskMessagesImpl({ taskId, headerRef }: TaskMessagesProps) { while (element) { const overflowY = window.getComputedStyle(element).overflowY; if (overflowY === 'auto' || overflowY === 'scroll') { + // Store reference to scroll container for position preservation + scrollContainerRef.current = element; setContainerHeight( element.clientHeight - (headerRef.current?.clientHeight ?? 0) ); @@ -131,11 +202,67 @@ function TaskMessagesImpl({ taskId, headerRef }: TaskMessagesProps) { return () => window.removeEventListener('resize', measureHeight); }, [headerRef, messages]); + // Preserve scroll position when older messages are loaded + // This runs BEFORE paint to prevent visual jumping + useLayoutEffect(() => { + // Detect when fetching completes (was fetching, now not fetching) + if (wasFetchingRef.current && !isFetchingNextPage) { + const scrollContainer = scrollContainerRef.current; + const previousScrollHeight = previousScrollHeightRef.current; + + if (scrollContainer && previousScrollHeight > 0) { + const newScrollHeight = scrollContainer.scrollHeight; + const heightDifference = newScrollHeight - previousScrollHeight; + + if (heightDifference > 0) { + // Adjust scroll position by the height of newly added content + scrollContainer.scrollTop += heightDifference; + } + + // Reset for next pagination + previousScrollHeightRef.current = 0; + } + } + + wasFetchingRef.current = isFetchingNextPage; + }, [isFetchingNextPage]); + + // Initial scroll: use useLayoutEffect to scroll BEFORE browser paints + // This prevents the user from seeing the scroll animation on first load + useLayoutEffect(() => { + // Check if we need to scroll: either first load OR task changed + const needsScroll = + !hasScrolledToBottomRef.current || + lastScrolledTaskIdRef.current !== taskId; + + if (needsScroll && messagePairs.length > 0 && lastPairRef.current) { + // Scroll instantly before paint + lastPairRef.current.scrollIntoView({ + behavior: 'instant', + block: 'end', + }); + + hasScrolledToBottomRef.current = true; + lastScrolledTaskIdRef.current = taskId; + + // Enable IntersectionObserver after a short delay + setTimeout(() => { + setIsInitialScrollComplete(true); + }, 300); + } + }, [messagePairs.length, taskId]); + + // Subsequent new messages: smooth scroll (using regular useEffect) useEffect(() => { const previousCount = previousMessageCountRef.current; const currentCount = messagePairs.length; - if (currentCount > previousCount && lastPairRef.current) { + // Only handle NEW messages after initial load + if ( + hasScrolledToBottomRef.current && + currentCount > previousCount && + lastPairRef.current + ) { setTimeout(() => { lastPairRef.current?.scrollIntoView({ behavior: 'smooth', @@ -179,6 +306,23 @@ function TaskMessagesImpl({ taskId, headerRef }: TaskMessagesProps) { ref={containerRef} className="flex w-full flex-1 flex-col items-center" > + {/* Sentinel for IntersectionObserver - triggers loading older messages */} +
+ + {/* Loading indicator for older messages */} + {isFetchingNextPage && ( +
+ +
+ )} + + {/* Indicator when all history is loaded */} + {!hasNextPage && messages.length > 0 && ( +
+ Beginning of conversation +
+ )} + {messagePairs.map((pair, index) => { const isLastPair = index === messagePairs.length - 1; const shouldShowThinking = isLastPair && shouldShowThinkingForLastPair; diff --git a/agentex-ui/components/ui/spinner.tsx b/agentex-ui/components/ui/spinner.tsx new file mode 100644 index 0000000..1cc8258 --- /dev/null +++ b/agentex-ui/components/ui/spinner.tsx @@ -0,0 +1,30 @@ +import { cn } from '@/lib/utils'; + +type SpinnerProps = { + className?: string; +}; + +export function Spinner({ className }: SpinnerProps) { + return ( + + + + + ); +} diff --git a/agentex-ui/hooks/use-infinite-task-messages.ts b/agentex-ui/hooks/use-infinite-task-messages.ts new file mode 100644 index 0000000..89a4d59 --- /dev/null +++ b/agentex-ui/hooks/use-infinite-task-messages.ts @@ -0,0 +1,84 @@ +import { useInfiniteQuery } from '@tanstack/react-query'; + +import type AgentexSDK from 'agentex'; +import type { + MessageListPaginatedResponse, + TaskMessage, +} from 'agentex/resources'; + +// Re-export for use in other hooks +export type { MessageListPaginatedResponse }; + +export const infiniteTaskMessagesKeys = { + all: ['infiniteTaskMessages'] as const, + byTaskId: (taskId: string) => + [...infiniteTaskMessagesKeys.all, taskId] as const, +}; + +export type InfiniteTaskMessagesData = { + messages: TaskMessage[]; + hasMore: boolean; +}; + +/** + * Fetches task messages with infinite scroll pagination. + * + * Uses cursor-based pagination to efficiently load older messages + * as the user scrolls up. Messages are returned in chronological order + * (oldest first) for display. + * + * @param agentexClient - AgentexSDK - The SDK client used to fetch messages + * @param taskId - string - The unique ID of the task whose messages to retrieve + * @param limit - number - Number of messages to fetch per page (default: 50) + * @returns UseInfiniteQueryResult with messages and pagination controls + * + * @example + * const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = + * useInfiniteTaskMessages({ agentexClient, taskId }); + * + * // Load more older messages when user scrolls to top + * if (hasNextPage && !isFetchingNextPage) { + * fetchNextPage(); + * } + */ +export function useInfiniteTaskMessages({ + agentexClient, + taskId, + limit = 50, +}: { + agentexClient: AgentexSDK; + taskId: string; + limit?: number; +}) { + return useInfiniteQuery({ + queryKey: infiniteTaskMessagesKeys.byTaskId(taskId), + queryFn: async ({ pageParam }): Promise => { + return agentexClient.messages.listPaginated({ + task_id: taskId, + limit, + direction: 'older', + ...(pageParam && { cursor: pageParam }), + }); + }, + getNextPageParam: lastPage => { + // Return next_cursor if there are more messages + return lastPage.has_more ? lastPage.next_cursor : undefined; + }, + initialPageParam: undefined as string | undefined, + enabled: !!taskId, + // Transform pages into a flat, chronologically ordered array + select: data => { + // Flatten all pages and reverse to get chronological order + const allMessages = data.pages.flatMap(page => page.data); + // Messages come newest first, so reverse for chronological order + const chronologicalMessages = allMessages.slice().reverse(); + + return { + pages: data.pages, + pageParams: data.pageParams, + messages: chronologicalMessages, + hasMore: data.pages[data.pages.length - 1]?.has_more ?? false, + }; + }, + }); +} diff --git a/agentex-ui/hooks/use-task-messages.ts b/agentex-ui/hooks/use-task-messages.ts index 61b3fb2..66c546a 100644 --- a/agentex-ui/hooks/use-task-messages.ts +++ b/agentex-ui/hooks/use-task-messages.ts @@ -1,4 +1,9 @@ -import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { + InfiniteData, + useMutation, + useQuery, + useQueryClient, +} from '@tanstack/react-query'; import { agentRPCNonStreaming, agentRPCWithStreaming, @@ -8,6 +13,10 @@ import { v4 } from 'uuid'; import { toast } from '@/components/ui/toast'; import { agentsKeys } from '@/hooks/use-agents'; +import { + infiniteTaskMessagesKeys, + MessageListPaginatedResponse, +} from '@/hooks/use-infinite-task-messages'; import type AgentexSDK from 'agentex'; import type { IDeltaAccumulator } from 'agentex/lib'; @@ -49,14 +58,15 @@ export function useTaskMessages({ return { messages: [], deltaAccumulator: null, rpcStatus: 'idle' }; } - const messages = await agentexClient.messages.list({ + // Uses default limit (50) - for full history use useInfiniteTaskMessages + const response = await agentexClient.messages.listPaginated({ task_id: taskId, }); // API returns messages in descending order (newest first), // reverse to chronological order (oldest first) for display return { - messages: messages.slice().reverse(), + messages: response.data.slice().reverse(), deltaAccumulator: null, rpcStatus: 'idle', }; @@ -82,8 +92,6 @@ export function useSendMessage({ return useMutation({ mutationFn: async ({ taskId, agentName, content }: SendMessageParams) => { - const queryKey = taskMessagesKeys.byTaskId(taskId); - const agents = queryClient.getQueryData(agentsKeys.all) || []; const agent = agents.find(a => a.name === agentName); @@ -94,12 +102,48 @@ export function useSendMessage({ switch (agent.acp_type) { case 'async': case 'agentic': { - queryClient.setQueryData(queryKey, data => ({ - messages: data?.messages || [], - deltaAccumulator: data?.deltaAccumulator || null, - rpcStatus: 'pending', + const infiniteQueryKey = infiniteTaskMessagesKeys.byTaskId(taskId); + + // Cancel any in-flight queries to prevent race conditions + await queryClient.cancelQueries({ queryKey: infiniteQueryKey }); + + // Add optimistic update to show user's message immediately + const tempUserMessage: TaskMessage = { + id: v4(), + content, + task_id: taskId, + created_at: new Date().toISOString(), + streaming_status: 'DONE', + updated_at: new Date().toISOString(), + }; + + // Get current messages and add the user's message optimistically + const currentInfiniteData = + queryClient.getQueryData< + InfiniteData + >(infiniteQueryKey); + + const currentMessages = currentInfiniteData + ? currentInfiniteData.pages.flatMap(page => page.data).reverse() + : []; + const updatedMessages = [...currentMessages, tempUserMessage]; + const newestFirst = [...updatedMessages].reverse(); + + queryClient.setQueryData< + InfiniteData + >(infiniteQueryKey, oldData => ({ + pages: [ + { + data: newestFirst, + next_cursor: oldData?.pages[0]?.next_cursor ?? null, + has_more: oldData?.pages[0]?.has_more ?? false, + }, + ...(oldData?.pages.slice(1) ?? []), + ], + pageParams: oldData?.pageParams ?? [undefined], })); + // Send the event to the backend const response = await agentRPCNonStreaming( agentexClient, { agentName }, @@ -108,37 +152,41 @@ export function useSendMessage({ ); if (response.error != null) { - queryClient.setQueryData(queryKey, data => ({ - messages: data?.messages || [], - deltaAccumulator: data?.deltaAccumulator || null, - rpcStatus: 'error', - })); throw new Error(response.error.message); } - queryClient.setQueryData(queryKey, data => ({ - messages: data?.messages || [], - deltaAccumulator: data?.deltaAccumulator || null, - rpcStatus: 'pending', - })); - - return ( - queryClient.getQueryData(queryKey) || { - messages: [], - deltaAccumulator: null, - rpcStatus: 'pending', - } - ); - } + // Invalidate and refetch after a short delay to ensure we get the latest state + // This is a fallback in case the subscription doesn't update correctly + setTimeout(() => { + queryClient.invalidateQueries({ + queryKey: infiniteTaskMessagesKeys.byTaskId(taskId), + }); + }, 500); - case 'sync': { - const currentData = queryClient.getQueryData( - queryKey - ) || { - messages: [], + return { + messages: updatedMessages, deltaAccumulator: null, rpcStatus: 'pending', }; + } + + case 'sync': { + const infiniteQueryKey = infiniteTaskMessagesKeys.byTaskId(taskId); + + // Cancel any in-flight queries to prevent race conditions + await queryClient.cancelQueries({ queryKey: infiniteQueryKey }); + + // Get current messages from infinite query cache + const currentInfiniteData = + queryClient.getQueryData< + InfiniteData + >(infiniteQueryKey); + + // Extract current messages (flattened and in chronological order) + let latestMessages: TaskMessage[] = currentInfiniteData + ? currentInfiniteData.pages.flatMap(page => page.data).reverse() + : []; + let latestDeltaAccumulator: IDeltaAccumulator | null = null; const tempUserMessage: TaskMessage = { id: v4(), @@ -149,14 +197,28 @@ export function useSendMessage({ updated_at: new Date().toISOString(), }; - let latestMessages = [...currentData.messages, tempUserMessage]; - let latestDeltaAccumulator = currentData.deltaAccumulator; + latestMessages = [...latestMessages, tempUserMessage]; + + // Update the infinite query with optimistic update + const updateInfiniteCache = (messages: TaskMessage[]) => { + // Convert to newest-first for API format + const newestFirst = [...messages].reverse(); + queryClient.setQueryData< + InfiniteData + >(infiniteQueryKey, oldData => ({ + pages: [ + { + data: newestFirst, + next_cursor: oldData?.pages[0]?.next_cursor ?? null, + has_more: oldData?.pages[0]?.has_more ?? false, + }, + ...(oldData?.pages.slice(1) ?? []), + ], + pageParams: oldData?.pageParams ?? [undefined], + })); + }; - queryClient.setQueryData(queryKey, { - messages: latestMessages, - deltaAccumulator: latestDeltaAccumulator, - rpcStatus: 'pending', - }); + updateInfiniteCache(latestMessages); const controller = new AbortController(); @@ -168,21 +230,9 @@ export function useSendMessage({ { signal: controller.signal } )) { if (response.error != null) { - queryClient.setQueryData(queryKey, data => ({ - messages: data?.messages || [], - deltaAccumulator: data?.deltaAccumulator || null, - rpcStatus: 'error', - })); throw new Error(response.error.message); } - const cacheData = - queryClient.getQueryData(queryKey); - if (cacheData) { - latestMessages = cacheData.messages; - latestDeltaAccumulator = cacheData.deltaAccumulator; - } - const result = aggregateMessageEvents( latestMessages, latestDeltaAccumulator, @@ -192,30 +242,36 @@ export function useSendMessage({ latestMessages = result.messages; latestDeltaAccumulator = result.deltaAccumulator; - queryClient.setQueryData(queryKey, { - messages: latestMessages, - deltaAccumulator: latestDeltaAccumulator, - rpcStatus: 'pending', - }); + updateInfiniteCache(latestMessages); if (response.result.type === 'done') { queryClient.invalidateQueries({ queryKey: ['spans', taskId] }); } } - const finalMessages = await agentexClient.messages.list({ + // Fetch final state from server to ensure consistency + const response = await agentexClient.messages.listPaginated({ task_id: taskId, }); - // API returns messages in descending order (newest first), - // reverse to chronological order (oldest first) for display - const chronologicalMessages = finalMessages.slice().reverse(); - - queryClient.setQueryData(queryKey, { - messages: chronologicalMessages, - deltaAccumulator: null, - rpcStatus: 'success', - }); + // API returns messages in descending order (newest first) + const newestFirstMessages = response.data; + const chronologicalMessages = newestFirstMessages.slice().reverse(); + + // Update the first page with server data + queryClient.setQueryData< + InfiniteData + >(infiniteQueryKey, oldData => ({ + pages: [ + { + data: newestFirstMessages, + next_cursor: response.next_cursor ?? null, + has_more: response.has_more ?? false, + }, + ...(oldData?.pages.slice(1) ?? []), + ], + pageParams: oldData?.pageParams ?? [undefined], + })); return { messages: chronologicalMessages, diff --git a/agentex-ui/hooks/use-task-subscription.ts b/agentex-ui/hooks/use-task-subscription.ts index f43c09f..6d2b102 100644 --- a/agentex-ui/hooks/use-task-subscription.ts +++ b/agentex-ui/hooks/use-task-subscription.ts @@ -1,16 +1,21 @@ import { useEffect } from 'react'; import { InfiniteData, useQueryClient } from '@tanstack/react-query'; -// import { subscribeTaskState } from 'agentex/lib'; +import { subscribeTaskState } from 'agentex/lib'; -import { subscribeTaskState } from '@/hooks/custom-subscribe-task-state'; import { updateTaskInInfiniteQuery } from '@/hooks/use-create-task'; -import { taskMessagesKeys } from '@/hooks/use-task-messages'; -import type { TaskMessagesData } from '@/hooks/use-task-messages'; +import { + infiniteTaskMessagesKeys, + MessageListPaginatedResponse, +} from '@/hooks/use-infinite-task-messages'; import { tasksKeys } from '@/hooks/use-tasks'; import type AgentexSDK from 'agentex'; -import type { TaskListResponse, TaskRetrieveResponse } from 'agentex/resources'; +import type { + TaskListResponse, + TaskMessage, + TaskRetrieveResponse, +} from 'agentex/resources'; /** * Subscribes to real-time updates for a task's state, messages, and streaming status. @@ -48,28 +53,55 @@ export function useTaskSubscription({ { taskID: taskId }, { onMessagesChange(messages) { - queryClient.setQueryData( - taskMessagesKeys.byTaskId(taskId), - { - messages: [...messages], - deltaAccumulator: null, - rpcStatus: 'pending', + // Guard against undefined messages from SDK + if (!messages || !Array.isArray(messages)) { + return; + } + + // Update the infinite query cache with real-time messages + // Messages from SDK come in chronological order (oldest first) + // We need to convert to API format (newest first) for the first page + const newestFirstMessages = [...messages].reverse(); + + queryClient.setQueryData< + InfiniteData + >(infiniteTaskMessagesKeys.byTaskId(taskId), oldData => { + if (!oldData) { + // Don't initialize cache from subscription - the SDK doesn't pass + // pagination metadata (has_more, next_cursor), so we'd incorrectly + // set has_more: false even when more messages exist. + // Let useInfiniteTaskMessages handle initial fetch with proper metadata. + return undefined; } - ); + + // Get IDs of all messages from older pages (not the first page) + const olderPageMessageIds = new Set( + oldData.pages.slice(1).flatMap(page => page.data.map(m => m.id)) + ); + + // Filter out messages that are already in older pages + // to avoid duplicates when combining real-time with paginated data + const newFirstPageMessages = newestFirstMessages.filter( + m => !olderPageMessageIds.has(m.id) + ); + + return { + ...oldData, + pages: [ + { + ...oldData.pages[0], + data: newFirstPageMessages, + } as MessageListPaginatedResponse, + ...oldData.pages.slice(1), + ], + }; + }); const hasStreamingMessages = messages.some( - msg => msg.streaming_status === 'IN_PROGRESS' + (msg: TaskMessage) => msg.streaming_status === 'IN_PROGRESS' ); if (!hasStreamingMessages && messages.length > 0) { - queryClient.setQueryData( - taskMessagesKeys.byTaskId(taskId), - data => ({ - messages: data?.messages || [], - deltaAccumulator: data?.deltaAccumulator || null, - rpcStatus: 'success', - }) - ); queryClient.invalidateQueries({ queryKey: ['spans', taskId] }); } }, @@ -90,14 +122,9 @@ export function useTaskSubscription({ }, onStreamStatusChange() {}, onError() { - queryClient.setQueryData( - taskMessagesKeys.byTaskId(taskId), - data => ({ - messages: data?.messages || [], - deltaAccumulator: data?.deltaAccumulator || null, - rpcStatus: 'error', - }) - ); + // Error handling - we don't need to update query cache on error + // The UI will show the last known good state + console.error('Task subscription error for task:', taskId); }, }, { signal: abortController.signal } diff --git a/agentex-ui/package-lock.json b/agentex-ui/package-lock.json index 42b2a93..ee3c7b9 100644 --- a/agentex-ui/package-lock.json +++ b/agentex-ui/package-lock.json @@ -22,7 +22,7 @@ "@tailwindcss/postcss": "^4", "@tanstack/react-query": "^5.90.3", "@uiw/react-codemirror": "^4.25.2", - "agentex": "^0.1.0-alpha.9", + "agentex": "^0.1.0-alpha.11", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0", @@ -4841,9 +4841,9 @@ } }, "node_modules/agentex": { - "version": "0.1.0-alpha.9", - "resolved": "https://registry.npmjs.org/agentex/-/agentex-0.1.0-alpha.9.tgz", - "integrity": "sha512-iFJb4aaYdbk2L29Ers4VJydvxRxw3zM8pBp5gsUewYSMVTko0lwKTJvziGdFhbJj3ntdmV+26GCXHf6dnnGROg==" + "version": "0.1.0-alpha.11", + "resolved": "https://registry.npmjs.org/agentex/-/agentex-0.1.0-alpha.11.tgz", + "integrity": "sha512-QYPRxtCG+45VmClxeJhceBBUMhLMInLHUdLpJtoAiNspHlgedY9PYyg0R0ALaLg96AS4h/3zfTUX83n8zmtx9A==" }, "node_modules/ajv": { "version": "6.12.6", diff --git a/agentex-ui/package.json b/agentex-ui/package.json index 9062e77..f1618b7 100644 --- a/agentex-ui/package.json +++ b/agentex-ui/package.json @@ -33,7 +33,7 @@ "@tailwindcss/postcss": "^4", "@tanstack/react-query": "^5.90.3", "@uiw/react-codemirror": "^4.25.2", - "agentex": "^0.1.0-alpha.9", + "agentex": "^0.1.0-alpha.11", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "date-fns": "^4.1.0",