feat(desktop): permission policy config + actionable Allow/Deny card (#4938) - #5107
Closed
wpfleger96 wants to merge 2 commits into
Closed
feat(desktop): permission policy config + actionable Allow/Deny card (#4938)#5107wpfleger96 wants to merge 2 commits into
wpfleger96 wants to merge 2 commits into
Conversation
…4938) Add per-agent and fleet-wide permission policy configuration with an actionable Allow/Deny card for the ask policy. **Rust (desktop/src-tauri)** - Add `permission_policy` module: `PermissionPolicy` enum (ask | allow | reject, lowercase serde), `PermissionPolicySource` (agent | global_default | built_in), and `resolve_effective_permission_policy` (precedence: per-agent > global > built-in ask) - Add `permission_policy: Option<PermissionPolicy>` to `ManagedAgentRecord` (per-agent override) and `GlobalAgentConfig` (fleet default) - Inject resolved policy as `BUZZ_ACP_PERMISSION_POLICY` env var at spawn; add to `RESERVED_ENV_KEYS` so users cannot override via env-vars UI - Include `permission_policy` in `SpawnSnapshot` / restart-diff so edits surface in the existing `needsRestart` flow - Expose `permission_policy` + `permission_policy_source` on `ManagedAgentSummary` (resolved values) - Extend `UpdateManagedAgentRequest` with double-Option `permission_policy` (None = unchanged, Some(None) = clear, Some(Some(v)) = set); reject edits to remotely deployed agents with a clear error message - Add remote-deployed agent path in `agents_deploy.rs`: read per-record policy, fall back to desktop default, inject into `policy_env` **TypeScript (desktop/src)** - `PermissionPolicy = "ask" | "allow" | "reject"` and `PermissionPolicySource = "agent" | "global_default" | "built_in"` in `types.ts`; add to `ManagedAgent`, `CreateManagedAgentInput`, and `UpdateManagedAgentInput` (null = clear per-agent override) - `tauri.ts`: add `permission_policy` / `permission_policy_source` to `RawManagedAgent` with safe defaults; map in `fromRawManagedAgent` - `agentSessionTypes.ts`: add `authorization?: { requestNonce, actionable, reason? }` to `ObserverEvent`; extend `lifecycle` `TranscriptItem` with `requestNonce`, `actionable`, `authorizationReason`, `options` - `agentSessionTranscript.ts`: add `pendingPermissionsByNonce` map; parse `authorization` envelope from `session/request_permission` events; handle `control_result/permission_decision` to retire cards on terminal outcomes, including the pinned uncertain message - `agentControl.ts`: add `sendPermissionDecision(pubkey, nonce, optionId)` fire-and-forget control API - `LifecycleActivity.tsx`: `PermissionDecisionButtons` component renders per-option buttons styled by kind (reject_* = destructive); local pending state with retry on error; rendered when `actionable && !outcome` - `AgentInstanceEditDialog.tsx`: permission policy select (Inherit / Ask / Allow / Reject) for local agents; read-only for remote-deployed agents with a shutdown+redeploy hint; shows effective value and source Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
…utcome Per interface note from Paul (2026-08-06): control_result statuses (sent | no_active_turn | channel_full | channel_closed | no_channel) confirm whether the permission_decision click was delivered to the harness, not whether the permission was applied/denied. Terminal outcomes arrive as enveloped acp_write frames correlated by requestNonce. The card retirement matrix will be wired once Thufir's review of Duncan's buzz-acp contract lands and NIP-AO is pinned. Updated the control_result handler to preserve card actionability on delivery — the PermissionDecisionButtons component already handles button-level pending-state reset via its own catch handler if the fire-and-forget send fails. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
Member
Author
|
Consolidated into #5106 per Will's request that this ships as one PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements the desktop side of #4938 — permission policy configuration and the actionable Allow/Deny card for ACP
session/request_permissionrequests.Config
Rust structs
permission_policymodule:PermissionPolicyenum (ask|allow|reject, lowercase serde),PermissionPolicySource(agent|global_default|built_in), andresolve_effective_permission_policy(precedence: per-agent > global > built-inask).ManagedAgentRecord.permission_policy: Option<PermissionPolicy>— per-agent override,None= inherit.GlobalAgentConfig.permission_policy: Option<PermissionPolicy>— fleet default, follows the existing spawn-time read + picked-up-on-next-restart pattern.BUZZ_ACP_PERMISSION_POLICYat spawn; key added toRESERVED_ENV_KEYS.SpawnSnapshotincludespermission_policy; a per-agent edit surfaces in the existingneedsRestartflow.ManagedAgentSummaryexposes the resolvedpermission_policy+permission_policy_source.UpdateManagedAgentRequestuses double-Option (None= unchanged,Some(None)= clear,Some(Some(v))= set); server rejects edits to remotely deployed agents.policy_env, read-only while deployed.TypeScript types
PermissionPolicy = "ask" | "allow" | "reject"andPermissionPolicySource = "agent" | "global_default" | "built_in"intypes.ts.ManagedAgentgainspermissionPolicy+permissionPolicySource(resolved values).CreateManagedAgentInputandUpdateManagedAgentInputget optionalpermissionPolicy(null= clear).RawManagedAgentmapped intauri.tswith safe defaults.Actionable card
ObserverEvent.authorization?: { requestNonce, actionable, reason? }added toagentSessionTypes.ts.agentSessionTranscript.ts:pendingPermissionsByNoncemap indexes actionable cards by nonce;authorizationenvelope fields attached to lifecycle items.control_result/permission_decisionis treated as a delivery confirmation only (statuses:sent | no_active_turn | channel_full | channel_closed | no_channel) — the card stays actionable on any non-sentstatus so the user can retry.acp_writeframes correlated byrequestNonceonce Duncan's contract is pinned in NIP-AO and Thufir's review lands.agentControl.ts:sendPermissionDecision(pubkey, nonce, optionId)— fire-and-forget;PermissionDecisionButtonsresets button pending-state on error.LifecycleActivity.tsx:PermissionDecisionButtonsrenders per-option buttons (reject_* = destructive styling); local pending state; resets on error so the user can retry. Shown whenactionable && !outcome.Settings UI
AgentInstanceEditDialog.tsx:Tests
All Rust and TS unit test suites pass (2249 Rust, 4392 TS).
File size ratchet
Several pre-existing large files grow by the minimum required to add new fields/logic. The ratchet CI step will flag these; the growth is intentional for this feature:
types.rsManagedAgentRecord+ManagedAgentSummaryagent_models.rspermission_policyinput fieldagents.rsdiscovery/tests.rs,readiness.rsAgentInstanceEditDialog.tsxagentSessionTranscript.tstauri.tsRawManagedAgentmappingtypes.tsNew file:
permission_policy.rs(82 lines) — extracted to keeptypes.rsas close to the limit as possible.