Skip to content

Commit 7dcdf60

Browse files
authored
Display subtask messages (#215)
1 parent 5a17732 commit 7dcdf60

File tree

2 files changed

+201
-1
lines changed

2 files changed

+201
-1
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { describe, it, expect } from 'vitest';
2+
3+
describe('Messages component integration', () => {
4+
it('should parse newTask tool data correctly', () => {
5+
const toolText = JSON.stringify({
6+
tool: 'newTask',
7+
mode: 'Code',
8+
content: 'Implement a new authentication system',
9+
});
10+
11+
let toolData = null;
12+
let isNewTask = false;
13+
14+
try {
15+
toolData = JSON.parse(toolText);
16+
isNewTask = toolData?.tool === 'newTask';
17+
} catch {
18+
// Invalid JSON, treat as regular tool message
19+
}
20+
21+
expect(isNewTask).toBe(true);
22+
expect(toolData?.mode).toBe('Code');
23+
expect(toolData?.content).toBe('Implement a new authentication system');
24+
});
25+
26+
it('should handle invalid JSON gracefully', () => {
27+
const toolText = 'invalid json';
28+
29+
let toolData = null;
30+
let isNewTask = false;
31+
32+
try {
33+
toolData = JSON.parse(toolText);
34+
isNewTask = toolData?.tool === 'newTask';
35+
} catch {
36+
// Invalid JSON, treat as regular tool message
37+
}
38+
39+
expect(isNewTask).toBe(false);
40+
expect(toolData).toBe(null);
41+
});
42+
43+
it('should identify non-newTask tools correctly', () => {
44+
const toolText = JSON.stringify({
45+
tool: 'readFile',
46+
path: 'src/app.ts',
47+
});
48+
49+
let toolData = null;
50+
let isNewTask = false;
51+
52+
try {
53+
toolData = JSON.parse(toolText);
54+
isNewTask = toolData?.tool === 'newTask';
55+
} catch {
56+
// Invalid JSON, treat as regular tool message
57+
}
58+
59+
expect(isNewTask).toBe(false);
60+
expect(toolData?.tool).toBe('readFile');
61+
});
62+
63+
it('should handle subtask_result messages correctly', () => {
64+
// Test the isVisible function logic for subtask_result messages
65+
const isVisible = (message: {
66+
type: string;
67+
ask?: string;
68+
say?: string;
69+
text?: string;
70+
}) => {
71+
if (
72+
message.type === 'ask' &&
73+
(message.ask === 'followup' ||
74+
message.ask === 'command' ||
75+
message.ask === 'tool')
76+
) {
77+
return true;
78+
}
79+
80+
return (
81+
(message.ask === 'text' ||
82+
message.say === 'text' ||
83+
message.say === 'completion_result' ||
84+
message.say === 'subtask_result' ||
85+
message.say === 'user_feedback') &&
86+
typeof message.text === 'string' &&
87+
message.text.length > 0
88+
);
89+
};
90+
91+
const subtaskResultMessage = {
92+
type: 'say',
93+
say: 'subtask_result',
94+
text: 'Subtask completed successfully with the following results...',
95+
};
96+
97+
expect(isVisible(subtaskResultMessage)).toBe(true);
98+
expect(subtaskResultMessage.say).toBe('subtask_result');
99+
expect(subtaskResultMessage.text).toBe(
100+
'Subtask completed successfully with the following results...',
101+
);
102+
});
103+
});

apps/web/src/app/(authenticated)/usage/Messages.tsx

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,103 @@ export const Messages = ({
221221

222222
const messageId = `message-${message.id}`;
223223

224-
// For tool messages, render only the tool usage badge in the space between bubbles
224+
// Parse tool data to check for special cases
225+
let toolData = null;
226+
let isNewTask = false;
227+
if (isTool && message.text) {
228+
try {
229+
toolData = JSON.parse(message.text);
230+
isNewTask = toolData?.tool === 'newTask';
231+
} catch {
232+
// Invalid JSON, treat as regular tool message
233+
}
234+
}
235+
236+
// For newTask tool messages, render the full content
237+
if (isNewTask && toolData) {
238+
return (
239+
<div key={message.id} className="py-2">
240+
<div className="bg-secondary/30 border border-border rounded-lg overflow-hidden">
241+
<div className="bg-secondary/50 px-4 py-2 border-b border-border">
242+
<div className="flex items-center gap-2 text-sm font-medium text-foreground/80">
243+
<svg
244+
className="w-4 h-4"
245+
fill="none"
246+
stroke="currentColor"
247+
viewBox="0 0 24 24"
248+
>
249+
<path
250+
strokeLinecap="round"
251+
strokeLinejoin="round"
252+
strokeWidth={2}
253+
d="M17 8l4 4m0 0l-4 4m4-4H3"
254+
/>
255+
</svg>
256+
New task created in {toolData.mode} mode
257+
</div>
258+
</div>
259+
<div className="p-4">
260+
<div className="text-sm leading-relaxed markdown-prose text-foreground/90">
261+
<ReactMarkdown
262+
remarkPlugins={[remarkBreaks]}
263+
rehypePlugins={[rehypeSanitize]}
264+
components={{
265+
a: PlainTextLink,
266+
code: CodeBlock,
267+
}}
268+
>
269+
{toolData.content}
270+
</ReactMarkdown>
271+
</div>
272+
</div>
273+
</div>
274+
</div>
275+
);
276+
}
277+
278+
// For subtask_result messages, render with special styling
279+
if (message.say === 'subtask_result') {
280+
return (
281+
<div key={message.id} className="py-2">
282+
<div className="bg-secondary/30 border border-border rounded-lg overflow-hidden">
283+
<div className="bg-secondary/50 px-4 py-2 border-b border-border">
284+
<div className="flex items-center gap-2 text-sm font-medium text-foreground/80">
285+
<svg
286+
className="w-4 h-4"
287+
fill="none"
288+
stroke="currentColor"
289+
viewBox="0 0 24 24"
290+
>
291+
<path
292+
strokeLinecap="round"
293+
strokeLinejoin="round"
294+
strokeWidth={2}
295+
d="M7 16l-4-4m0 0l4-4m-4 4h18"
296+
/>
297+
</svg>
298+
Subtask results
299+
</div>
300+
</div>
301+
<div className="p-4">
302+
<div className="text-sm leading-relaxed markdown-prose text-foreground/90">
303+
<ReactMarkdown
304+
remarkPlugins={[remarkBreaks]}
305+
rehypePlugins={[rehypeSanitize]}
306+
components={{
307+
a: PlainTextLink,
308+
code: CodeBlock,
309+
}}
310+
>
311+
{message.text}
312+
</ReactMarkdown>
313+
</div>
314+
</div>
315+
</div>
316+
</div>
317+
);
318+
}
319+
320+
// For other tool messages, render only the tool usage badge in the space between bubbles
225321
if (isTool) {
226322
return (
227323
<div key={message.id} className="py-2 pl-4">
@@ -424,6 +520,7 @@ const isVisible = (message: Message) => {
424520
(message.ask === 'text' ||
425521
message.say === 'text' ||
426522
message.say === 'completion_result' ||
523+
message.say === 'subtask_result' ||
427524
message.say === 'user_feedback') &&
428525
typeof message.text === 'string' &&
429526
message.text.length > 0

0 commit comments

Comments
 (0)