mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-06 16:31:12 +08:00
feat: Implement phases 6 to 9 of the review cycle fix process, including discovery, batching, parallel planning, execution, and completion
- Added Phase 6: Fix Discovery & Batching with intelligent grouping and batching of findings. - Added Phase 7: Fix Parallel Planning to launch planning agents for concurrent analysis and aggregation of partial plans. - Added Phase 8: Fix Execution for stage-based execution of fixes with conservative test verification. - Added Phase 9: Fix Completion to aggregate results, generate summary reports, and handle session completion. - Introduced new frontend components: ResizeHandle for draggable resizing of sidebar panels and useResizablePanel hook for managing panel sizes with localStorage persistence. - Added PowerShell script for checking TypeScript errors in source code, excluding test files.
This commit is contained in:
@@ -59,7 +59,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockData);
|
||||
@@ -71,12 +71,12 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
mockApi.get.mockResolvedValue({ data: mockData });
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useWorkflowStatusCounts({ projectPath: '/test/workspace' }),
|
||||
() => useWorkflowStatusCounts({ projectPath: '/test/workspace' } as any),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith('/api/session-status-counts', {
|
||||
@@ -90,7 +90,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isError).toBe(true);
|
||||
expect((result.current as any).isError).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.error).toBeDefined();
|
||||
@@ -102,13 +102,13 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
mockApi.get.mockResolvedValue({ data: mockData });
|
||||
|
||||
const { result: result1 } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
await waitFor(() => expect(result1.current.isSuccess).toBe(true));
|
||||
await waitFor(() => expect((result1.current as any).isSuccess).toBe(true));
|
||||
|
||||
// Second render should use cache
|
||||
const { result: result2 } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result2.current.isSuccess).toBe(true);
|
||||
expect((result2.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
// API should only be called once (cached)
|
||||
@@ -122,7 +122,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
|
||||
const { result } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
await waitFor(() => expect((result.current as any).isSuccess).toBe(true));
|
||||
|
||||
// Refetch
|
||||
await result.current.refetch();
|
||||
@@ -143,7 +143,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result } = renderHook(() => useActivityTimeline(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockData);
|
||||
@@ -159,10 +159,10 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
end: new Date('2026-01-31'),
|
||||
};
|
||||
|
||||
const { result } = renderHook(() => useActivityTimeline(dateRange), { wrapper });
|
||||
const { result } = renderHook(() => (useActivityTimeline as any)(dateRange), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith('/api/activity-timeline', {
|
||||
@@ -179,7 +179,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result } = renderHook(() => useActivityTimeline(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual([]);
|
||||
@@ -190,12 +190,12 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
mockApi.get.mockResolvedValue({ data: mockData });
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useActivityTimeline(undefined, '/test/workspace'),
|
||||
() => (useActivityTimeline as any)(undefined, '/test/workspace'),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith('/api/activity-timeline', {
|
||||
@@ -210,11 +210,11 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
mockApi.get.mockResolvedValueOnce({ data: mockData1 });
|
||||
|
||||
const { result, rerender } = renderHook(
|
||||
({ workspace }: { workspace?: string }) => useActivityTimeline(undefined, workspace),
|
||||
({ workspace }: { workspace?: string }) => (useActivityTimeline as any)(undefined, workspace),
|
||||
{ wrapper, initialProps: { workspace: '/workspace1' } }
|
||||
);
|
||||
|
||||
await waitFor(() => expect(result.current.isSuccess).toBe(true));
|
||||
await waitFor(() => expect((result.current as any).isSuccess).toBe(true));
|
||||
expect(result.current.data).toEqual(mockData1);
|
||||
|
||||
// Change workspace
|
||||
@@ -242,7 +242,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result } = renderHook(() => useTaskTypeCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockData);
|
||||
@@ -254,12 +254,12 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
mockApi.get.mockResolvedValue({ data: mockData });
|
||||
|
||||
const { result } = renderHook(
|
||||
() => useTaskTypeCounts({ projectPath: '/test/workspace' }),
|
||||
() => useTaskTypeCounts({ projectPath: '/test/workspace' } as any),
|
||||
{ wrapper }
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledWith('/api/task-type-counts', {
|
||||
@@ -278,7 +278,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result } = renderHook(() => useTaskTypeCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(result.current.data).toEqual(mockData);
|
||||
@@ -294,7 +294,7 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isSuccess).toBe(true);
|
||||
expect((result.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
// Data should be fresh for 30s
|
||||
@@ -318,9 +318,9 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result: result3 } = renderHook(() => useTaskTypeCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result1.current.isSuccess).toBe(true);
|
||||
expect(result2.current.isSuccess).toBe(true);
|
||||
expect(result3.current.isSuccess).toBe(true);
|
||||
expect((result1.current as any).isSuccess).toBe(true);
|
||||
expect((result2.current as any).isSuccess).toBe(true);
|
||||
expect((result3.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
expect(mockApi.get).toHaveBeenCalledTimes(3);
|
||||
@@ -343,9 +343,9 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
const { result: result3 } = renderHook(() => useTaskTypeCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result1.current.isError).toBe(true);
|
||||
expect(result2.current.isSuccess).toBe(true);
|
||||
expect(result3.current.isSuccess).toBe(true);
|
||||
expect((result1.current as any).isError).toBe(true);
|
||||
expect((result2.current as any).isSuccess).toBe(true);
|
||||
expect((result3.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -355,13 +355,13 @@ describe('Chart Hooks Integration Tests', () => {
|
||||
|
||||
// First component
|
||||
const { result: result1 } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
await waitFor(() => expect(result1.current.isSuccess).toBe(true));
|
||||
await waitFor(() => expect((result1.current as any).isSuccess).toBe(true));
|
||||
|
||||
// Second component should use cache
|
||||
const { result: result2 } = renderHook(() => useWorkflowStatusCounts(), { wrapper });
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result2.current.isSuccess).toBe(true);
|
||||
expect((result2.current as any).isSuccess).toBe(true);
|
||||
});
|
||||
|
||||
// Only one API call
|
||||
|
||||
@@ -183,7 +183,7 @@ describe('useCodexLens Hook', () => {
|
||||
|
||||
describe('useCodexLensModels', () => {
|
||||
it('should fetch and filter models by type', async () => {
|
||||
vi.mocked(api.fetchCodexLensModels).mockResolvedValue(mockModelsData);
|
||||
vi.mocked(api.fetchCodexLensModels).mockResolvedValue(mockModelsData as any);
|
||||
|
||||
const { result } = renderHook(() => useCodexLensModels(), { wrapper });
|
||||
|
||||
@@ -203,7 +203,7 @@ describe('useCodexLens Hook', () => {
|
||||
settings: { SETTING1: 'setting1' },
|
||||
raw: 'KEY1=value1\nKEY2=value2',
|
||||
};
|
||||
vi.mocked(api.fetchCodexLensEnv).mockResolvedValue(mockEnv);
|
||||
vi.mocked(api.fetchCodexLensEnv).mockResolvedValue(mockEnv as any);
|
||||
|
||||
const { result } = renderHook(() => useCodexLensEnv(), { wrapper });
|
||||
|
||||
@@ -225,8 +225,8 @@ describe('useCodexLens Hook', () => {
|
||||
],
|
||||
selected_device_id: 0,
|
||||
};
|
||||
vi.mocked(api.fetchCodexLensGpuDetect).mockResolvedValue(mockDetect);
|
||||
vi.mocked(api.fetchCodexLensGpuList).mockResolvedValue(mockList);
|
||||
vi.mocked(api.fetchCodexLensGpuDetect).mockResolvedValue(mockDetect as any);
|
||||
vi.mocked(api.fetchCodexLensGpuList).mockResolvedValue(mockList as any);
|
||||
|
||||
const { result } = renderHook(() => useCodexLensGpu(), { wrapper });
|
||||
|
||||
@@ -366,13 +366,13 @@ describe('useCodexLens Hook', () => {
|
||||
env: { KEY1: 'newvalue' },
|
||||
settings: {},
|
||||
raw: 'KEY1=newvalue',
|
||||
});
|
||||
} as any);
|
||||
|
||||
const { result } = renderHook(() => useUpdateCodexLensEnv(), { wrapper });
|
||||
|
||||
const updateResult = await result.current.updateEnv({
|
||||
raw: 'KEY1=newvalue',
|
||||
});
|
||||
} as any);
|
||||
|
||||
expect(api.updateCodexLensEnv).toHaveBeenCalledWith({ raw: 'KEY1=newvalue' });
|
||||
expect(updateResult.success).toBe(true);
|
||||
|
||||
@@ -1000,7 +1000,7 @@ export function useCodexLensIndexingStatus(): UseCodexLensIndexingStatusReturn {
|
||||
queryKey: codexLensKeys.indexingStatus(),
|
||||
queryFn: checkCodexLensIndexingStatus,
|
||||
staleTime: STALE_TIME_SHORT,
|
||||
refetchInterval: (data) => (data?.inProgress ? 2000 : false), // Poll every 2s when indexing
|
||||
refetchInterval: (query) => ((query.state.data as any)?.inProgress ? 2000 : false), // Poll every 2s when indexing
|
||||
retry: false,
|
||||
});
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ describe('useIssueQueue', () => {
|
||||
grouped_items: { 'parallel-group': ['task1', 'task2'] },
|
||||
};
|
||||
|
||||
vi.mocked(api.fetchIssueQueue).mockResolvedValue(mockQueue);
|
||||
vi.mocked(api.fetchIssueQueue).mockResolvedValue(mockQueue as any);
|
||||
|
||||
const { result } = renderHook(() => useIssueQueue(), {
|
||||
wrapper: createWrapper(),
|
||||
@@ -192,7 +192,7 @@ describe('useIssueDiscovery', () => {
|
||||
vi.mocked(api.fetchDiscoveries).mockResolvedValue([
|
||||
{ id: '1', name: 'Session 1', status: 'completed' as const, progress: 100, findings_count: 2, created_at: '2024-01-01T00:00:00Z' },
|
||||
]);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings as any);
|
||||
|
||||
const { result } = renderHook(() => useIssueDiscovery(), {
|
||||
wrapper: createWrapper(),
|
||||
@@ -228,7 +228,7 @@ describe('useIssueDiscovery', () => {
|
||||
vi.mocked(api.fetchDiscoveries).mockResolvedValue([
|
||||
{ id: '1', name: 'Session 1', status: 'completed' as const, progress: 100, findings_count: 2, created_at: '2024-01-01T00:00:00Z' },
|
||||
]);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings as any);
|
||||
|
||||
const { result } = renderHook(() => useIssueDiscovery(), {
|
||||
wrapper: createWrapper(),
|
||||
@@ -264,7 +264,7 @@ describe('useIssueDiscovery', () => {
|
||||
vi.mocked(api.fetchDiscoveries).mockResolvedValue([
|
||||
{ id: '1', name: 'Session 1', status: 'completed' as const, progress: 100, findings_count: 2, created_at: '2024-01-01T00:00:00Z' },
|
||||
]);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings as any);
|
||||
|
||||
const { result } = renderHook(() => useIssueDiscovery(), {
|
||||
wrapper: createWrapper(),
|
||||
@@ -299,7 +299,7 @@ describe('useIssueDiscovery', () => {
|
||||
vi.mocked(api.fetchDiscoveries).mockResolvedValue([
|
||||
{ id: '1', name: 'Session 1', status: 'completed' as const, progress: 100, findings_count: 1, created_at: '2024-01-01T00:00:00Z' },
|
||||
]);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings);
|
||||
vi.mocked(api.fetchDiscoveryFindings).mockResolvedValue(mockFindings as any);
|
||||
|
||||
const { result } = renderHook(() => useIssueDiscovery(), {
|
||||
wrapper: createWrapper(),
|
||||
|
||||
@@ -56,6 +56,10 @@ export function useLocale(): UseLocaleReturn {
|
||||
* Hook to format i18n messages with the current locale
|
||||
* @returns A formatMessage function for translating message IDs
|
||||
*
|
||||
* Supports both string and react-intl descriptor formats:
|
||||
* - formatMessage('home.title')
|
||||
* - formatMessage({ id: 'home.title' })
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* const formatMessage = useFormatMessage();
|
||||
@@ -63,12 +67,13 @@ export function useLocale(): UseLocaleReturn {
|
||||
* ```
|
||||
*/
|
||||
export function useFormatMessage(): (
|
||||
id: string,
|
||||
idOrDescriptor: string | { id: string; defaultMessage?: string },
|
||||
values?: Record<string, string | number | boolean | Date | null | undefined>
|
||||
) => string {
|
||||
// Use useMemo to avoid recreating the function on each render
|
||||
return useMemo(() => {
|
||||
return (id: string, values?: Record<string, string | number | boolean | Date | null | undefined>) => {
|
||||
return (idOrDescriptor: string | { id: string; defaultMessage?: string }, values?: Record<string, string | number | boolean | Date | null | undefined>) => {
|
||||
const id = typeof idOrDescriptor === 'string' ? idOrDescriptor : idOrDescriptor.id;
|
||||
return formatMessage(id, values);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -298,7 +298,7 @@ export function usePrefetchSessions() {
|
||||
return (filter?: SessionsFilter) => {
|
||||
queryClient.prefetchQuery({
|
||||
queryKey: sessionsKeys.list(filter),
|
||||
queryFn: fetchSessions,
|
||||
queryFn: () => fetchSessions(),
|
||||
staleTime: STALE_TIME,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
type ExecutionLog,
|
||||
} from '../types/execution';
|
||||
import { SurfaceUpdateSchema } from '../packages/a2ui-runtime/core/A2UITypes';
|
||||
import type { ToolCallKind } from '../types/toolCall';
|
||||
|
||||
// Constants
|
||||
const RECONNECT_DELAY_BASE = 1000; // 1 second
|
||||
@@ -42,6 +43,15 @@ function getStoreState() {
|
||||
addLog: execution.addLog,
|
||||
completeExecution: execution.completeExecution,
|
||||
currentExecution: execution.currentExecution,
|
||||
// Tool call actions
|
||||
startToolCall: execution.startToolCall,
|
||||
updateToolCall: execution.updateToolCall,
|
||||
completeToolCall: execution.completeToolCall,
|
||||
toggleToolCallExpanded: execution.toggleToolCallExpanded,
|
||||
// Tool call getters
|
||||
getToolCallsForNode: execution.getToolCallsForNode,
|
||||
// Node output actions
|
||||
addNodeOutput: execution.addNodeOutput,
|
||||
// Flow store
|
||||
updateNode: flow.updateNode,
|
||||
// CLI stream store
|
||||
@@ -60,6 +70,61 @@ export interface UseWebSocketReturn {
|
||||
reconnect: () => void;
|
||||
}
|
||||
|
||||
// ========== Tool Call Parsing Helpers ==========
|
||||
|
||||
/**
|
||||
* Parse tool call metadata from content
|
||||
* Expected format: "[Tool] toolName(args)"
|
||||
*/
|
||||
function parseToolCallMetadata(content: string): { toolName: string; args: string } | null {
|
||||
// Handle string content
|
||||
if (typeof content === 'string') {
|
||||
const match = content.match(/^\[Tool\]\s+(\w+)\((.*)\)$/);
|
||||
if (match) {
|
||||
return { toolName: match[1], args: match[2] || '' };
|
||||
}
|
||||
}
|
||||
|
||||
// Handle object content with toolName field
|
||||
try {
|
||||
const parsed = typeof content === 'string' ? JSON.parse(content) : content;
|
||||
if (parsed && typeof parsed === 'object' && 'toolName' in parsed) {
|
||||
return {
|
||||
toolName: String(parsed.toolName),
|
||||
args: parsed.parameters ? JSON.stringify(parsed.parameters) : '',
|
||||
};
|
||||
}
|
||||
} catch {
|
||||
// Not valid JSON, return null
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Infer tool call kind from tool name
|
||||
*/
|
||||
function inferToolCallKind(toolName: string): ToolCallKind {
|
||||
const name = toolName.toLowerCase();
|
||||
|
||||
if (name === 'exec_command' || name === 'execute') return 'execute';
|
||||
if (name === 'apply_patch' || name === 'patch') return 'patch';
|
||||
if (name === 'web_search' || name === 'exa_search') return 'web_search';
|
||||
if (name.startsWith('mcp_') || name.includes('mcp')) return 'mcp_tool';
|
||||
if (name.includes('file') || name.includes('read') || name.includes('write')) return 'file_operation';
|
||||
if (name.includes('think') || name.includes('reason')) return 'thinking';
|
||||
|
||||
// Default to execute
|
||||
return 'execute';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate unique tool call ID
|
||||
*/
|
||||
function generateToolCallId(): string {
|
||||
return `tool_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
||||
}
|
||||
|
||||
export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketReturn {
|
||||
const { enabled = true, onMessage } = options;
|
||||
|
||||
@@ -105,7 +170,7 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
const unitContent = unit?.content || outputData;
|
||||
const unitType = unit?.type || chunkType;
|
||||
|
||||
// Special handling for tool_call type
|
||||
// Convert content to string for display
|
||||
let content: string;
|
||||
if (unitType === 'tool_call' && typeof unitContent === 'object' && unitContent !== null) {
|
||||
// Format tool_call display
|
||||
@@ -114,7 +179,49 @@ export function useWebSocket(options: UseWebSocketOptions = {}): UseWebSocketRet
|
||||
content = typeof unitContent === 'string' ? unitContent : JSON.stringify(unitContent);
|
||||
}
|
||||
|
||||
// Split by lines and add each line to store
|
||||
// ========== Tool Call Processing ==========
|
||||
// Parse and start new tool call if this is a tool_call type
|
||||
if (unitType === 'tool_call') {
|
||||
const metadata = parseToolCallMetadata(content);
|
||||
if (metadata) {
|
||||
const callId = generateToolCallId();
|
||||
const currentNodeId = stores.currentExecution?.currentNodeId;
|
||||
|
||||
if (currentNodeId) {
|
||||
stores.startToolCall(currentNodeId, callId, {
|
||||
kind: inferToolCallKind(metadata.toolName),
|
||||
description: metadata.args
|
||||
? `${metadata.toolName}(${metadata.args})`
|
||||
: metadata.toolName,
|
||||
});
|
||||
|
||||
// Also add to node output for streaming display
|
||||
stores.addNodeOutput(currentNodeId, {
|
||||
type: 'tool_call',
|
||||
content,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Stream Processing ==========
|
||||
// Update tool call output buffer if we have an active tool call for this node
|
||||
const currentNodeId = stores.currentExecution?.currentNodeId;
|
||||
if (currentNodeId && (unitType === 'stdout' || unitType === 'stderr')) {
|
||||
const toolCalls = stores.getToolCallsForNode?.(currentNodeId);
|
||||
const activeCall = toolCalls?.find(c => c.status === 'executing');
|
||||
|
||||
if (activeCall) {
|
||||
stores.updateToolCall(currentNodeId, activeCall.callId, {
|
||||
outputChunk: content,
|
||||
stream: unitType === 'stderr' ? 'stderr' : 'stdout',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Legacy CLI Stream Output ==========
|
||||
// Split by lines and add each line to cliStreamStore
|
||||
const lines = content.split('\n');
|
||||
lines.forEach((line: string) => {
|
||||
// Add non-empty lines, or single line if that's all we have
|
||||
|
||||
Reference in New Issue
Block a user