Ctrl K
ComunidadGuías…

internal/approvalgate/harness_permission.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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
}