From 32d4cfa5dcd797fffe3f134918a0f9f482bd8121 Mon Sep 17 00:00:00 2001 From: kami Date: Tue, 26 May 2026 21:17:07 +0400 Subject: [PATCH] fix: emit ToolExecutionCompleted/Failed/Rejected events after tool dispatch ToolInvocationRequestedEvent was emitted but the corresponding outcome events were never emitted, so the TUI permanently showed tools as RUNNING. Now the orchestrator closes every tool invocation with the appropriate event: - ToolExecutionCompletedEvent (Success path) - ToolExecutionFailedEvent (Failure path) - ToolExecutionRejectedEvent (approval denied path) --- .../orchestration/SessionOrchestrator.kt | 40 +++++++++++++++++-- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt index 2c2d3c4f..593cab7f 100644 --- a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt +++ b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt @@ -29,7 +29,11 @@ import com.correx.core.events.events.NewEvent import com.correx.core.events.events.OrchestrationPausedEvent import com.correx.core.events.events.OrchestrationResumedEvent import com.correx.core.events.events.RiskAssessedEvent +import com.correx.core.events.events.ToolExecutionCompletedEvent +import com.correx.core.events.events.ToolExecutionFailedEvent +import com.correx.core.events.events.ToolExecutionRejectedEvent import com.correx.core.events.events.ToolInvocationRequestedEvent +import com.correx.core.events.events.ToolReceipt import com.correx.core.events.events.ToolRequest import com.correx.core.events.events.TransitionExecutedEvent import com.correx.core.events.events.WorkflowCompletedEvent @@ -371,6 +375,14 @@ abstract class SessionOrchestrator( } emitDecisionResolved(sessionId, domainRequest, decision) if (!decision.isApproved) { + val rejectReason = decision.reason ?: "approval denied" + emit(sessionId, ToolExecutionRejectedEvent( + invocationId = invocationId, + sessionId = sessionId, + toolName = toolCall.function.name, + tier = tier, + reason = rejectReason, + )) emit(sessionId, OrchestrationResumedEvent(sessionId, stageId)) val sourceId = toolCall.id ?: invocationId.value val assistantEntry = ContextEntry( @@ -381,14 +393,13 @@ abstract class SessionOrchestrator( content = Json.encodeToString(ToolCallRequest.serializer(), toolCall), tokenEstimate = toolCall.function.arguments.length / 4, ) - val reason = decision.reason ?: "approval denied" val resultEntry = ContextEntry( id = ContextEntryId(UUID.randomUUID().toString()), layer = ContextLayer.L2, sourceType = "toolResult", sourceId = sourceId, - content = "ERROR: $reason", - tokenEstimate = reason.length / 4, + content = "ERROR: $rejectReason", + tokenEstimate = rejectReason.length / 4, ) return@flatMap listOf(assistantEntry, resultEntry) } @@ -421,6 +432,29 @@ abstract class SessionOrchestrator( artifactContentCache["${sessionId.value}:${slot.name.value}"] = jsonContent } + when (result) { + is ToolResult.Success -> emit(sessionId, ToolExecutionCompletedEvent( + invocationId = invocationId, + sessionId = sessionId, + toolName = toolCall.function.name, + receipt = ToolReceipt( + invocationId = invocationId, + toolName = toolCall.function.name, + exitCode = result.exitCode, + outputSummary = result.output.take(500), + durationMs = 0, + tier = tier, + timestamp = Clock.System.now(), + ), + )) + is ToolResult.Failure -> emit(sessionId, ToolExecutionFailedEvent( + invocationId = invocationId, + sessionId = sessionId, + toolName = toolCall.function.name, + reason = result.reason, + )) + } + val sourceId = toolCall.id ?: invocationId.value val assistantEntry = ContextEntry( id = ContextEntryId(UUID.randomUUID().toString()),