mirror of https://github.com/buster-so/buster.git
rearrange and get some things right for the initial read file tool
This commit is contained in:
parent
7e1adbec2a
commit
06249f5e8a
|
@ -330,27 +330,27 @@ export const slackAgentTask: ReturnType<
|
|||
let progressMessageTs: string | undefined;
|
||||
let queuedMessageTs: string | undefined;
|
||||
let hasStartedRunning = false;
|
||||
|
||||
|
||||
// First, do rapid polling for up to 20 seconds to see if task starts
|
||||
const rapidPollInterval = 1000; // 1 second
|
||||
const maxRapidPolls = 20; // 20 attempts = 20 seconds total
|
||||
let rapidPollCount = 0;
|
||||
let isComplete = false;
|
||||
let analystResult: { ok: boolean; output?: unknown; error?: unknown } | null = null;
|
||||
|
||||
|
||||
while (rapidPollCount < maxRapidPolls && !hasStartedRunning && !isComplete) {
|
||||
await wait.for({ seconds: rapidPollInterval / 1000 });
|
||||
rapidPollCount++;
|
||||
|
||||
|
||||
try {
|
||||
const run = await runs.retrieve(analystHandle.id);
|
||||
|
||||
|
||||
logger.log('Rapid polling analyst task status', {
|
||||
runId: analystHandle.id,
|
||||
status: run.status,
|
||||
pollCount: rapidPollCount,
|
||||
});
|
||||
|
||||
|
||||
// Check if task has started
|
||||
if (run.status === 'EXECUTING' || run.status === 'REATTEMPTING') {
|
||||
hasStartedRunning = true;
|
||||
|
@ -358,7 +358,7 @@ export const slackAgentTask: ReturnType<
|
|||
runId: analystHandle.id,
|
||||
pollCount: rapidPollCount,
|
||||
});
|
||||
|
||||
|
||||
// Send the progress message
|
||||
try {
|
||||
const progressMessage = {
|
||||
|
@ -397,7 +397,9 @@ export const slackAgentTask: ReturnType<
|
|||
|
||||
if (sendResult.success && sendResult.messageTs) {
|
||||
progressMessageTs = sendResult.messageTs;
|
||||
logger.log('Sent progress message to Slack thread', { messageTs: progressMessageTs });
|
||||
logger.log('Sent progress message to Slack thread', {
|
||||
messageTs: progressMessageTs,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Failed to send progress message to Slack', {
|
||||
|
@ -454,7 +456,7 @@ export const slackAgentTask: ReturnType<
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Step 7: Main polling loop for task completion
|
||||
const maxPollingTime = 30 * 60 * 1000; // 30 minutes
|
||||
const normalPollingInterval = 10000; // 10 seconds
|
||||
|
@ -542,7 +544,9 @@ export const slackAgentTask: ReturnType<
|
|||
|
||||
if (sendResult.success && sendResult.messageTs) {
|
||||
progressMessageTs = sendResult.messageTs;
|
||||
logger.log('Sent progress message to Slack thread', { messageTs: progressMessageTs });
|
||||
logger.log('Sent progress message to Slack thread', {
|
||||
messageTs: progressMessageTs,
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn('Failed to send progress message to Slack', {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import type Sandbox from '@daytonaio/sdk';
|
||||
|
||||
export enum SandboxContextKey {
|
||||
Sandbox = 'sandbox',
|
||||
}
|
||||
|
||||
export type SandboxContext = {
|
||||
sandbox: Sandbox;
|
||||
};
|
|
@ -11,9 +11,7 @@ export interface FileReadResult {
|
|||
|
||||
async function readSingleFile(filePath: string): Promise<FileReadResult> {
|
||||
try {
|
||||
const resolvedPath = path.isAbsolute(filePath)
|
||||
? filePath
|
||||
: path.join(process.cwd(), filePath);
|
||||
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
|
||||
|
||||
try {
|
||||
await fs.access(resolvedPath);
|
||||
|
@ -36,14 +34,14 @@ async function readSingleFile(filePath: string): Promise<FileReadResult> {
|
|||
content: truncatedContent,
|
||||
truncated: true,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
success: true,
|
||||
filePath,
|
||||
content,
|
||||
truncated: false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
filePath,
|
||||
content,
|
||||
truncated: false,
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
|
@ -54,6 +52,6 @@ async function readSingleFile(filePath: string): Promise<FileReadResult> {
|
|||
}
|
||||
|
||||
export async function readFilesSafely(filePaths: string[]): Promise<FileReadResult[]> {
|
||||
const fileReadPromises = filePaths.map(filePath => readSingleFile(filePath));
|
||||
const fileReadPromises = filePaths.map((filePath) => readSingleFile(filePath));
|
||||
return Promise.all(fileReadPromises);
|
||||
}
|
|
@ -2,7 +2,8 @@ import type { RuntimeContext } from '@mastra/core/runtime-context';
|
|||
import { createTool } from '@mastra/core/tools';
|
||||
import { wrapTraced } from 'braintrust';
|
||||
import { z } from 'zod';
|
||||
import type { AnalystRuntimeContext } from '../../schemas/workflow-schemas';
|
||||
import type { SandboxContext } from '../../../context/sandbox-context';
|
||||
import type { AnalystRuntimeContext } from '../../../schemas/workflow-schemas';
|
||||
|
||||
const readFilesInputSchema = z.object({
|
||||
files: z
|
||||
|
@ -32,60 +33,10 @@ const readFilesOutputSchema = z.object({
|
|||
),
|
||||
});
|
||||
|
||||
export function parseStreamingArgs(
|
||||
accumulatedText: string
|
||||
): Partial<z.infer<typeof readFilesInputSchema>> | null {
|
||||
if (typeof accumulatedText !== 'string') {
|
||||
throw new Error(`parseStreamingArgs expects string input, got ${typeof accumulatedText}`);
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(accumulatedText);
|
||||
if (parsed.files !== undefined && !Array.isArray(parsed.files)) {
|
||||
console.warn('[read-files parseStreamingArgs] files is not an array:', {
|
||||
type: typeof parsed.files,
|
||||
value: parsed.files,
|
||||
});
|
||||
return null;
|
||||
}
|
||||
return { files: parsed.files || undefined };
|
||||
} catch (error) {
|
||||
if (error instanceof SyntaxError) {
|
||||
const filesMatch = accumulatedText.match(/"files"\s*:\s*\[(.*)/s);
|
||||
if (filesMatch && filesMatch[1] !== undefined) {
|
||||
const arrayContent = filesMatch[1];
|
||||
try {
|
||||
const testArray = `[${arrayContent}]`;
|
||||
const parsed = JSON.parse(testArray);
|
||||
return { files: parsed };
|
||||
} catch {
|
||||
const files: string[] = [];
|
||||
const fileMatches = arrayContent.matchAll(/"((?:[^"\\]|\\.)*)"/g);
|
||||
for (const match of fileMatches) {
|
||||
if (match[1] !== undefined) {
|
||||
const filePath = match[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
|
||||
files.push(filePath);
|
||||
}
|
||||
}
|
||||
return { files };
|
||||
}
|
||||
}
|
||||
const partialMatch = accumulatedText.match(/"files"\s*:\s*\[/);
|
||||
if (partialMatch) {
|
||||
return { files: [] };
|
||||
}
|
||||
return null;
|
||||
}
|
||||
throw new Error(
|
||||
`Unexpected error in parseStreamingArgs: ${error instanceof Error ? error.message : 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const readFilesExecution = wrapTraced(
|
||||
async (
|
||||
params: z.infer<typeof readFilesInputSchema>,
|
||||
_runtimeContext: RuntimeContext<AnalystRuntimeContext>
|
||||
runtimeContext: RuntimeContext<SandboxContext>
|
||||
): Promise<z.infer<typeof readFilesOutputSchema>> => {
|
||||
const { files } = params;
|
||||
|
||||
|
@ -94,7 +45,7 @@ const readFilesExecution = wrapTraced(
|
|||
}
|
||||
|
||||
try {
|
||||
const { readFilesSafely } = await import('./file-operations');
|
||||
const { readFilesSafely } = await import('./read-file');
|
||||
const fileResults = await readFilesSafely(files);
|
||||
|
||||
return {
|
||||
|
@ -137,7 +88,7 @@ export const readFiles = createTool({
|
|||
runtimeContext,
|
||||
}: {
|
||||
context: z.infer<typeof readFilesInputSchema>;
|
||||
runtimeContext: RuntimeContext<AnalystRuntimeContext>;
|
||||
runtimeContext: RuntimeContext<SandboxContext>;
|
||||
}) => {
|
||||
return await readFilesExecution(context, runtimeContext);
|
||||
},
|
|
@ -0,0 +1 @@
|
|||
// This file is intentionally empty as all tests were related to the removed parseStreamingArgs function
|
|
@ -1,72 +0,0 @@
|
|||
import { describe, expect, test } from 'vitest';
|
||||
import { parseStreamingArgs } from './read-files';
|
||||
|
||||
describe('Read Files Tool Streaming Parser', () => {
|
||||
test('should return null for empty or invalid input', () => {
|
||||
expect(parseStreamingArgs('')).toBe(null);
|
||||
expect(parseStreamingArgs('{')).toBe(null);
|
||||
expect(parseStreamingArgs('invalid json')).toBe(null);
|
||||
});
|
||||
|
||||
test('should parse complete JSON with files array', () => {
|
||||
const input = '{"files": ["/path/to/file1.txt", "./relative/file2.ts"]}';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({
|
||||
files: ['/path/to/file1.txt', './relative/file2.ts'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should handle empty files array', () => {
|
||||
const input = '{"files": []}';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({ files: [] });
|
||||
});
|
||||
|
||||
test('should extract partial files array', () => {
|
||||
const input = '{"files": ["/path/to/file1.txt"';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({ files: ['/path/to/file1.txt'] });
|
||||
});
|
||||
|
||||
test('should handle files field start without content', () => {
|
||||
const input = '{"files": [';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({ files: [] });
|
||||
});
|
||||
|
||||
test('should return null for non-array files field', () => {
|
||||
const input = '{"files": "not an array"}';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toBe(null);
|
||||
});
|
||||
|
||||
test('should handle escaped quotes in file paths', () => {
|
||||
const input = '{"files": ["/path/to/\\"quoted\\"/file.txt"]}';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({
|
||||
files: ['/path/to/"quoted"/file.txt'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should extract multiple files from partial JSON', () => {
|
||||
const input = '{"files": ["/file1.txt", "/file2.txt", "/file3.txt"';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({
|
||||
files: ['/file1.txt', '/file2.txt', '/file3.txt'],
|
||||
});
|
||||
});
|
||||
|
||||
test('should throw error for non-string input', () => {
|
||||
expect(() => parseStreamingArgs(123 as any)).toThrow(
|
||||
'parseStreamingArgs expects string input, got number'
|
||||
);
|
||||
});
|
||||
|
||||
test('should handle mixed absolute and relative paths', () => {
|
||||
const input = '{"files": ["/absolute/path.txt", "./relative/path.ts", "../parent/file.js"]}';
|
||||
const result = parseStreamingArgs(input);
|
||||
expect(result).toEqual({
|
||||
files: ['/absolute/path.txt', './relative/path.ts', '../parent/file.js'],
|
||||
});
|
||||
});
|
||||
});
|
|
@ -9,4 +9,4 @@ export { createDashboards } from './visualization-tools/create-dashboards-file-t
|
|||
export { modifyDashboards } from './visualization-tools/modify-dashboards-file-tool';
|
||||
export { executeSql } from './database-tools/execute-sql';
|
||||
export { createTodoList } from './planning-thinking-tools/create-todo-item-tool';
|
||||
export { readFiles } from './file-tools/read-files';
|
||||
export { readFiles } from './file-tools/read-files-tool/read-files-tool';
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
"dependencies": {
|
||||
"@buster/typescript-config": "workspace:*",
|
||||
"@buster/vitest-config": "workspace:*",
|
||||
"@daytonaio/sdk": "^0.24.2",
|
||||
"@daytonaio/sdk": "catalog:",
|
||||
"zod": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@ settings:
|
|||
|
||||
catalogs:
|
||||
default:
|
||||
'@daytonaio/sdk':
|
||||
specifier: ^0.24.2
|
||||
version: 0.24.2
|
||||
'@mastra/core':
|
||||
specifier: ^0.10.8
|
||||
version: 0.10.8
|
||||
|
@ -812,7 +815,7 @@ importers:
|
|||
specifier: workspace:*
|
||||
version: link:../vitest-config
|
||||
'@daytonaio/sdk':
|
||||
specifier: ^0.24.2
|
||||
specifier: 'catalog:'
|
||||
version: 0.24.2
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
|
|
|
@ -17,6 +17,7 @@ catalog:
|
|||
ai: "^4.0.0"
|
||||
axios: "^1.10.0"
|
||||
"braintrust": "^0.0.209"
|
||||
"@daytonaio/sdk": "^0.24.2"
|
||||
drizzle-orm: "^0.44.2"
|
||||
hono: "^4.8.0"
|
||||
pg: "^8.16.2"
|
||||
|
|
Loading…
Reference in New Issue