+
)}
@@ -491,6 +506,9 @@ function convertToSteps(messages: Message[]): CoTStep[] {
steps.push(step);
}
for (const tool_call of message.tool_calls ?? []) {
+ if (tool_call.name === "task") {
+ continue;
+ }
const step: CoTToolCallStep = {
id: tool_call.id,
messageId: message.id,
diff --git a/frontend/src/components/workspace/messages/message-list.tsx b/frontend/src/components/workspace/messages/message-list.tsx
index e018335..e798a7c 100644
--- a/frontend/src/components/workspace/messages/message-list.tsx
+++ b/frontend/src/components/workspace/messages/message-list.tsx
@@ -5,14 +5,19 @@ import {
ConversationContent,
} from "@/components/ai-elements/conversation";
import { MessageResponse } from "@/components/ai-elements/message";
+import { useI18n } from "@/core/i18n/hooks";
import {
extractContentFromMessage,
extractPresentFilesFromMessage,
+ extractTextFromMessage,
groupMessages,
hasContent,
hasPresentFiles,
+ hasReasoning,
} from "@/core/messages/utils";
import { useRehypeSplitWordsIntoSpans } from "@/core/rehype";
+import type { Subtask } from "@/core/tasks";
+import { useUpdateSubtask } from "@/core/tasks/context";
import type { AgentThreadState } from "@/core/threads";
import { cn } from "@/lib/utils";
@@ -22,6 +27,7 @@ import { StreamingIndicator } from "../streaming-indicator";
import { MessageGroup } from "./message-group";
import { MessageListItem } from "./message-list-item";
import { MessageListSkeleton } from "./skeleton";
+import { SubtaskCard } from "./subtask-card";
export function MessageList({
className,
@@ -34,7 +40,9 @@ export function MessageList({
thread: UseStream
;
paddingBottom?: number;
}) {
+ const { t } = useI18n();
const rehypePlugins = useRehypeSplitWordsIntoSpans(thread.isLoading);
+ const updateSubtask = useUpdateSubtask();
if (thread.isThreadLoading) {
return ;
}
@@ -42,7 +50,7 @@ export function MessageList({
-
+
{groupMessages(thread.messages, (group) => {
if (group.type === "human" || group.type === "assistant") {
return (
@@ -52,8 +60,7 @@ export function MessageList({
isLoading={thread.isLoading}
/>
);
- }
- if (group.type === "assistant:clarification") {
+ } else if (group.type === "assistant:clarification") {
const message = group.messages[0];
if (message && hasContent(message)) {
return (
@@ -63,8 +70,7 @@ export function MessageList({
);
}
return null;
- }
- if (group.type === "assistant:present-files") {
+ } else if (group.type === "assistant:present-files") {
const files: string[] = [];
for (const message of group.messages) {
if (hasPresentFiles(message)) {
@@ -85,6 +91,92 @@ export function MessageList({
);
+ } else if (group.type === "assistant:subagent") {
+ const tasks: Subtask[] = [];
+ for (const message of group.messages) {
+ if (message.type === "ai") {
+ for (const toolCall of message.tool_calls ?? []) {
+ if (toolCall.name === "task") {
+ updateSubtask({
+ id: toolCall.id!,
+ subagent_type: toolCall.args.subagent_type,
+ description: toolCall.args.description,
+ prompt: toolCall.args.prompt,
+ status: "in_progress",
+ });
+ }
+ }
+ } else if (message.type === "tool") {
+ const taskId = message.tool_call_id;
+ if (taskId) {
+ const result = extractTextFromMessage(message);
+ if (result.startsWith("Task Succeeded. Result:")) {
+ updateSubtask({
+ id: taskId,
+ status: "completed",
+ result: result
+ .split("Task Succeeded. Result:")[1]
+ ?.trim(),
+ });
+ } else if (result.startsWith("Task failed.")) {
+ updateSubtask({
+ id: taskId,
+ status: "failed",
+ error: result.split("Task failed.")[1]?.trim(),
+ });
+ } else {
+ updateSubtask({
+ id: taskId,
+ status: "in_progress",
+ });
+ }
+ }
+ }
+ }
+ const results: React.ReactNode[] = [];
+ for (const message of group.messages.filter(
+ (message) => message.type === "ai",
+ )) {
+ if (hasReasoning(message)) {
+ results.push(
+