attachApproval function

void attachApproval(
  1. Agent agent,
  2. ApprovalManager manager
)

Composes the approval gate for manager onto agent, preserving any BeforeToolCallHook already registered (approval runs first).

A denied call becomes an error tool result carrying the denial reason, so the model sees the refusal and can adapt (pi's blocked-tool semantics).

Implementation

void attachApproval(Agent agent, ApprovalManager manager) {
  final existing = agent.beforeToolCall;
  agent.beforeToolCall = (hookContext, cancelToken) async {
    final outcome = await manager.authorize(
      toolName: hookContext.toolCall.name,
      tier: _tierFor(hookContext.context, hookContext.toolCall.name),
      arguments: hookContext.toolCall.arguments,
    );
    if (!outcome.allowed) {
      return BeforeToolCallResult(
        block: true,
        reason: outcome.reason ?? 'Tool call denied by approval policy',
      );
    }
    return existing?.call(hookContext, cancelToken);
  };
}