Display subtask messages (#215)

This commit is contained in:
Matt Rubens 2025-07-07 00:40:24 -04:00 committed by GitHub
parent 5a17732d7c
commit 7dcdf6039c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 201 additions and 1 deletions

View file

@ -0,0 +1,103 @@
import { describe, it, expect } from 'vitest';
describe('Messages component integration', () => {
it('should parse newTask tool data correctly', () => {
const toolText = JSON.stringify({
tool: 'newTask',
mode: 'Code',
content: 'Implement a new authentication system',
});
let toolData = null;
let isNewTask = false;
try {
toolData = JSON.parse(toolText);
isNewTask = toolData?.tool === 'newTask';
} catch {
// Invalid JSON, treat as regular tool message
}
expect(isNewTask).toBe(true);
expect(toolData?.mode).toBe('Code');
expect(toolData?.content).toBe('Implement a new authentication system');
});
it('should handle invalid JSON gracefully', () => {
const toolText = 'invalid json';
let toolData = null;
let isNewTask = false;
try {
toolData = JSON.parse(toolText);
isNewTask = toolData?.tool === 'newTask';
} catch {
// Invalid JSON, treat as regular tool message
}
expect(isNewTask).toBe(false);
expect(toolData).toBe(null);
});
it('should identify non-newTask tools correctly', () => {
const toolText = JSON.stringify({
tool: 'readFile',
path: 'src/app.ts',
});
let toolData = null;
let isNewTask = false;
try {
toolData = JSON.parse(toolText);
isNewTask = toolData?.tool === 'newTask';
} catch {
// Invalid JSON, treat as regular tool message
}
expect(isNewTask).toBe(false);
expect(toolData?.tool).toBe('readFile');
});
it('should handle subtask_result messages correctly', () => {
// Test the isVisible function logic for subtask_result messages
const isVisible = (message: {
type: string;
ask?: string;
say?: string;
text?: string;
}) => {
if (
message.type === 'ask' &&
(message.ask === 'followup' ||
message.ask === 'command' ||
message.ask === 'tool')
) {
return true;
}
return (
(message.ask === 'text' ||
message.say === 'text' ||
message.say === 'completion_result' ||
message.say === 'subtask_result' ||
message.say === 'user_feedback') &&
typeof message.text === 'string' &&
message.text.length > 0
);
};
const subtaskResultMessage = {
type: 'say',
say: 'subtask_result',
text: 'Subtask completed successfully with the following results...',
};
expect(isVisible(subtaskResultMessage)).toBe(true);
expect(subtaskResultMessage.say).toBe('subtask_result');
expect(subtaskResultMessage.text).toBe(
'Subtask completed successfully with the following results...',
);
});
});

View file

@ -221,7 +221,103 @@ export const Messages = ({
const messageId = `message-${message.id}`;
// For tool messages, render only the tool usage badge in the space between bubbles
// Parse tool data to check for special cases
let toolData = null;
let isNewTask = false;
if (isTool && message.text) {
try {
toolData = JSON.parse(message.text);
isNewTask = toolData?.tool === 'newTask';
} catch {
// Invalid JSON, treat as regular tool message
}
}
// For newTask tool messages, render the full content
if (isNewTask && toolData) {
return (
<div key={message.id} className="py-2">
<div className="bg-secondary/30 border border-border rounded-lg overflow-hidden">
<div className="bg-secondary/50 px-4 py-2 border-b border-border">
<div className="flex items-center gap-2 text-sm font-medium text-foreground/80">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 8l4 4m0 0l-4 4m4-4H3"
/>
</svg>
New task created in {toolData.mode} mode
</div>
</div>
<div className="p-4">
<div className="text-sm leading-relaxed markdown-prose text-foreground/90">
<ReactMarkdown
remarkPlugins={[remarkBreaks]}
rehypePlugins={[rehypeSanitize]}
components={{
a: PlainTextLink,
code: CodeBlock,
}}
>
{toolData.content}
</ReactMarkdown>
</div>
</div>
</div>
</div>
);
}
// For subtask_result messages, render with special styling
if (message.say === 'subtask_result') {
return (
<div key={message.id} className="py-2">
<div className="bg-secondary/30 border border-border rounded-lg overflow-hidden">
<div className="bg-secondary/50 px-4 py-2 border-b border-border">
<div className="flex items-center gap-2 text-sm font-medium text-foreground/80">
<svg
className="w-4 h-4"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M7 16l-4-4m0 0l4-4m-4 4h18"
/>
</svg>
Subtask results
</div>
</div>
<div className="p-4">
<div className="text-sm leading-relaxed markdown-prose text-foreground/90">
<ReactMarkdown
remarkPlugins={[remarkBreaks]}
rehypePlugins={[rehypeSanitize]}
components={{
a: PlainTextLink,
code: CodeBlock,
}}
>
{message.text}
</ReactMarkdown>
</div>
</div>
</div>
</div>
);
}
// For other tool messages, render only the tool usage badge in the space between bubbles
if (isTool) {
return (
<div key={message.id} className="py-2 pl-4">
@ -424,6 +520,7 @@ const isVisible = (message: Message) => {
(message.ask === 'text' ||
message.say === 'text' ||
message.say === 'completion_result' ||
message.say === 'subtask_result' ||
message.say === 'user_feedback') &&
typeof message.text === 'string' &&
message.text.length > 0