diff --git a/frontend/src/components/features/chat/chat-interface.tsx b/frontend/src/components/features/chat/chat-interface.tsx
index 040cd8f522..91f07fc611 100644
--- a/frontend/src/components/features/chat/chat-interface.tsx
+++ b/frontend/src/components/features/chat/chat-interface.tsx
@@ -68,6 +68,7 @@ export function ChatInterface() {
const conversationWebSocket = useConversationWebSocket();
const { send } = useSendMessage();
const storeEvents = useEventStore((state) => state.events);
+ const uiEvents = useEventStore((state) => state.uiEvents);
const { setOptimisticUserMessage, getOptimisticUserMessage } =
useOptimisticUserMessageStore();
const { t } = useTranslation();
@@ -121,11 +122,13 @@ export function ChatInterface() {
.filter(isActionOrObservation)
.filter(shouldRenderEvent);
- // Filter V1 events
- const v1Events = storeEvents.filter(isV1Event).filter(shouldRenderV1Event);
+ // Filter V1 events - use uiEvents for rendering (actions replaced by observations)
+ const v1UiEvents = uiEvents.filter(isV1Event).filter(shouldRenderV1Event);
+ // Keep full v1 events for lookups (includes both actions and observations)
+ const v1FullEvents = storeEvents.filter(isV1Event);
// Combined events count for tracking
- const totalEvents = v0Events.length || v1Events.length;
+ const totalEvents = v0Events.length || v1UiEvents.length;
// Check if there are any substantive agent actions (not just system messages)
const hasSubstantiveAgentActions = React.useMemo(
@@ -223,7 +226,7 @@ export function ChatInterface() {
};
const v0UserEventsExist = hasUserEvent(v0Events);
- const v1UserEventsExist = hasV1UserEvent(v1Events);
+ const v1UserEventsExist = hasV1UserEvent(v1FullEvents);
const userEventsExist = v0UserEventsExist || v1UserEventsExist;
return (
@@ -267,7 +270,7 @@ export function ChatInterface() {
)}
{!conversationWebSocket?.isLoadingHistory && v1UserEventsExist && (
-
+
)}
diff --git a/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts b/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts
index 8acef2da03..a5fdc62252 100644
--- a/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts
+++ b/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts
@@ -7,17 +7,6 @@ import {
isConversationStateUpdateEvent,
} from "#/types/v1/type-guards";
-// V1 events that should not be rendered
-const NO_RENDER_ACTION_TYPES = [
- "ThinkAction",
- // Add more action types that should not be rendered
-];
-
-const NO_RENDER_OBSERVATION_TYPES = [
- "ThinkObservation",
- // Add more observation types that should not be rendered
-];
-
export const shouldRenderEvent = (event: OpenHandsEvent) => {
// Explicitly exclude system events that should not be rendered in chat
if (isConversationStateUpdateEvent(event)) {
@@ -34,18 +23,12 @@ export const shouldRenderEvent = (event: OpenHandsEvent) => {
return false;
}
- return !NO_RENDER_ACTION_TYPES.includes(actionType);
+ return true;
}
- // Render observation events (with filtering)
+ // Render observation events
if (isObservationEvent(event)) {
- // For V1, observation is an object with kind property
- const observationType = event.observation.kind;
-
- // Note: ObservationEvent source is always "environment", not "user"
- // So no need to check for user source here
-
- return !NO_RENDER_OBSERVATION_TYPES.includes(observationType);
+ return true;
}
// Render message events (user and assistant messages)
diff --git a/frontend/src/components/v1/chat/event-message-components/index.ts b/frontend/src/components/v1/chat/event-message-components/index.ts
index 1f705a1f7a..3672255101 100644
--- a/frontend/src/components/v1/chat/event-message-components/index.ts
+++ b/frontend/src/components/v1/chat/event-message-components/index.ts
@@ -3,3 +3,4 @@ export { ObservationPairEventMessage } from "./observation-pair-event-message";
export { ErrorEventMessage } from "./error-event-message";
export { FinishEventMessage } from "./finish-event-message";
export { GenericEventMessageWrapper } from "./generic-event-message-wrapper";
+export { ThoughtEventMessage } from "./thought-event-message";
diff --git a/frontend/src/components/v1/chat/event-message-components/thought-event-message.tsx b/frontend/src/components/v1/chat/event-message-components/thought-event-message.tsx
new file mode 100644
index 0000000000..9d6dfdcea1
--- /dev/null
+++ b/frontend/src/components/v1/chat/event-message-components/thought-event-message.tsx
@@ -0,0 +1,32 @@
+import React from "react";
+import { ActionEvent } from "#/types/v1/core";
+import { ChatMessage } from "../../../features/chat/chat-message";
+
+interface ThoughtEventMessageProps {
+ event: ActionEvent;
+ actions?: Array<{
+ icon: React.ReactNode;
+ onClick: () => void;
+ tooltip?: string;
+ }>;
+}
+
+export function ThoughtEventMessage({
+ event,
+ actions,
+}: ThoughtEventMessageProps) {
+ // Extract thought content from the action event
+ const thoughtContent = event.thought
+ .filter((t) => t.type === "text")
+ .map((t) => t.text)
+ .join("\n");
+
+ // If there's no thought content, don't render anything
+ if (!thoughtContent) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/frontend/src/components/v1/chat/event-message.tsx b/frontend/src/components/v1/chat/event-message.tsx
index cc5463f080..dbe327b31b 100644
--- a/frontend/src/components/v1/chat/event-message.tsx
+++ b/frontend/src/components/v1/chat/event-message.tsx
@@ -14,13 +14,13 @@ import {
ErrorEventMessage,
UserAssistantEventMessage,
FinishEventMessage,
- ObservationPairEventMessage,
GenericEventMessageWrapper,
+ ThoughtEventMessage,
} from "./event-message-components";
interface EventMessageProps {
event: OpenHandsEvent;
- hasObservationPair: boolean;
+ messages: OpenHandsEvent[];
isLastMessage: boolean;
microagentStatus?: MicroagentStatus | null;
microagentConversationId?: string;
@@ -36,7 +36,7 @@ interface EventMessageProps {
/* eslint-disable react/jsx-props-no-spreading */
export function EventMessage({
event,
- hasObservationPair,
+ messages,
isLastMessage,
microagentStatus,
microagentConversationId,
@@ -69,19 +69,6 @@ export function EventMessage({
return ;
}
- // Observation pairs with actions
- if (hasObservationPair && isActionEvent(event)) {
- return (
-
- );
- }
-
// Finish actions
if (isActionEvent(event) && event.action.kind === "FinishAction") {
return (
@@ -92,6 +79,39 @@ export function EventMessage({
);
}
+ // Action events - render thought + action (will be replaced by thought + observation)
+ if (isActionEvent(event)) {
+ return (
+ <>
+
+
+ >
+ );
+ }
+
+ // Observation events - find the corresponding action and render thought + observation
+ if (isObservationEvent(event)) {
+ // Find the action that this observation is responding to
+ const correspondingAction = messages.find(
+ (msg) => isActionEvent(msg) && msg.id === event.action_id,
+ );
+
+ return (
+ <>
+ {correspondingAction && isActionEvent(correspondingAction) && (
+
+ )}
+
+ >
+ );
+ }
+
// Message events (user and assistant messages)
if (!isActionEvent(event) && !isObservationEvent(event)) {
// This is a MessageEvent
@@ -104,7 +124,7 @@ export function EventMessage({
);
}
- // Generic fallback for all other events (including observation events)
+ // Generic fallback for all other events
return (
);
diff --git a/frontend/src/components/v1/chat/messages.tsx b/frontend/src/components/v1/chat/messages.tsx
index d6cc018090..4c4b733e76 100644
--- a/frontend/src/components/v1/chat/messages.tsx
+++ b/frontend/src/components/v1/chat/messages.tsx
@@ -1,6 +1,5 @@
import React from "react";
import { OpenHandsEvent } from "#/types/v1/core";
-import { isActionEvent, isObservationEvent } from "#/types/v1/type-guards";
import { EventMessage } from "./event-message";
import { ChatMessage } from "../../features/chat/chat-message";
import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message-store";
@@ -9,29 +8,16 @@ import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message-
// import MemoryIcon from "#/icons/memory_icon.svg?react";
interface MessagesProps {
- messages: OpenHandsEvent[];
+ messages: OpenHandsEvent[]; // UI events (actions replaced by observations)
+ allEvents: OpenHandsEvent[]; // Full event history (for action lookup)
}
export const Messages: React.FC = React.memo(
- ({ messages }) => {
+ ({ messages, allEvents }) => {
const { getOptimisticUserMessage } = useOptimisticUserMessageStore();
const optimisticUserMessage = getOptimisticUserMessage();
- const actionHasObservationPair = React.useCallback(
- (event: OpenHandsEvent): boolean => {
- if (isActionEvent(event)) {
- // Check if there's a corresponding observation event
- return !!messages.some(
- (msg) => isObservationEvent(msg) && msg.action_id === event.id,
- );
- }
-
- return false;
- },
- [messages],
- );
-
// TODO: Implement microagent functionality for V1 if needed
// For now, we'll skip microagent features
@@ -41,7 +27,7 @@ export const Messages: React.FC = React.memo(