Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# APP-4118: Toggle AI auto-gen + Enterprise gate for git operations
|
||||
Linear: [APP-4118](https://linear.app/warpdotdev/issue/APP-4118/toggle-ai-auto-gen-enterprise-gate)
|
||||
Parent: APP-3918 (git operations button) / APP-3071 (git operations in code review)
|
||||
## Summary
|
||||
Git-operation dialogs in the code review panel (Commit, Push, Create PR, and their chained variants) currently call the code-review AI endpoint unconditionally, governed only by `FeatureFlag::GitOperationsInCodeReview`. They ignore the user's global AI toggle and expose an AI-backed draft to enterprise customers who should not have AI reaching their source. This spec gates every AI call in the git-operations flow behind the user's AI toggle and a hard enterprise check, so the feature degrades to a pure-manual experience when AI is not allowed — without hiding the git buttons themselves.
|
||||
## Behavior
|
||||
### When AI auto-gen is available
|
||||
1. "AI auto-gen is available" means ALL of the following are true:
|
||||
- `FeatureFlag::GitOperationsInCodeReview.is_enabled()` (the parent feature flag).
|
||||
- `AISettings::is_git_operations_autogen_enabled(app)` returns `true`. That getter is itself `is_active_ai_enabled(app) && *self.git_operations_autogen_enabled_internal`, matching the sibling `is_shared_block_title_generation_enabled` getter. `is_active_ai_enabled` in turn composes `is_any_ai_enabled` (global AI toggle + auth state + remote-session org policy) with the active-AI internal toggle and `AppExecutionMode::allows_active_ai()`, plus a dedicated per-feature toggle the user can flip independently.
|
||||
- Either the user's current team is not enterprise, **or** the team is on the Warp Plan (internal Warp team), **or** the build is a dogfood channel build. This matches `share_block_modal.rs::should_send_title_gen_request` (lines 1161-1174) exactly, which exists for the same reason: our internal Warp team and our dogfood team are both tagged as enterprise customers and would otherwise self-disable AI on internal builds.
|
||||
2. When AI auto-gen is available, the Commit, Push, Create PR, Commit-and-push, and Commit-and-create-PR flows behave exactly as they do today (see `specs/APP-3918/PRODUCT.md`). No regressions.
|
||||
### When AI auto-gen is not available
|
||||
3. The primary git-operations split button, its chevron menu, and all dialog entry points remain visible and functional exactly as they are when AI is available. Git operations themselves do not depend on AI.
|
||||
4. The Commit dialog opens with the `"Type a commit message"` placeholder immediately. It never shows the `"Generating…"` placeholder.
|
||||
5. The Commit dialog does not issue any AI request when it opens. The message editor starts empty and remains empty until the user types something.
|
||||
6. The Confirm button in the Commit dialog stays disabled until the user types a non-empty message, matching the existing "AI returned empty / failed" path — the user is never blocked on a network call that will not happen.
|
||||
7. The `CommitAndPush` chain runs `run_commit` → `run_push` with the user-typed message, unchanged. It does not involve AI in any state, so it is unaffected.
|
||||
8. The `CommitAndCreatePr` chain runs `run_commit` → `run_push` → creates the PR via `gh pr create --fill` (i.e. the existing AI-failure fallback path in `create_pr_with_ai_content`), without issuing any AI request for title or body. The resulting PR uses the latest commit's subject/body as title/body.
|
||||
9. The standalone Create PR dialog (`GitDialogMode::CreatePr`) creates the PR via `gh pr create --fill` on confirm, without issuing any AI request for title or body.
|
||||
10. Logs and telemetry emitted by the git-operations flow do not reference AI activity in this state. No `"Failed to autogenerate commit message"` warnings fire, because no request is attempted.
|
||||
11. No toast, banner, or dialog copy tells the user AI is disabled. The absence of the `"Generating…"` state is the only user-visible signal; the feature simply degrades to manual.
|
||||
### State transitions
|
||||
12. If the user flips the global AI toggle while a Commit dialog is open, the in-flight state of that dialog is preserved — any already-generated draft stays, any already-showing `"Generating…"` placeholder stays until the in-flight request resolves or fails. The toggle only affects dialogs opened after the change.
|
||||
13. If the user's team `customer_type` changes (e.g. workspace switch) while a Commit dialog is open, the same rule applies: the open dialog keeps whatever state it was in; only the next-opened dialog reflects the new gate.
|
||||
14. Toggling the global AI off mid-session and then reopening the Commit dialog yields the not-available behavior above. Toggling it back on yields the available behavior on the next open.
|
||||
14a. In-flight AI requests are never proactively cancelled, including when the user's team `customer_type` or global AI toggle changes mid-request. The gate is evaluated once when the dialog opens and the result sticks for that dialog's lifetime, including the confirm-time `CommitAndCreatePr` chain and the standalone Create PR confirm. Only the next-opened dialog reflects any subsequent toggle or team change.
|
||||
### Interaction with existing gates
|
||||
15. The overflow menu's `"Add diff set as context"` item continues to use its existing `AISettings::is_any_ai_enabled` gate (`code_review_view.rs:7104`). This spec does not change that gate, and does not add an enterprise check there — adding one is a follow-up if product decides.
|
||||
16. The `FeatureFlag::GitOperationsInCodeReview` flag continues to gate the entire git-operations UI. When the flag is off, the gates in this spec are moot.
|
||||
17. Remote-session org policy (`is_ai_disabled_due_to_remote_session_org_policy`) already flows through `is_any_ai_enabled`, which `is_active_ai_enabled` composes with — and the new `is_git_operations_autogen_enabled` getter composes with `is_active_ai_enabled`. Enterprise workspaces that disable AI in remote sessions get the not-available behavior without any separate plumbing.
|
||||
18. The new per-feature setting `git_operations_autogen_enabled_internal` appears in the AI settings page alongside the other per-feature toggles (shared block title generation, code suggestions, etc.). Default: `true`. A user may flip it off independently of the global AI toggle.
|
||||
@@ -0,0 +1,114 @@
|
||||
# APP-4118: Toggle AI auto-gen + Enterprise gate — Tech Spec
|
||||
Product spec: `specs/APP-4118/PRODUCT.md`
|
||||
Parent: APP-3918 (git operations button)
|
||||
## Context
|
||||
APP-3918 landed the git-operations split button and wired three dialogs (Commit, Push, Create PR) plus two chains (`CommitAndPush`, `CommitAndCreatePr`). Every AI-backed path in those flows is gated only on `FeatureFlag::GitOperationsInCodeReview`; none of them respect `AISettings::is_any_ai_enabled` or enterprise status. This spec mirrors the `share_block_modal.rs::should_send_title_gen_request` pattern: a dedicated per-feature AI setting, plus a shared `should_send_git_ops_ai_request` helper that folds the feature flag, the per-feature AI toggle, and an enterprise check (with Warp-plan exception and dogfood override) together and routes every AI call site in the git-operations flow through it.
|
||||
### Relevant code
|
||||
- `app/src/code_review/git_dialog/commit.rs:116-120` — chooses `GENERATING_PLACEHOLDER_TEXT` vs `FALLBACK_PLACEHOLDER_TEXT` based on the feature flag only.
|
||||
- `app/src/code_review/git_dialog/commit.rs:209-222` — kicks off `generate_commit_message` on dialog open when there are changes.
|
||||
- `app/src/code_review/git_dialog/commit.rs:260-320` — `generate_commit_message` helper; unconditionally calls `code_review_ai.generate_code_review_content`.
|
||||
- `app/src/code_review/git_dialog/commit.rs:385-396` — `CommitAndCreatePr` chain calls `create_pr_with_ai_content`.
|
||||
- `app/src/code_review/git_dialog/pr.rs:101-126` — standalone Create PR confirm calls `create_pr_with_ai_content`.
|
||||
- `app/src/code_review/git_dialog/pr.rs:134-184` — `create_pr_with_ai_content` issues two `generate_code_review_content` requests (title + body) and falls back to `gh pr create --fill` on failure / empty content.
|
||||
- `app/src/settings/ai.rs:1468-1477` — `AISettings::is_any_ai_enabled` (already folds in auth + remote-session org policy).
|
||||
- `app/src/settings/ai.rs:815-823` — `shared_block_title_generation_enabled_internal` setting, shape we are copying for the new `git_operations_autogen_enabled_internal`.
|
||||
- `app/src/settings/ai.rs:1536-1538` — `is_shared_block_title_generation_enabled` getter, shape we are copying for the new `is_git_operations_autogen_enabled`.
|
||||
- `app/src/terminal/share_block_modal.rs:1161-1174` — `should_send_title_gen_request`, the AI-title-gen gate we are mirroring (`is_active_ai_enabled` via per-feature getter, enterprise check with Warp-plan exception, dogfood override).
|
||||
- `app/src/workspaces/workspace.rs:562` — `BillingMetadata::is_warp_plan()` accessor used by the Warp-plan exception.
|
||||
- `app/src/workspaces/user_workspaces.rs` — `UserWorkspaces`, `current_team` accessor.
|
||||
- `crates/graphql/src/api/workspace.rs` — `CustomerType` enum definition (reached via `billing_metadata`).
|
||||
## Current state
|
||||
The commit dialog opens, sets the `"Generating…"` placeholder, fires a diff-build plus a `generate_code_review_content` call, and swaps the placeholder + writes the generated text when the future resolves. Failure and empty-content cases already exist and are well-tested: both fall back to `FALLBACK_PLACEHOLDER_TEXT` with an empty editor. The PR flow already handles AI failure via `gh pr create --fill`. In other words: the "skip AI entirely" code paths already exist — we just need to take them deterministically when the gate is closed.
|
||||
## Proposed changes
|
||||
### 1. New per-feature AI setting
|
||||
Add `git_operations_autogen_enabled_internal` to the `define_settings_group!(AISettings, ...)` block in `app/src/settings/ai.rs`, matching the shape of `shared_block_title_generation_enabled_internal` (`ai.rs:815-823`):
|
||||
```rust path=null start=null
|
||||
git_operations_autogen_enabled_internal: GitOperationsAutogenEnabled {
|
||||
type: bool,
|
||||
default: true,
|
||||
supported_platforms: SupportedPlatforms::ALL,
|
||||
sync_to_cloud: SyncToCloud::Globally(RespectUserSyncSetting::Yes),
|
||||
private: false,
|
||||
toml_path: "agents.oz.active_ai.git_operations_autogen_enabled",
|
||||
description: "Controls whether AI auto-generates commit messages and PR title/body in the code review dialogs.",
|
||||
}
|
||||
```
|
||||
And the getter on `AISettings`, matching `is_shared_block_title_generation_enabled` (`ai.rs:1547-1549`):
|
||||
```rust path=null start=null
|
||||
pub fn is_git_operations_autogen_enabled(&self, app: &AppContext) -> bool {
|
||||
self.is_active_ai_enabled(app) && *self.git_operations_autogen_enabled_internal
|
||||
}
|
||||
```
|
||||
`is_active_ai_enabled` composes `is_any_ai_enabled` (global AI toggle + auth + remote-session org policy) with the active-AI internal toggle and `AppExecutionMode::allows_active_ai()`, so the new getter transitively inherits all of those checks.
|
||||
Expose it in the AI settings page (`app/src/settings_view/ai_page.rs`) alongside the existing per-feature toggles.
|
||||
### 2. New helper: `should_send_git_ops_ai_request`
|
||||
Location: `app/src/code_review/git_dialog/mod.rs` (new private helper, co-located with the other shared git-dialog utilities like `show_toast` and `user_facing_git_error`). Structure copied from `share_block_modal.rs::should_send_title_gen_request` (`share_block_modal.rs:1161-1174`):
|
||||
```rust path=null start=null
|
||||
fn should_send_git_ops_ai_request(app: &AppContext) -> bool {
|
||||
FeatureFlag::GitOperationsInCodeReview.is_enabled()
|
||||
&& AISettings::as_ref(app).is_git_operations_autogen_enabled(app)
|
||||
&& (!UserWorkspaces::as_ref(app)
|
||||
.current_team()
|
||||
.is_some_and(|team| team.billing_metadata.customer_type == CustomerType::Enterprise)
|
||||
// Allow the Warp Stable team to use this.
|
||||
|| UserWorkspaces::as_ref(app)
|
||||
.current_team()
|
||||
.is_some_and(|team| team.billing_metadata.is_warp_plan())
|
||||
// Override the enterprise check for dogfood builds, as our dogfood team
|
||||
// is an enterprise team.
|
||||
|| ChannelState::channel().is_dogfood())
|
||||
}
|
||||
```
|
||||
Shared helper (vs inlining like `share_block_modal.rs`) because we call it from both `commit::new_state` and `pr::new_state`, and keeping the enterprise / Warp-plan / dogfood details out of `commit.rs` and `pr.rs` avoids pulling billing + channel imports into those files.
|
||||
### 2a. Gate evaluation
|
||||
`should_send_git_ops_ai_request` is called directly at each decision point rather than snapshotted onto dialog state. In `commit::new_state` it is called once to set the initial placeholder and decide whether to kick off open-time autogen. In `commit::start_confirm` and `pr::start_confirm` it is called again at confirm time to decide whether to materialize a `code_review_ai` handle. This is consistent with how the sibling `should_send_title_gen_request` is used.
|
||||
### 3. Gate `commit.rs` open-time autogen
|
||||
In `commit::new_state`, read `ai_autogen_enabled = should_send_git_ops_ai_request(ctx)` once and use it for both:
|
||||
- `initial_placeholder` becomes `GENERATING_PLACEHOLDER_TEXT` only when `ai_autogen_enabled`; otherwise `FALLBACK_PLACEHOLDER_TEXT`.
|
||||
- The `generate_commit_message` call inside the spawn's resolution is only issued when `ai_autogen_enabled && has_changes`. When the gate is closed (or there are no changes), the spawn resolution skips autogen and the editor stays on `FALLBACK_PLACEHOLDER_TEXT`.
|
||||
No changes to `generate_commit_message` itself — it is unreachable when the gate is closed.
|
||||
### 4. Gate `pr.rs::start_confirm`
|
||||
`start_confirm` calls `should_send_git_ops_ai_request(ctx)` directly and branches:
|
||||
- AI path: `create_pr_with_ai_content` with the `code_review_ai` handle.
|
||||
- Fallback path: `create_pr(repo_path, None, None)` (`gh pr create --fill`).
|
||||
The `code_review_ai` handle is only materialized on the AI branch so we do not hold an unused handle in the AI-off path. Existing success/error handling is unchanged — both branches return `Result<PrInfo>`.
|
||||
### 5. Gate the `CommitAndCreatePr` chain
|
||||
`commit::start_confirm` calls `should_send_git_ops_ai_request(ctx)` and uses the result to decide whether to materialize `code_review_ai`. The `Option<Arc<dyn AIClient>>` is captured into the `ctx.spawn` async body; the `CommitAndCreatePr` arm matches on it:
|
||||
```rust path=null start=null
|
||||
CommitIntent::CommitAndCreatePr => {
|
||||
run_push(&repo_path, &branch_name).await?;
|
||||
let pr = match code_review_ai {
|
||||
Some(ai) => create_pr_with_ai_content(&repo_path, &branch_name, ai.as_ref()).await?,
|
||||
None => create_pr(&repo_path, None, None).await?,
|
||||
};
|
||||
CommitOutcome::PrCreated(pr)
|
||||
}
|
||||
```
|
||||
### 6. Imports
|
||||
The helper in `git_dialog/mod.rs` needs:
|
||||
- `warp_core::features::FeatureFlag`
|
||||
- `crate::settings::AISettings`
|
||||
- `crate::workspaces::user_workspaces::UserWorkspaces`
|
||||
- `CustomerType` (match the path used by `share_block_modal.rs:32`)
|
||||
- `warp_core::channel::ChannelState` (match the path used by `share_block_modal.rs`)
|
||||
`commit.rs` and `pr.rs` each import `should_send_git_ops_ai_request` from `super`. `commit::new_state` calls it to set the initial placeholder; `commit::start_confirm` and `pr::start_confirm` each call it again at confirm time.
|
||||
## Testing and validation
|
||||
No unit tests are added for this change. Validation is manual; maps to Behavior invariants 2, 4–9, 12–14:
|
||||
- With AI enabled + non-enterprise: commit / push / PR flows unchanged from APP-3918 (no regressions). Covered by re-running APP-3918's manual validation list.
|
||||
- Toggle `is_any_ai_enabled` off → open Commit dialog → editor shows `"Type a commit message"` immediately, no `"Generating…"`, no AI request in the network log. (Invariants 4–6, 10.)
|
||||
- Still off → confirm a typed message → commit succeeds. `Commit and push` chain with a typed message → commit + push succeed. (Invariants 7, 11.)
|
||||
- Still off → `Commit and create PR` with a typed message → PR gets created with the latest commit's subject/body (via `--fill`). (Invariant 8.)
|
||||
- Still off → open Create PR dialog → confirm → PR gets created via `--fill`. (Invariant 9.)
|
||||
- Enterprise user (AI on): same behaviors as AI-off. (Invariant 1, second clause.)
|
||||
- Toggle AI off *while* the dialog is open mid-`"Generating…"`: the dialog keeps its in-flight state; close and reopen → now behaves as AI-off. (Invariants 12, 14.)
|
||||
### Verification aid
|
||||
The existing log line `"Failed to autogenerate commit message"` at `commit.rs:310` is a useful canary: it must not fire in AI-off/enterprise runs of the manual validation (because no request is attempted). Invariant 10 depends on this.
|
||||
## Risks and mitigations
|
||||
- **Drift between commit-message autogen and PR-creation gate decisions**: each call to `should_send_git_ops_ai_request` evaluates the same predicate, so open-time autogen (evaluated in `new_state`) and confirm-time PR creation (evaluated in `start_confirm`) both reflect the current AI state at the time they run. Invariant 14a (gate evaluated at dialog-open) holds for the initial placeholder/autogen decision; confirm re-evaluates but in practice the user cannot change AI state between open and confirm in normal usage.
|
||||
- **Enterprise detection via `current_team()` returns `None`**: if a signed-in user has no current team, `is_some_and` yields `false` for both the enterprise branch and the Warp-plan branch. The enterprise-guard's outer negation means no-team users pass the gate — same behavior as `should_send_title_gen_request`.
|
||||
- **Dogfood override on production builds**: `ChannelState::channel().is_dogfood()` is false on Preview/Stable, so the override only fires where it is intended.
|
||||
- **Per-feature setting drift**: new `git_operations_autogen_enabled_internal` setting needs to be exposed in the AI settings page alongside the other per-feature toggles — easy to forget. Covered under §1.
|
||||
## Follow-ups
|
||||
- Consider adding the same enterprise check to `code_review_view.rs:7104`'s `"Add diff set as context"` gate if product wants a uniform enterprise AI posture across code review (Invariant 15 explicitly scopes this out for now).
|
||||
- If telemetry ever needs to distinguish "AI skipped because disabled" from "AI attempted and failed", add an event in the gate's `false` branch — not required by this spec.
|
||||
- If other code-review AI call sites (e.g. future review-comment summarization) land, reuse `should_send_git_ops_ai_request` or a sibling helper rather than inlining the enterprise-plus-Warp-plan-plus-dogfood clause again.
|
||||
Reference in New Issue
Block a user