diff --git a/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts b/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts index 3a782ef68..252653217 100644 --- a/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts +++ b/apps/trigger/src/tasks/slack-agent-task/slack-agent-task.ts @@ -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', { diff --git a/packages/ai/src/context/sandbox-context.ts b/packages/ai/src/context/sandbox-context.ts new file mode 100644 index 000000000..d9f000cdd --- /dev/null +++ b/packages/ai/src/context/sandbox-context.ts @@ -0,0 +1,9 @@ +import type Sandbox from '@daytonaio/sdk'; + +export enum SandboxContextKey { + Sandbox = 'sandbox', +} + +export type SandboxContext = { + sandbox: Sandbox; +}; diff --git a/packages/ai/src/tools/file-tools/file-operations.ts b/packages/ai/src/tools/file-tools/read-files-tool/read-file.ts similarity index 77% rename from packages/ai/src/tools/file-tools/file-operations.ts rename to packages/ai/src/tools/file-tools/read-files-tool/read-file.ts index f957d2e0c..57a3fc8ad 100644 --- a/packages/ai/src/tools/file-tools/file-operations.ts +++ b/packages/ai/src/tools/file-tools/read-files-tool/read-file.ts @@ -11,9 +11,7 @@ export interface FileReadResult { async function readSingleFile(filePath: string): Promise { 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 { 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 { } export async function readFilesSafely(filePaths: string[]): Promise { - const fileReadPromises = filePaths.map(filePath => readSingleFile(filePath)); + const fileReadPromises = filePaths.map((filePath) => readSingleFile(filePath)); return Promise.all(fileReadPromises); } diff --git a/packages/ai/src/tools/file-tools/read-files.ts b/packages/ai/src/tools/file-tools/read-files-tool/read-files-tool.ts similarity index 58% rename from packages/ai/src/tools/file-tools/read-files.ts rename to packages/ai/src/tools/file-tools/read-files-tool/read-files-tool.ts index d91ff1d99..8a82aaa7a 100644 --- a/packages/ai/src/tools/file-tools/read-files.ts +++ b/packages/ai/src/tools/file-tools/read-files-tool/read-files-tool.ts @@ -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> | 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, - _runtimeContext: RuntimeContext + runtimeContext: RuntimeContext ): Promise> => { 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; - runtimeContext: RuntimeContext; + runtimeContext: RuntimeContext; }) => { return await readFilesExecution(context, runtimeContext); }, diff --git a/packages/ai/src/tools/file-tools/read-files-tool/read-files.test.ts b/packages/ai/src/tools/file-tools/read-files-tool/read-files.test.ts new file mode 100644 index 000000000..8a95cc6d3 --- /dev/null +++ b/packages/ai/src/tools/file-tools/read-files-tool/read-files.test.ts @@ -0,0 +1 @@ +// This file is intentionally empty as all tests were related to the removed parseStreamingArgs function diff --git a/packages/ai/src/tools/file-tools/read-files.test.ts b/packages/ai/src/tools/file-tools/read-files.test.ts deleted file mode 100644 index a185e07af..000000000 --- a/packages/ai/src/tools/file-tools/read-files.test.ts +++ /dev/null @@ -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'], - }); - }); -}); diff --git a/packages/ai/src/tools/index.ts b/packages/ai/src/tools/index.ts index 16a4c3537..78816bb31 100644 --- a/packages/ai/src/tools/index.ts +++ b/packages/ai/src/tools/index.ts @@ -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'; diff --git a/packages/sandbox/package.json b/packages/sandbox/package.json index f72060fb4..35903480d 100644 --- a/packages/sandbox/package.json +++ b/packages/sandbox/package.json @@ -29,7 +29,7 @@ "dependencies": { "@buster/typescript-config": "workspace:*", "@buster/vitest-config": "workspace:*", - "@daytonaio/sdk": "^0.24.2", + "@daytonaio/sdk": "catalog:", "zod": "catalog:" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6599158b1..e61abbeb4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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:' diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3ee165a56..f06295e3c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -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"