internal/approvalgate/harness_permission.go
Rama main · réplica de lectura del repositorio original.
package approvalgate
import (
"context"
"encoding/json"
"log/slog"
"strings"
acp "github.com/coder/acp-go-sdk"
"github.com/yeomyeonggeori/blueclaw/internal/mcpserver"
"github.com/yeomyeonggeori/blueprotocol/agentcontract"
"github.com/yeomyeonggeori/blueprotocol/holdrecord"
)
const harnessPermissionSource = "harness_permission"
type HarnessPermissionQuestion struct {
Text string
ToolCall acp.ToolCallUpdate
Options []acp.PermissionOption
}
type HarnessPermissionAsker interface {
AskHarnessPermission(ctx context.Context, approvalRequest mcpserver.ApprovalRequest, question HarnessPermissionQuestion) (acp.RequestPermissionOutcome, AskStatus)
}
func (gate *Gate) AskHarnessPermission(ctx context.Context, approvalRequest mcpserver.ApprovalRequest, question HarnessPermissionQuestion) (acp.RequestPermissionOutcome, AskStatus) {
taskRunID := strings.TrimSpace(approvalRequest.TaskRunID)
heldCall := heldCallOfHarnessQuestion(question)
if outcome, isAnswered := gate.knownHarnessAnswer(taskRunID, approvalRequest, heldCall, question.Options); isAnswered {
return outcome, AskAnswered
}
harnessAsker, canAskHarnessPermission := gate.permissionAsker.(HarnessPermissionAsker)
if !canAskHarnessPermission {
return acp.RequestPermissionOutcome{}, AskUnreachable
}
if taskRunID == "" {
return harnessAsker.AskHarnessPermission(ctx, approvalRequest, question)
}
profileName := gate.currentAgentProfileName(taskRunID)
if _, errorValue := gate.taskRunService.PauseTaskRun(taskRunID, agentcontract.TaskStatusWaitingApproval, question.Text); errorValue != nil {
slog.Warn("approvalgate.harness_permission_is_unanswerable", "taskRunID", taskRunID, "reason", errorValue.Error())
return acp.RequestPermissionOutcome{}, AskUnreachable
}
hold := holdrecord.Open(gate.taskRunService, taskRunID, heldCall, nil)
outcome, status := harnessAsker.AskHarnessPermission(ctx, approvalRequest, question)
switch status {
case AskAnswered:
gate.settleHarnessHold(taskRunID, hold.ID, question.Options, outcome)
case AskUnreachable, AskExpired:
gate.endUnansweredHold(taskRunID, profileName, status)
return acp.RequestPermissionOutcome{}, status
default:
return acp.RequestPermissionOutcome{}, status
}
if _, errorValue := gate.taskRunService.AdvanceTaskRun(taskRunID, profileName); errorValue != nil {
slog.Warn("approvalgate.answered_run_will_not_advance", "taskRunID", taskRunID, "reason", errorValue.Error())
}
return outcome, AskAnswered
}
func (gate *Gate) knownHarnessAnswer(taskRunID string, approvalRequest mcpserver.ApprovalRequest, heldCall agentcontract.HeldCall, options []acp.PermissionOption) (acp.RequestPermissionOutcome, bool) {
if taskRunID == "" {
return acp.RequestPermissionOutcome{}, false
}
if outcome, isApproved := gate.spendApprovedHarnessCall(taskRunID, heldCall, options); isApproved {
return outcome, true
}
if !gate.isAnsweredByItsSchedule(approvalRequest) {
return acp.RequestPermissionOutcome{}, false
}
return gate.harnessAllowedByItsSchedule(taskRunID, heldCall, options)
}
func heldCallOfHarnessQuestion(question HarnessPermissionQuestion) agentcontract.HeldCall {
heldCall := agentcontract.HeldCall{Confirmation: question.Text}
if question.ToolCall.RawInput != nil {
heldCall.ToolInput, _ = json.Marshal(question.ToolCall.RawInput)
}
return heldCall
}
func (gate *Gate) spendApprovedHarnessCall(taskRunID string, heldCall agentcontract.HeldCall, options []acp.PermissionOption) (acp.RequestPermissionOutcome, bool) {
allowOnce, isOffered := optionOfKind(options, acp.PermissionOptionKindAllowOnce)
if !isOffered {
return acp.RequestPermissionOutcome{}, false
}
if _, isSpent := holdrecord.SpendApprovedCall(gate.taskRunService, taskRunID, heldCall.ToolName, heldCall.ToolInput); !isSpent {
return acp.RequestPermissionOutcome{}, false
}
return acp.RequestPermissionOutcome{Selected: &acp.RequestPermissionOutcomeSelected{Outcome: "selected", OptionId: allowOnce.OptionId}}, true
}
func (gate *Gate) settleHarnessHold(taskRunID string, holdID string, options []acp.PermissionOption, outcome acp.RequestPermissionOutcome) {
if outcome.Selected == nil || !isAllowOption(options, outcome.Selected.OptionId) {
holdrecord.Decide(gate.taskRunService, taskRunID, holdID, holdrecord.DecisionReject, harnessPermissionSource)
return
}
holdrecord.Decide(gate.taskRunService, taskRunID, holdID, holdrecord.DecisionApprove, harnessPermissionSource)
}
func optionOfKind(options []acp.PermissionOption, kind acp.PermissionOptionKind) (acp.PermissionOption, bool) {
for _, permissionOption := range options {
if permissionOption.Kind == kind {
return permissionOption, true
}
}
return acp.PermissionOption{}, false
}
func isAllowOption(options []acp.PermissionOption, optionID acp.PermissionOptionId) bool {
for _, permissionOption := range options {
if permissionOption.OptionId == optionID {
return permissionOption.Kind == acp.PermissionOptionKindAllowOnce || permissionOption.Kind == acp.PermissionOptionKindAllowAlways
}
}
return false
}