diff --git a/src/components/app-builder/project-manager/sessions/v2/streaming.ts b/src/components/app-builder/project-manager/sessions/v2/streaming.ts index fdf327183..2cf8e703f 100644 --- a/src/components/app-builder/project-manager/sessions/v2/streaming.ts +++ b/src/components/app-builder/project-manager/sessions/v2/streaming.ts @@ -180,7 +180,7 @@ export function createV2StreamingCoordinator(config: V2StreamingConfig): V2Strea }); }, - onSessionStatusChanged: status => { + onSessionStatusChanged: (status, _eventSessionId) => { if (status.type === 'idle') { store.setState({ isStreaming: false }); onStreamComplete?.(); diff --git a/src/components/cloud-agent-next/useCloudAgentStream.ts b/src/components/cloud-agent-next/useCloudAgentStream.ts index 7ac88150f..89a3c270d 100644 --- a/src/components/cloud-agent-next/useCloudAgentStream.ts +++ b/src/components/cloud-agent-next/useCloudAgentStream.ts @@ -239,7 +239,7 @@ export function useCloudAgentStream({ } }, - onSessionStatusChanged: status => { + onSessionStatusChanged: (status, _eventSessionId) => { setSessionStatus(status); // Handle streaming state based on status diff --git a/src/lib/cloud-agent-next/processor/event-processor.test.ts b/src/lib/cloud-agent-next/processor/event-processor.test.ts index eb26c6252..ad865f764 100644 --- a/src/lib/cloud-agent-next/processor/event-processor.test.ts +++ b/src/lib/cloud-agent-next/processor/event-processor.test.ts @@ -441,7 +441,10 @@ describe('createEventProcessor', () => { }) ); - expect(callbacks.onSessionStatusChanged).toHaveBeenCalledWith({ type: 'busy' }); + expect(callbacks.onSessionStatusChanged).toHaveBeenCalledWith( + { type: 'busy' }, + 'session-123' + ); expect(callbacks.onStreamingChanged).toHaveBeenCalledWith(true); }); @@ -468,7 +471,10 @@ describe('createEventProcessor', () => { }) ); - expect(callbacks.onSessionStatusChanged).toHaveBeenLastCalledWith({ type: 'idle' }); + expect(callbacks.onSessionStatusChanged).toHaveBeenLastCalledWith( + { type: 'idle' }, + 'session-123' + ); expect(callbacks.onStreamingChanged).toHaveBeenLastCalledWith(false); }); diff --git a/src/lib/cloud-agent-next/processor/event-processor.ts b/src/lib/cloud-agent-next/processor/event-processor.ts index b745ca8e2..d41465468 100644 --- a/src/lib/cloud-agent-next/processor/event-processor.ts +++ b/src/lib/cloud-agent-next/processor/event-processor.ts @@ -309,9 +309,9 @@ export function createEventProcessor(config: EventProcessorConfig = {}): EventPr * Handle session.status events. */ function handleSessionStatus(data: EventSessionStatus['properties']): void { - const { status } = data; + const { sessionID, status } = data; - callbacks.onSessionStatusChanged?.(status); + callbacks.onSessionStatusChanged?.(status, sessionID); // Update streaming state based on status if (status.type === 'idle') { diff --git a/src/lib/cloud-agent-next/processor/types.ts b/src/lib/cloud-agent-next/processor/types.ts index 7fd73e911..f8bf7b641 100644 --- a/src/lib/cloud-agent-next/processor/types.ts +++ b/src/lib/cloud-agent-next/processor/types.ts @@ -69,8 +69,8 @@ export type EventProcessorCallbacks = { parentSessionId: string | null ) => void; - /** Called when session status changes (idle/busy/retry) */ - onSessionStatusChanged?: (status: SessionStatus) => void; + /** Called when session status changes (idle/busy/retry). sessionID identifies which session changed. */ + onSessionStatusChanged?: (status: SessionStatus, sessionID: string) => void; /** Called when a new session is created (session.created event) */ onSessionCreated?: (sessionInfo: Session) => void; diff --git a/src/lib/cloud-agent-next/run-session.ts b/src/lib/cloud-agent-next/run-session.ts index 3fd979720..f4eb013c4 100644 --- a/src/lib/cloud-agent-next/run-session.ts +++ b/src/lib/cloud-agent-next/run-session.ts @@ -242,8 +242,11 @@ export async function runSessionToCompletion(input: RunSessionInput): Promise { - if (status.type === 'idle') resolveOnce(); + onSessionStatusChanged: (status, eventSessionId) => { + // Only resolve when the *root* session goes idle. Subagent (child) + // sessions also emit idle events; resolving on those would cause + // the orchestrator to return a partial result prematurely. + if (status.type === 'idle' && eventSessionId === sessionId) resolveOnce(); }, onError: error => { hasError = true;