feat(core): optional plugin gate in PermissionV2 for allow→ask - #34329
feat(core): optional plugin gate in PermissionV2 for allow→ask#34329thomaslwang wants to merge 2 commits into
Conversation
Add an optional Gate service that PermissionV2 consults (via Effect.serviceOption, so it stays a no-op with no hard dependency) right after a permission effect resolves. A provider can downgrade a would-auto-approve `allow` to `ask`/`deny`, or upgrade an `ask` to `allow`. Hard `deny` rules are not routed through it. This is the consultation point requested in anomalyco#34327; it lets a guardrails plugin turn a would-auto-approve tool call into an interactive prompt — something tool.execute.before cannot do (it can only throw to deny). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@jlongster gentle nudge on this one (you're assigned to the tracking issue #34327) — no rush. Quick recap of the ask: this adds an optional Context for why it's useful: it's the one interception State: mergeable, checks green, conventional title + compliance template in place. Happy to adjust the seam (naming, where it's consulted, the provider contract) if you'd prefer a different shape. |
|
Thanks for this — this is exactly the hook point I've been looking for. I discovered the same gap while exploring how to build a plugin that auto-approves external_directory requests when the target is a git-managed repo (and injects git-commit reminders afterwards). #7006 describes the V1 path issue, but the V2 path was the real dead end. One architectural suggestion: would it simplify the wiring if Gate were defined as a global-scoped service rather than location-scoped? Effect.serviceOption resolves upward through layers, so a Gate provided at the global level (in opencode's server.ts) would be visible inside evaluateInput without needing to touch the locationServices monolith or wrestle with the hoist/compile machinery. This also cleanly separates concerns: core defines the contract, opencode provides the plugin-backed implementation. Once this merges, I'd be happy to submit the follow-up PR wiring the permission.ask plugin hook through Gate. Happy to coordinate or defer to whatever approach the maintainers prefer. |
Issue for this PR
Implements the consultation point requested in #34327.
Type of change
What does this PR do?
PermissionV2is where every tool (bash, edit, webfetch, read, …) resolves apermission to
allow/ask/deny. A plugin today can only observe a tool callvia
tool.execute.before, which runs after permission resolution and can denyonly by throwing — there's no way for a plugin to turn a would-auto-approve
call into an interactive prompt.
This adds an optional
Gateservice thatevaluateInputconsults right after theeffect resolves (and only on the non-hard-deny path). A provider can downgrade an
allowtoask/deny, or upgrade anasktoallow. It's read viaEffect.serviceOption, soPermissionV2keeps no hard dependency on it andthe behavior is unchanged when nothing provides a
Gate(the common case). Theservice is defined in core and consumed in core; a host wires a provider.
Scope note: this is intentionally just the core hook point. Wiring a concrete
provider — e.g. one backed by the already-declared
permission.askplugin hook —into
PermissionV2's location-scoped runtime touches the location-servicecomposition, so I've left that out for maintainer guidance rather than guess at
the layering. Happy to follow up with the provider once the approach is agreed.
Motivation: I maintain an external OGR guardrails plugin that evaluates tool calls
against agent-configured rules; today it can only block (via
tool.execute.before)and a first-class
askfrom a plugin would let it request human confirmation.How did you verify your code works?
tsgo --noEmitis clean for@opencode-ai/core. The change is additive andno-op without a registered
Gate(verified theserviceOptionpath leaves theexisting effect untouched). No existing behavior changes.
Screenshots / recordings
N/A — no UI changes.
Checklist