internal/approvalgate/target.go
Rama main · réplica de lectura del repositorio original.
package approvalgate
import (
"context"
"encoding/json"
"log/slog"
"strings"
"github.com/yeomyeonggeori/blueclaw/internal/mcpserver"
"github.com/yeomyeonggeori/blueprotocol/agentcontract"
"github.com/yeomyeonggeori/blueprotocol/holdrecord"
"github.com/yeomyeonggeori/blueprotocol/toolcontract"
)
type ApprovalTarget = agentcontract.ApprovalTarget
type ApprovalTargetResolution struct {
Target ApprovalTarget
Choices []holdrecord.Choice
Failure toolcontract.ToolResult
}
type ApprovalTargetRequest struct {
ToolName string
ToolInput json.RawMessage
RequesterPersonID string
RequesterEmail string
Platform string
ConversationID string
ReplyTargetID string
Transport map[string]any
}
type ApprovalTargetResolver interface {
ResolveApprovalTarget(context.Context, ApprovalTargetRequest) (ApprovalTargetResolution, error)
}
func (gate *Gate) UseApprovalTargetResolver(approvalTargetResolver ApprovalTargetResolver) {
gate.approvalTargetResolver = approvalTargetResolver
}
func (resolution ApprovalTargetResolution) namesNothingThatExists() bool {
return !resolution.Target.IsResolved() && resolution.Failure.Failure != nil
}
func sendReachesOnlyTheRequester(approvalRequest mcpserver.ApprovalRequest, target ApprovalTarget) bool {
if strings.TrimSpace(approvalRequest.SideEffectClass) != toolcontract.ToolSideEffectExternalSend || !target.IsResolved() {
return false
}
for _, recipient := range append([]string{target.ID}, target.IDs...) {
if strings.TrimSpace(recipient) != "" && !namesTheRequester(approvalRequest, recipient) {
return false
}
}
return true
}
func namesTheRequester(approvalRequest mcpserver.ApprovalRequest, recipient string) bool {
trimmedRecipient := strings.TrimSpace(recipient)
requesterEmail := strings.TrimSpace(approvalRequest.RequesterEmail)
return trimmedRecipient == strings.TrimSpace(approvalRequest.RequesterPersonID) ||
(requesterEmail != "" && strings.EqualFold(trimmedRecipient, requesterEmail))
}
func (gate *Gate) resolveApprovalTarget(ctx context.Context, approvalRequest mcpserver.ApprovalRequest) ApprovalTargetResolution {
if target, isNamed := refusalTarget(ctx); isNamed {
return ApprovalTargetResolution{Target: target}
}
if gate.approvalTargetResolver == nil {
return ApprovalTargetResolution{}
}
transport, failure := CarriedWorkspaceFiles(ctx)
if failure != nil {
return ApprovalTargetResolution{Failure: *failure}
}
resolution, errorValue := gate.approvalTargetResolver.ResolveApprovalTarget(ctx, ApprovalTargetRequest{
ToolName: strings.TrimSpace(approvalRequest.ToolName),
ToolInput: approvalRequest.ToolInput,
RequesterPersonID: approvalRequest.RequesterPersonID,
RequesterEmail: approvalRequest.RequesterEmail,
Platform: approvalRequest.Platform,
ConversationID: approvalRequest.ConversationID,
ReplyTargetID: approvalRequest.ReplyTargetID,
Transport: transport,
})
if errorValue == nil {
return resolution
}
slog.Warn("approvalgate.target_resolution_unreachable",
"toolName", strings.TrimSpace(approvalRequest.ToolName),
"taskRunID", strings.TrimSpace(approvalRequest.TaskRunID),
"reason", errorValue.Error())
return ApprovalTargetResolution{}
}
func narrowedToolInput(toolInput json.RawMessage, target ApprovalTarget) json.RawMessage {
if !target.IsResolved() {
return nil
}
document := map[string]json.RawMessage{}
if json.Unmarshal(toolInput, &document) != nil {
return nil
}
identity, errorValue := json.Marshal(narrowedIdentity(target))
if errorValue != nil {
return nil
}
document[strings.TrimSpace(target.InputField)] = identity
narrowedInput, errorValue := json.Marshal(document)
if errorValue != nil {
return nil
}
return narrowedInput
}
func narrowedIdentity(target ApprovalTarget) any {
if len(target.IDs) > 0 {
return target.IDs
}
return strings.TrimSpace(target.ID)
}