Add client-side user setting for agent commit attribution (#9323)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
# APP-3585: Setting for agent commit and PR attribution
|
||||
Linear: https://linear.app/warpdotdev/issue/APP-3585
|
||||
## Summary
|
||||
Oz agent currently always adds a `Co-Authored-By: Oz <oz-agent@warp.dev>` attribution line to commit messages and pull request descriptions it creates. This behavior is gated by a binary team-level setting managed by admins. This feature upgrades that gate to a two-level setting: admins choose Yes / No / Respect User Choice, and individual users can control the setting when their team has not forced it.
|
||||
## Problem
|
||||
Teams and individual users may not want Oz's name in their git history or PR descriptions. Currently, admins can only turn attribution on or off for the entire team. There is no per-user opt-out, and users who want to control the behavior independently cannot do so unless their admin acts first.
|
||||
## Goals
|
||||
- Add a user-level toggle in the AI settings page for "Enable agent attribution."
|
||||
- Give team admins a three-way choice (Yes / No / Respect User Choice) rather than a binary toggle.
|
||||
- When a team has forced the setting on or off, reflect that in the user's UI and prevent the user from overriding it.
|
||||
- Gate the attribution instructions in the Oz agent prompt on the effective combined setting.
|
||||
## Non-goals
|
||||
- Changing the content or format of the attribution line itself.
|
||||
- Controlling attribution granularly per-repo or per-project.
|
||||
- Any changes to attribution on shared agent runs.
|
||||
## Figma / design references
|
||||
Figma: none provided. The toggle follows the existing pattern used for "Store AI conversations in the cloud" and "Codebase Context."
|
||||
## User experience
|
||||
### AI settings page — user toggle
|
||||
- A new widget appears in the AI settings page under the **Oz** section, in its own row.
|
||||
- Label: **"Enable agent attribution"**
|
||||
- Description: **"Oz can add attribution to commit messages and pull requests it creates"**
|
||||
- Rendered as a standard boolean toggle switch.
|
||||
**Default behavior:**
|
||||
- When the user has no team, or their team's setting is "Respect User Choice," the toggle defaults to **on** (attribution enabled).
|
||||
|
||||
**Team-forced-on:**
|
||||
- When the admin panel setting is "Yes" (force-enable), the toggle is shown in the **checked / on** state and is **non-interactive** (grayed out).
|
||||
- A tooltip on the disabled toggle reads: **"This option is enforced by your organization's settings and cannot be customized."** (the standard AI-page wording shared with other AI settings such as Computer use in Cloud Agents).
|
||||
**Team-forced-off:**
|
||||
- When the admin panel setting is "No" (force-disable), the toggle is shown in the **unchecked / off** state and is **non-interactive**.
|
||||
- Same tooltip as above.
|
||||
|
||||
**Team respects user choice:**
|
||||
- When the admin panel setting is "Respect User Choice," the toggle is **interactive** and reflects the user's stored preference (default: on).
|
||||
- Toggling it updates the user's preference immediately and persists across sessions.
|
||||
|
||||
### Admin panel — team-level setting
|
||||
- The existing binary (on/off) control for agent commit attribution in the admin panel is replaced with a three-way selector: **Yes / No / Respect User Choice**.
|
||||
- Default: **Respect User Choice** (matching the current behavior for teams that have not set this explicitly).
|
||||
|
||||
### Agent prompt
|
||||
- Attribution instructions are only included in the Oz agent prompt when the **effective** setting resolves to **on**:
|
||||
- Team = Yes → always on (attribution instructions included).
|
||||
- Team = No → always off (attribution instructions excluded).
|
||||
- Team = Respect User Choice → on if user's preference is on, off if user's preference is off.
|
||||
## Success criteria
|
||||
- The "Enable agent attribution" toggle appears in the AI settings page under the Oz section.
|
||||
- When no team or team setting is "Respect User Choice": the toggle is interactive, defaults to checked, and the user can turn it off.
|
||||
- When team setting is "Yes": the toggle is visible, checked, and non-interactive with the managed-by-org tooltip.
|
||||
- When team setting is "No": the toggle is visible, unchecked, and non-interactive with the managed-by-org tooltip.
|
||||
- A commit or PR created by Oz **contains** the attribution line when the effective setting is on.
|
||||
- A commit or PR created by Oz **does not contain** the attribution line when the effective setting is off.
|
||||
- The user's preference persists across app restarts and across devices (cloud-synced).
|
||||
- The admin panel shows a three-way selector (Yes / No / Respect User Choice) for team attribution.
|
||||
## Validation
|
||||
- Manual: verify toggle appears in Settings > AI under the Oz section with the correct label and description.
|
||||
- Manual: verify toggle is locked (with tooltip) when team setting is Yes or No.
|
||||
- Manual: verify toggling the setting off and creating a commit/PR via Oz does not include the attribution line.
|
||||
- Manual: verify toggling the setting on and creating a commit/PR via Oz includes the attribution line.
|
||||
- Manual: verify user preference persists after restarting the app.
|
||||
- Manual: verify admin panel shows the three-way selector, and that each team-level option is correctly reflected in the user's toggle state.
|
||||
## Open questions
|
||||
- None at this time.
|
||||
@@ -0,0 +1,92 @@
|
||||
# APP-3585: Setting for agent commit and PR attribution — Client Tech Spec
|
||||
Product spec: `specs/APP-3585/PRODUCT.md`.
|
||||
|
||||
Server tech spec: `warp-server/specs/APP-3585/TECH.md` (PR https://github.com/warpdotdev/warp-server/pull/10476).
|
||||
## Problem
|
||||
The attribution setting (whether Oz adds a `Co-Authored-By` line to commits and PRs) is currently a binary team-level flag. We need to turn it into a two-level setting: team admins get a three-way `AdminEnablementSetting` (Enable / Disable / RespectUserSetting), and individual users get their own boolean preference that takes effect when the team delegates.
|
||||
|
||||
This spec covers the warp-internal (client) changes. Anything about how the server stores or resolves the effective value lives in the server spec.
|
||||
## Relevant code
|
||||
- `app/src/settings/ai.rs` — `define_settings_group!(AISettings, ...)`: new user-level `agent_attribution_enabled` bool.
|
||||
- `app/src/workspaces/workspace.rs (631–776)` — domain types for team settings (`AdminEnablementSetting`, `WorkspaceSettings`); flat `enable_warp_attribution: AdminEnablementSetting` added directly on `WorkspaceSettings`.
|
||||
- `app/src/workspaces/user_workspaces.rs (1363–1458)` — accessor methods for team settings; new `get_agent_attribution_setting()`.
|
||||
- `app/src/workspaces/gql_convert.rs` — `From<GqlAdminEnablementSetting>` mapping for the new field.
|
||||
- `crates/graphql/src/api/workspace.rs (124–146)` — `WorkspaceSettings` cynic fragment; new `AmbientAgentSettings` fragment.
|
||||
- `app/src/settings_view/ai_page.rs` — new `AgentAttributionWidget`; follows `CloudConversationStorageWidget` in `privacy_page.rs (1667–1770)` for team-override rendering.
|
||||
- `app/src/settings/cloud_preferences_syncer.rs` — existing syncer that publishes any `SyncToCloud::Globally(...)` setting to warp-server as a `JsonPreference` GSO. No changes required — reused as the user-preference transport.
|
||||
## Current state
|
||||
`AmbientAgentSettings` on the server exposes `enableWarpAttribution: Boolean!`; the client does not fetch it. There is no user-level attribution preference, and attribution instructions are unconditionally present or absent in the agent prompt based solely on the server-side team boolean.
|
||||
|
||||
The client already has the transport we need for a user-level preference: any setting declared with `SyncToCloud::Globally(...)` via `define_settings_group!` is uploaded to warp-server by `CloudPreferencesSyncer` as a `JsonPreference` GSO with `unique_key = Global_<StorageKey>` (scoped per user). Other `AISettings` fields (e.g. `IncludeAgentCommandsInHistory`, `ShowConversationHistory`) already use this pattern, and warp-server reads them back by GSO lookup.
|
||||
## Proposed changes
|
||||
### 1. New user-level client setting
|
||||
Add `agent_attribution_enabled` to `define_settings_group!(AISettings, ...)` in `app/src/settings/ai.rs`:
|
||||
|
||||
- default `true`
|
||||
- `SyncToCloud::Globally(RespectUserSyncSetting::Yes)` (AI/coding preference, not a privacy/safety one)
|
||||
- implicit storage key `AgentAttributionEnabled`
|
||||
|
||||
On toggle, the setting is persisted locally and the existing `CloudPreferencesSyncer` upserts the user's `JsonPreference` GSO (`unique_key = {firebaseUID}_Global_AgentAttributionEnabled` on the server side). No bespoke server call. See the server spec for the read path.
|
||||
|
||||
Because we use `RespectUserSyncSetting::Yes`, users who have turned "Sync settings across devices" off do not publish their preference to the server; the server falls back to the default (`true`) for those users. This matches how all other `AISettings` fields behave today.
|
||||
|
||||
### 2. Team-level field on `WorkspaceSettings` (flat, not wrapped)
|
||||
Add a flat `enable_warp_attribution: AdminEnablementSetting` field (with `#[serde(default)]`) directly on `WorkspaceSettings` in `app/src/workspaces/workspace.rs`. No wrapper struct — the GQL field name matches the server directly.
|
||||
|
||||
`UserWorkspaces::get_agent_attribution_setting()` reads this field off the current team:
|
||||
|
||||
```rust
|
||||
pub fn get_agent_attribution_setting(&self) -> AdminEnablementSetting {
|
||||
self.current_team()
|
||||
.map(|team| team.organization_settings.enable_warp_attribution.clone())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
```
|
||||
|
||||
This matches the shape of every other admin-enablement accessor on `UserWorkspaces` (`team_allows_codebase_context`, `is_ai_allowed_in_remote_sessions`, `get_cloud_conversation_storage_enablement_setting`).
|
||||
|
||||
### 3. GraphQL fetch
|
||||
Add an `AmbientAgentSettings` cynic fragment in `crates/graphql/src/api/workspace.rs` with `enable_warp_attribution: AdminEnablementSetting`, and include it on the `WorkspaceSettings` fragment as `ambient_agent_settings: Option<AmbientAgentSettings>`. The wrapper is `Option` because `WorkspaceSettings.ambientAgentSettings` is nullable in the GQL schema (no `!`); this matches the existing convention for `sandboxed_agent_settings`. When the wrapper is `None`, `gql_convert.rs` falls back to `AdminEnablementSetting::default()` (= `RespectUserSetting`). The inner `enable_warp_attribution` is non-optional, as the server guarantees a value when the wrapper is present.
|
||||
### 4. AI settings page widget
|
||||
Add `AgentAttributionWidget` in `app/src/settings_view/ai_page.rs`. It reads `UserWorkspaces::get_agent_attribution_setting()` and derives the toggle state:
|
||||
- `Enable` → locked on
|
||||
- `Disable` → locked off
|
||||
- `RespectUserSetting` → interactive, reflects `AISettings.agent_attribution_enabled`
|
||||
Included in both the legacy full-page (`None` subpage) and the `Some(AISubpage::WarpAgent)` subpage. The widget always renders; when AI is globally disabled, the toggle is shown disabled with a greyed-out label and description, matching the behavior of every other widget on the AI page (e.g. `CloudAgentComputerUseWidget`, `CLIAgentWidget`).
|
||||
Visual structure follows the AI-page convention: a `render_separator` and a `build_sub_header("Agent Attribution", ...)` precede the toggle row and description, mirroring `CloudAgentComputerUseWidget`. The cited `CloudConversationStorageWidget` reference applies only to the team-override state machine (locked vs. interactive based on `AdminEnablementSetting`), not to the visual structure, since that widget lives on the Privacy page which has different conventions.
|
||||
For the locked-toggle tooltip, the widget uses `WORKSPACE_OVERRIDE_TOOLTIP_MESSAGE` from `app/src/ai/execution_profiles/editor/ui_helpers.rs` ("This option is enforced by your organization's settings and cannot be customized."), matching `CloudAgentComputerUseWidget` and the rest of the AI page. The Privacy page uses a shorter "This setting is managed by your organization." string; we deliberately follow the AI-page wording here for consistency.
|
||||
|
||||
## End-to-end flow (user toggles attribution off)
|
||||
1. User opens Settings → AI → Oz.
|
||||
2. Client reads the team's `enable_warp_attribution` from cached `WorkspaceSettings` via `get_agent_attribution_setting()`.
|
||||
3. Setting is `RespectUserSetting`, so the toggle is interactive and reflects `AISettings.agent_attribution_enabled` (default `true`, showing as checked).
|
||||
4. User clicks the toggle → `ToggleAgentAttribution` action sets `AISettings.agent_attribution_enabled` to `false` (persisted locally).
|
||||
5. `CloudPreferencesSyncer` picks up the change and upserts the user's `JsonPreference` GSO (`unique_key = {firebaseUID}_Global_AgentAttributionEnabled`, `serialized_model.value = false`).
|
||||
6. Next Oz run: warp-server resolves the effective setting (team = RespectUserSetting → read user GSO = false) and excludes attribution instructions from the prompt. Oz creates a commit without the `Co-Authored-By` line.
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[User toggles off] --> B[AISettings.agent_attribution_enabled = false]
|
||||
B --> C[CloudPreferencesSyncer upsert]
|
||||
C --> D[(warp-server generic_string_objects\nunique_key = firebaseUID_Global_AgentAttributionEnabled)]
|
||||
D --> E[Next Oz run]
|
||||
E --> F{Team setting}
|
||||
F -->|Enable| G[Attribution included]
|
||||
F -->|Disable| H[Attribution excluded]
|
||||
F -->|RespectUserSetting| I[Read user GSO = false]
|
||||
I --> H
|
||||
```
|
||||
## Risks and mitigations
|
||||
- **Client ships ahead of server**: the cynic fragment requires `enable_warp_attribution: AdminEnablementSetting!` on the inner `AmbientAgentSettings` type; if the client lands first, `GetWorkspaceSettings` fails to parse. Mitigation: sequence the server change first (covered by the server spec). The outer `Option<AmbientAgentSettings>` wrapper already absorbs the case where the whole sub-object is missing.
|
||||
- **User has settings sync off**: their preference never reaches the server; the server defaults to `true`. This is the documented behavior for any `RespectUserSyncSetting::Yes` setting.
|
||||
- **Widget enablement**: when AI is globally disabled, the widget renders in a disabled state (greyed-out toggle, label, and description), consistent with every other widget on the AI page.
|
||||
## Testing and validation
|
||||
- Unit tests in `app/src/workspaces/user_workspaces_tests.rs`:
|
||||
- `test_agent_attribution_default_with_no_workspace` — no workspace → `RespectUserSetting`
|
||||
- `test_agent_attribution_forced_on_by_team` — team `Enable` → accessor returns `Enable`
|
||||
- `test_agent_attribution_forced_off_by_team` — team `Disable` → accessor returns `Disable`
|
||||
- `test_agent_attribution_respects_user_setting` — team `RespectUserSetting` → accessor returns `RespectUserSetting`
|
||||
- UI manual test: verify each team-setting state renders the toggle in the correct locked/interactive state, in both the legacy and Oz subpages.
|
||||
- End-to-end (once the server branch merges): run an Oz agent task that creates a commit with the setting off → verify no attribution line. Run with team-level `Enable` and verify the client toggle is locked on; same for `Disable`.
|
||||
## Follow-ups
|
||||
- Admin-panel UI (separate warp-server PR) to change the team-level control from a binary toggle to a three-way selector.
|
||||
Reference in New Issue
Block a user