docs/references/ai/tool-approval.md
Main is the single writer of approval state. The renderer surfaces an
approval-requested ToolUIPart, takes the user's decision, and posts it
to Main. Main applies the decision to the DB-authoritative anchor parts,
persists, and resumes the stream.
Tool needs approval — at execute time, the wrapper checks
tool.needsApproval and the assistant's auto-approve policy. If
approval is required, the wrapper writes an approval-requested part
and resolves the tool's promise into a held state (Claude-Agent: holds
canUseTool; MCP: stream pauses on the approval part).
Stream pauses — AiStreamManager transitions the topic to
awaiting-approval. The topic.stream.statuses.<topicId> shared-cache
entry carries the status; every renderer window reading that key sees
the pause atomically.
User decides — the approval card renders from the part. On click,
useToolApprovalBridge (src/renderer/hooks/useToolApprovalBridge.ts)
calls window.api.ai.toolApproval.respond(...) with approvalId,
approved, optional reason / updatedInput, topicId, anchorId.
Main applies — AiService's Ai_ToolApproval_Respond handler
branches on transport before touching the DB:
AiService.ts:191-197): hands the
decision to AgentSessionRuntimeService.respondToolApproval, which
resolves the live canUseTool promise so the existing stream
proceeds. When a live registry entry handles it, the handler
early-returns — no DB read happens (and topicId / anchorId
are not required).topicId + anchorId): reads the anchor message's current parts
from DB, applies the decision, and writes only when the target
approval-requested part is present on the DB row — guarding the
overlay-only case (approval received before the part has persisted).
When all approvals on the turn are decided it dispatches a synthetic
continue-conversation request through dispatchStreamRequest; the
provider applies the decision when it reads parts.Awaiting-approval clears — the moment the continue stream
broadcasts pending, the shared-cache entry flips back. Every window
sees the approval card disappear in the same tick.
useToolApproval (src/renderer/pages/home/Messages/Tools/hooks/useToolApproval.ts)
exposes an autoApprove action only for MCP tools — when an mcpTool
descriptor is passed. It persists the opt-out by PATCHing the server's
disabledAutoApproveTools, so the MCP settings page reflects it and
subsequent calls of that tool skip the approval card. There is no generic
per-tool default for non-MCP (e.g. Claude-Agent) tools.
awaiting-approval
status is the single source of truth for "this topic is paused".approval-requested part via overlay before it lands in the DB row.
Writing unconditionally would clobber the (concurrent) Main-side
persistence; the conditional write + continue-dispatch covers that case.src/main/ai/AiService.ts (Ai_ToolApproval_Respond)src/renderer/hooks/useToolApprovalBridge.tssrc/renderer/pages/home/Messages/Tools/hooks/useToolApproval.ts