fix: guard stuck-session fix against OrchestrationPaused/ApprovalRequested race

When a TUI client connects at the exact moment between an
OrchestrationPausedEvent being stored and its corresponding
ApprovalRequestedEvent being stored, the approval projector sees empty
pendingApprovalRequests while orchState.pendingApproval=true. The
stuck-session fix would then append a spurious OrchestrationResumedEvent,
clearing the TUI's pendingApproval display and hiding the approval dialog.

Fix: count OrchestrationPausedEvents vs ApprovalRequestedEvents in the
event log. If there is an unpaired pause (more pauses than requests),
the session just entered the gate — skip the fix entirely.

Also remove spurious ToolExecutionCompleted/Failed emissions from the
kernel orchestrator; those events are emitted by SandboxedToolExecutor
in the infrastructure layer. Keep ToolExecutionRejectedEvent which the
orchestrator does own (rejection happens before executor.execute is
called).
This commit is contained in:
2026-05-26 21:41:56 +04:00
parent 32d4cfa5dc
commit bedd6e020c
2 changed files with 10 additions and 27 deletions
@@ -29,11 +29,8 @@ 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
@@ -432,29 +429,6 @@ 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()),