fix(kernel+infra): tool approval gate and executor cleanup
- SessionOrchestrator: wire T2/T3/T4 approval gate before tool execution; emit OrchestrationPausedEvent/ApprovalRequestedEvent, await decision, return rejection as ERROR context entry so LLM sees the denial; propagate tool ERROR entries as StageExecutionResult.Failure - SandboxedToolExecutor: remove dead code and simplify - InfrastructureModule: minor wiring cleanup - LlamaCppInferenceProvider / build.gradle: related build fixes
This commit is contained in:
+61
@@ -238,6 +238,10 @@ abstract class SessionOrchestrator(
|
||||
toolRounds < MAX_TOOL_ROUNDS
|
||||
) {
|
||||
val toolEntries = dispatchToolCalls(sessionId, stageId, inferenceResult.response.toolCalls)
|
||||
val firstError = toolEntries.firstOrNull { it.content.startsWith("ERROR:") }
|
||||
if (firstError != null) {
|
||||
return StageExecutionResult.Failure(firstError.content.removePrefix("ERROR: "), retryable = true)
|
||||
}
|
||||
val allEntries = currentContext.layers.values.flatten() + toolEntries
|
||||
currentContext = contextPackBuilder.build(
|
||||
id = ContextPackId(UUID.randomUUID().toString()),
|
||||
@@ -297,6 +301,63 @@ abstract class SessionOrchestrator(
|
||||
request = request,
|
||||
),
|
||||
)
|
||||
val requiresApproval = when (tier) {
|
||||
Tier.T0, Tier.T1 -> false
|
||||
Tier.T2, Tier.T3, Tier.T4 -> true
|
||||
}
|
||||
if (requiresApproval) {
|
||||
val requestId = ApprovalRequestId(UUID.randomUUID().toString())
|
||||
val domainRequest = DomainApprovalRequest(
|
||||
id = requestId,
|
||||
tier = tier,
|
||||
validationReportId = ValidationReportId(UUID.randomUUID().toString()),
|
||||
riskSummaryId = null,
|
||||
timestamp = Clock.System.now(),
|
||||
)
|
||||
emit(sessionId, OrchestrationPausedEvent(sessionId, stageId, "APPROVAL_PENDING"))
|
||||
emit(
|
||||
sessionId,
|
||||
ApprovalRequestedEvent(
|
||||
requestId = requestId,
|
||||
tier = tier,
|
||||
validationReportId = domainRequest.validationReportId,
|
||||
riskSummaryId = null,
|
||||
sessionId = sessionId,
|
||||
stageId = stageId,
|
||||
projectId = null,
|
||||
),
|
||||
)
|
||||
val deferred = CompletableDeferred<ApprovalDecision>()
|
||||
pendingApprovals[requestId] = deferred
|
||||
val decision = try {
|
||||
deferred.await()
|
||||
} finally {
|
||||
pendingApprovals.remove(requestId)
|
||||
}
|
||||
emitDecisionResolved(sessionId, domainRequest, decision)
|
||||
if (!decision.isApproved) {
|
||||
val sourceId = toolCall.id ?: invocationId.value
|
||||
val assistantEntry = ContextEntry(
|
||||
id = ContextEntryId(UUID.randomUUID().toString()),
|
||||
layer = ContextLayer.L2,
|
||||
sourceType = "assistantToolCall",
|
||||
sourceId = sourceId,
|
||||
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,
|
||||
)
|
||||
return@flatMap listOf(assistantEntry, resultEntry)
|
||||
}
|
||||
emit(sessionId, OrchestrationResumedEvent(sessionId, stageId))
|
||||
}
|
||||
val result = executor.execute(request)
|
||||
val sourceId = toolCall.id ?: invocationId.value
|
||||
val assistantEntry = ContextEntry(
|
||||
|
||||
Reference in New Issue
Block a user