mirror of https://github.com/buster-so/buster.git
write file message
This commit is contained in:
parent
edfa13e785
commit
f601bf7ca1
|
@ -2,6 +2,7 @@ import { Box, Text } from 'ink';
|
|||
import React from 'react';
|
||||
import type { AgentMessage } from '../services/analytics-engineer-handler';
|
||||
import { ExecuteMessage } from './execute-message';
|
||||
import { WriteMessage } from './write-message';
|
||||
|
||||
interface AgentMessageComponentProps {
|
||||
message: AgentMessage;
|
||||
|
@ -42,6 +43,10 @@ export function AgentMessageComponent({ message }: AgentMessageComponentProps) {
|
|||
// For execute commands, use the ExecuteMessage component
|
||||
return <ExecuteMessage message={message} />;
|
||||
|
||||
case 'write':
|
||||
// For write operations, use the WriteMessage component
|
||||
return <WriteMessage message={message} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,87 @@
|
|||
import { Box, Text, useInput } from 'ink';
|
||||
import React, { useState } from 'react';
|
||||
import path from 'node:path';
|
||||
import type { AgentMessage } from '../services/analytics-engineer-handler';
|
||||
|
||||
interface WriteMessageProps {
|
||||
message: Extract<AgentMessage, { kind: 'write' }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for displaying write file operations
|
||||
* Shows WRITE badge, relative file path, and file content preview
|
||||
* Supports expansion with Ctrl+O to show full content
|
||||
*/
|
||||
export function WriteMessage({ message }: WriteMessageProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// Handle Ctrl+O to toggle expansion
|
||||
useInput((input, key) => {
|
||||
if (key.ctrl && input === 'o') {
|
||||
setIsExpanded((prev) => !prev);
|
||||
}
|
||||
});
|
||||
|
||||
const { args, result } = message;
|
||||
|
||||
// For each file, show its content
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{args.files.map((file, fileIdx) => {
|
||||
// Get relative path from cwd
|
||||
const relativePath = path.relative(process.cwd(), file.path);
|
||||
|
||||
// Split content into lines
|
||||
const contentLines = file.content.split('\n');
|
||||
|
||||
// Show first 5 lines when not expanded, all lines when expanded
|
||||
const displayLines = isExpanded ? contentLines : contentLines.slice(0, 5);
|
||||
|
||||
// Find the result for this file
|
||||
const fileResult = result?.results[fileIdx];
|
||||
const success = fileResult?.status === 'success';
|
||||
|
||||
return (
|
||||
<Box key={fileIdx} flexDirection="column" marginBottom={fileIdx < args.files.length - 1 ? 1 : 0}>
|
||||
{/* WRITE badge with relative file path */}
|
||||
<Box flexDirection="row">
|
||||
<Text bold color="white" backgroundColor="magenta">
|
||||
WRITE
|
||||
</Text>
|
||||
<Text color="#94a3b8"> ({relativePath})</Text>
|
||||
</Box>
|
||||
|
||||
{/* File content lines - always show with indentation */}
|
||||
{contentLines.length > 0 && (
|
||||
<Box flexDirection="column" paddingLeft={2}>
|
||||
{displayLines.map((line, idx) => (
|
||||
<Text key={idx} color="#e0e7ff">
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Expansion hint if content is long */}
|
||||
{contentLines.length > 5 && (
|
||||
<Box paddingLeft={2}>
|
||||
<Text color="#64748b" dimColor>
|
||||
{isExpanded ? '(Press Ctrl+O to collapse)' : `... +${contentLines.length - 5} lines (Press Ctrl+O to expand)`}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Status line with indentation */}
|
||||
{fileResult && (
|
||||
<Box paddingLeft={2}>
|
||||
<Text color={success ? '#64748b' : 'red'} dimColor>
|
||||
↳ {success ? `Wrote ${contentLines.length} lines` : `Failed: ${fileResult.errorMessage}`}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -36,6 +36,12 @@ export type AgentMessage =
|
|||
event: 'start' | 'complete';
|
||||
args: LsToolInput;
|
||||
result?: LsToolOutput;
|
||||
}
|
||||
| {
|
||||
kind: 'write';
|
||||
event: 'start' | 'complete';
|
||||
args: { files: { path: string; content: string }[] };
|
||||
result?: { results: Array<{ status: 'success' | 'error'; filePath: string; errorMessage?: string }> };
|
||||
};
|
||||
|
||||
export interface DocsAgentMessage {
|
||||
|
@ -122,6 +128,18 @@ export async function runDocsAgent(params: RunDocsAgentParams) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Handle write tool events - only show complete to avoid duplicates
|
||||
if (event.tool === 'writeFileTool' && event.event === 'complete') {
|
||||
onMessage({
|
||||
message: {
|
||||
kind: 'write',
|
||||
event: 'complete',
|
||||
args: event.args,
|
||||
result: event.result, // Type-safe: WriteFileToolOutput
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -64,6 +64,7 @@ export function createAnalyticsEngineerAgent(analyticsEngineerAgentOptions: Anal
|
|||
const writeFileTool = createWriteFileTool({
|
||||
messageId: analyticsEngineerAgentOptions.messageId,
|
||||
projectDirectory: analyticsEngineerAgentOptions.folder_structure,
|
||||
onToolEvent: analyticsEngineerAgentOptions.onToolEvent,
|
||||
});
|
||||
const grepTool = createGrepTool({
|
||||
messageId: analyticsEngineerAgentOptions.messageId,
|
||||
|
|
|
@ -90,11 +90,18 @@ async function createSingleFile(
|
|||
*/
|
||||
export function createWriteFileToolExecute(context: WriteFileToolContext) {
|
||||
return async function execute(input: WriteFileToolInput): Promise<WriteFileToolOutput> {
|
||||
const { messageId, projectDirectory } = context;
|
||||
const { messageId, projectDirectory, onToolEvent } = context;
|
||||
const { files } = input;
|
||||
|
||||
console.info(`Creating ${files.length} file(s) for message ${messageId}`);
|
||||
|
||||
// Emit start event
|
||||
onToolEvent?.({
|
||||
tool: 'writeFileTool',
|
||||
event: 'start',
|
||||
args: input,
|
||||
});
|
||||
|
||||
// Process all files in parallel
|
||||
const fileResults = await Promise.all(
|
||||
files.map((file) => createSingleFile(file.path, file.content, projectDirectory))
|
||||
|
@ -127,6 +134,16 @@ export function createWriteFileToolExecute(context: WriteFileToolContext) {
|
|||
console.error('Failed files:', errors);
|
||||
}
|
||||
|
||||
return { results };
|
||||
const output = { results };
|
||||
|
||||
// Emit complete event
|
||||
onToolEvent?.({
|
||||
tool: 'writeFileTool',
|
||||
event: 'complete',
|
||||
result: output,
|
||||
args: input,
|
||||
});
|
||||
|
||||
return output;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ const WriteFileToolOutputSchema = z.object({
|
|||
const WriteFileToolContextSchema = z.object({
|
||||
messageId: z.string().describe('The message ID for database updates'),
|
||||
projectDirectory: z.string().describe('The root directory of the project'),
|
||||
onToolEvent: z.any().optional().describe('Callback for tool events'),
|
||||
});
|
||||
|
||||
export type WriteFileToolInput = z.infer<typeof WriteFileToolInputSchema>;
|
||||
|
|
Loading…
Reference in New Issue