From 8ea94a119fd6c3bde3e392fc70d9775348da6042 Mon Sep 17 00:00:00 2001 From: Alexandre Reyes Martins Date: Wed, 26 Aug 2026 11:15:49 -0300 Subject: [PATCH] fix: restore baseline validation (#3142) --- packages/ui/src/apps/runtimeEndpointReset.ts | 2 +- .../ui/src/components/chat/ChatMessage.tsx | 20 -------- .../components/chat/message/MessageBody.tsx | 46 ------------------- packages/ui/src/components/layout/Header.tsx | 2 - .../sidebar/list/sessionCollection.test.ts | 2 +- ...llapsedActivityIndicator.behavior.test.tsx | 2 +- .../web/server/lib/session-goal/runtime.js | 3 +- .../server/lib/session-goal/runtime.test.js | 6 ++- 8 files changed, 9 insertions(+), 74 deletions(-) diff --git a/packages/ui/src/apps/runtimeEndpointReset.ts b/packages/ui/src/apps/runtimeEndpointReset.ts index 22e9705b..cd1f33ce 100644 --- a/packages/ui/src/apps/runtimeEndpointReset.ts +++ b/packages/ui/src/apps/runtimeEndpointReset.ts @@ -15,7 +15,7 @@ import { useFilesViewTabsStore } from '@/stores/useFilesViewTabsStore'; import { useTerminalStore } from '@/stores/useTerminalStore'; import { useSessionUIStore } from '@/sync/session-ui-store'; import { resetStreamingState } from '@/sync/streaming'; -import { useGlobalSessionStatusStore, replaceGlobalSessionStatusById } from '@/sync/global-session-status'; +import { replaceGlobalSessionStatusById } from '@/sync/global-session-status'; import { resetSessionOrdering } from '@/sync/session-ordering'; import { resetSessionActivityTiming } from '@/sync/session-activity-timing'; import { syncDesktopSettings } from '@/lib/persistence'; diff --git a/packages/ui/src/components/chat/ChatMessage.tsx b/packages/ui/src/components/chat/ChatMessage.tsx index a4327167..20a86cde 100644 --- a/packages/ui/src/components/chat/ChatMessage.tsx +++ b/packages/ui/src/components/chat/ChatMessage.tsx @@ -457,13 +457,6 @@ const ChatMessage: React.FC = ({ }, [chatRenderMode, isMessageCompleted, isUser, visibleParts]); - const assistantTextParts = React.useMemo(() => { - if (isUser) { - return []; - } - return visibleParts.filter((part) => part.type === 'text'); - }, [isUser, visibleParts]); - const toolParts = React.useMemo(() => { if (isUser) { return []; @@ -545,19 +538,6 @@ const ChatMessage: React.FC = ({ const shouldHideUserMessage = isUser && displayParts.length === 0; - // Message is considered to have an "open step" if info.finish is not yet present - const hasOpenStep = typeof messageFinish !== 'string'; - - const shouldCoordinateRendering = React.useMemo(() => { - if (isUser) { - return false; - } - if (assistantTextParts.length === 0 || toolParts.length === 0) { - return hasOpenStep; - } - return true; - }, [assistantTextParts.length, toolParts.length, hasOpenStep, isUser]); - const themeVariant = currentTheme?.metadata.variant; const isDarkTheme = React.useMemo(() => { if (themeVariant) { diff --git a/packages/ui/src/components/chat/message/MessageBody.tsx b/packages/ui/src/components/chat/message/MessageBody.tsx index 35a17a4d..c02d4fc3 100644 --- a/packages/ui/src/components/chat/message/MessageBody.tsx +++ b/packages/ui/src/components/chat/message/MessageBody.tsx @@ -1343,16 +1343,6 @@ const AssistantMessageBody = React.memo(({ return resolved ? { id: resolved.id, path: resolved.path } : null; }, [availableWorktreesByProject, canUseProjectPlanActions, currentSessionId, effectiveDirectory, getDirectoryForSession, projects]); - const hasTools = toolParts.length > 0; - - const hasPendingTools = React.useMemo(() => { - return toolParts.some((toolPart) => { - const state = (toolPart as Record).state as Record | undefined ?? {}; - const status = state?.status; - return status === 'pending' || status === 'running' || status === 'started'; - }); - }, [toolParts]); - const isActiveTool = React.useCallback((toolPart: ToolPartType): boolean => { const state = (toolPart as Record).state as Record | undefined ?? {}; const status = state?.status; @@ -1381,42 +1371,6 @@ const AssistantMessageBody = React.memo(({ return isActiveTool(toolPart) || isToolFinalized(toolPart); }, [isActiveTool, isToolFinalized]); - const allToolsFinalized = React.useMemo(() => { - if (toolParts.length === 0) { - return true; - } - if (hasPendingTools) { - return false; - } - return toolParts.every((toolPart) => isToolFinalized(toolPart)); - }, [toolParts, hasPendingTools, isToolFinalized]); - - const reasoningParts = React.useMemo(() => { - return visibleParts.filter((part) => part.type === 'reasoning'); - }, [visibleParts]); - - const reasoningComplete = React.useMemo(() => { - if (reasoningParts.length === 0) { - return true; - } - return reasoningParts.every((part) => { - const time = (part as Record).time as { end?: number } | undefined; - return typeof time?.end === 'number'; - }); - }, [reasoningParts]); - - // Message is considered to have an "open step" if info.finish is not yet present - const hasOpenStep = typeof messageFinish !== 'string'; - - const shouldHoldForReasoning = - reasoningParts.length > 0 && - hasTools && - (hasPendingTools || hasOpenStep || !allToolsFinalized); - - const shouldHoldTools = awaitingMessageCompletion - || (hasTools && (hasPendingTools || hasOpenStep || !allToolsFinalized)); - const shouldHoldReasoning = awaitingMessageCompletion || shouldHoldForReasoning; - const hasCopyableText = Boolean(hasTextContent) && !awaitingMessageCompletion; const handleForkClick = React.useCallback( diff --git a/packages/ui/src/components/layout/Header.tsx b/packages/ui/src/components/layout/Header.tsx index 90573996..154ba5a5 100644 --- a/packages/ui/src/components/layout/Header.tsx +++ b/packages/ui/src/components/layout/Header.tsx @@ -486,8 +486,6 @@ export const Header: React.FC = () => { const pathSegments = activeProject.path.split(/[\\/]/).filter(Boolean); return pathSegments[pathSegments.length - 1] ?? null; }, [activeProject]); - const quotaResults = useQuotaStore((state) => state.results); - const fetchAllQuotas = useQuotaStore((state) => state.fetchAllQuotas); const loadQuotaSettings = useQuotaStore((state) => state.loadSettings); const { isMobile } = useDeviceInfo(); diff --git a/packages/ui/src/components/session/sidebar/list/sessionCollection.test.ts b/packages/ui/src/components/session/sidebar/list/sessionCollection.test.ts index 66c9b12a..38de74e2 100644 --- a/packages/ui/src/components/session/sidebar/list/sessionCollection.test.ts +++ b/packages/ui/src/components/session/sidebar/list/sessionCollection.test.ts @@ -4,7 +4,7 @@ import type { Event } from '@opencode-ai/sdk/v2/client'; import React, { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { deriveRecentSessions } from '../recent/activitySections'; -import { applyGlobalSessionStatusEvent, useGlobalSessionStatusStore , replaceGlobalSessionStatusById} from '@/sync/global-session-status'; +import { applyGlobalSessionStatusEvent, replaceGlobalSessionStatusById } from '@/sync/global-session-status'; import { buildSidebarSessionProjection, getDescendantIds, diff --git a/packages/ui/src/components/session/sidebar/sessions/collapsedActivityIndicator.behavior.test.tsx b/packages/ui/src/components/session/sidebar/sessions/collapsedActivityIndicator.behavior.test.tsx index a63095f7..74a7a518 100644 --- a/packages/ui/src/components/session/sidebar/sessions/collapsedActivityIndicator.behavior.test.tsx +++ b/packages/ui/src/components/session/sidebar/sessions/collapsedActivityIndicator.behavior.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, test } from 'bun:test'; import React, { act } from 'react'; import { createRoot } from 'react-dom/client'; import type { Session } from '@opencode-ai/sdk/v2'; -import { useGlobalSessionStatusStore , replaceGlobalSessionStatusById} from '@/sync/global-session-status'; +import { replaceGlobalSessionStatusById } from '@/sync/global-session-status'; import { useNotificationStore } from '@/sync/notification-store'; import { useCollapsedSessionActivityState } from './collapsedActivityState'; import type { SessionNode } from '../types'; diff --git a/packages/web/server/lib/session-goal/runtime.js b/packages/web/server/lib/session-goal/runtime.js index e6916997..f97af98d 100644 --- a/packages/web/server/lib/session-goal/runtime.js +++ b/packages/web/server/lib/session-goal/runtime.js @@ -249,6 +249,7 @@ export const createSessionGoalRuntime = ({ getOpenCodeAuthHeaders, getSmallModelService, emitGoalNotification, + isEnabled = isSessionGoalEnabled, idleQuietMs = IDLE_QUIET_MS, kickoffQuietMs = KICKOFF_QUIET_MS, maxAutoTurns = MAX_AUTO_TURNS, @@ -444,7 +445,7 @@ export const createSessionGoalRuntime = ({ }; const tick = async (sessionId, directory) => { - if (!isSessionGoalEnabled()) return; + if (!isEnabled()) return; const session = await openCodeFetch(`/session/${encodeURIComponent(sessionId)}`, { directory }) .catch((error) => { diff --git a/packages/web/server/lib/session-goal/runtime.test.js b/packages/web/server/lib/session-goal/runtime.test.js index e091c8c4..583e4e37 100644 --- a/packages/web/server/lib/session-goal/runtime.test.js +++ b/packages/web/server/lib/session-goal/runtime.test.js @@ -35,13 +35,14 @@ const startIdleTick = async (fetchImpl) => { buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`, getOpenCodeAuthHeaders: () => ({}), getSmallModelService, + isEnabled: () => true, idleQuietMs: 10, }); runtime.processPayload({ type: 'session.status', properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY }, }); - await vi.advanceTimersByTimeAsync(10); + await vi.runOnlyPendingTimersAsync(); return { runtime, getSmallModelService }; }; @@ -156,6 +157,7 @@ describe('session goal live activity gate', () => { buildOpenCodeUrl: (pathname) => `http://opencode.test${pathname}`, getOpenCodeAuthHeaders: () => ({}), getSmallModelService: async () => service, + isEnabled: () => true, idleQuietMs: 10, }); @@ -163,7 +165,7 @@ describe('session goal live activity gate', () => { type: 'session.status', properties: { sessionID: SESSION_ID, status: { type: 'idle' }, directory: DIRECTORY }, }); - await vi.advanceTimersByTimeAsync(10); + await vi.runOnlyPendingTimersAsync(); expect(service.generateSmallModelText).toHaveBeenCalledOnce(); const patch = requests.find((request) => request.pathname === `/session/${SESSION_ID}` && request.method === 'PATCH');