attachApproval function
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);
};
}