Initial public release of Warp.

Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
David Stern
2026-04-28 08:43:33 -05:00
commit 0dbd3d567a
4982 changed files with 1431549 additions and 0 deletions
+137
View File
@@ -0,0 +1,137 @@
# Product Spec: Gemini CLI Plugin Install & Update Flow
Linear: [APP-4067](https://linear.app/warpdotdev/issue/APP-4067/enable-gemini-notifications)
Figma: none provided
## Summary
Add install and update chip support for the Gemini CLI Warp plugin (`gemini-warp`), matching the existing Claude Code chip UX. Enable the listener so Warp processes structured notifications from Gemini sessions (session status, permission requests, task completion).
## Problem
The `gemini-warp` extension already exists and uses the same structured OSC 777 notification protocol as Claude Code (`warp://cli-agent` title, JSON body with `agent: "gemini"`). However, Warp currently ignores these notifications because:
1. `create_handler` in the listener returns `None` for `CLIAgent::Gemini`.
2. `plugin_manager_for` returns `None` for `CLIAgent::Gemini` — there's no install/update chip.
3. There is no `GeminiNotifications` feature flag to gate the rollout.
## Goals
- Gemini users see install/update chips that prompt them to set up the plugin.
- Clicking the chip auto-installs or auto-updates the plugin (Gemini CLI has first-class extension management commands).
- A manual instructions fallback is available for cases where auto-install fails.
- Structured notifications from the Gemini plugin are received and displayed (session status, blocked/success states, permission requests).
## Non-Goals
- Changing the gemini-warp plugin itself (it's maintained separately in `warpdotdev/gemini-cli-warp`).
- Platform plugin / Oz harness support for Gemini (future work).
## How Gemini CLI Extensions Work
Gemini CLI has a first-class extension system:
- **Install**: `gemini extensions install <github-url-or-local-path>` — copies the extension into `~/.gemini/extensions/<name>/`.
- **Update**: `gemini extensions update <name>` — pulls the latest version from the source.
- **Uninstall**: `gemini extensions uninstall <name>`.
- **Manifest**: Each extension has a `gemini-extension.json` with `name`, `version`, `description`, and hook/MCP/command definitions.
- **Hooks**: Defined in `hooks/hooks.json` within the extension directory (not in the manifest). The gemini-warp plugin uses `SessionStart`, `AfterAgent`, `Notification`, `BeforeAgent`, and `AfterTool` hooks.
Key difference from Claude Code: Gemini extensions install from a GitHub URL directly (no separate marketplace add step). The install and update are each a single CLI command.
Key difference from OpenCode: Gemini CLI *does* have CLI commands for extension management, so auto-install and auto-update are viable.
## User Experience
### Install Chip
When a user starts a Gemini session and the plugin has never connected (no listener, no `plugin_version` reported):
- A green chip appears: "Notifications setup instructions"
- Clicking triggers auto-install via `gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent`.
- `--consent` skips the interactive confirmation prompt that would block the non-interactive shell execution.
- On success, a toast confirms installation and tells the user to restart Gemini.
- On failure, a toast appears with an error, and the user can click the info (ⓘ) button to open manual instructions in a split pane.
**Manual install instructions (split pane):**
- Title: "Install Warp Plugin for Gemini CLI"
- Subtitle: "Run the following command, then restart Gemini CLI."
- Steps:
1. "Install the Warp extension" — command: `gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent`
- Post-install notes: "Restart Gemini CLI to activate the plugin."
### Update Chip
When the plugin is connected but reports a version below `MINIMUM_PLUGIN_VERSION`:
- Label: "Plugin update available"
- Clicking triggers auto-update via `gemini extensions update gemini-warp`.
- The extension name is `gemini-warp` (the installed directory name under `~/.gemini/extensions/`).
- On success, a toast confirms the update and tells the user to restart Gemini.
- On failure, a toast appears with an error, and the user can click the info (ⓘ) button to open manual instructions in a split pane.
**Manual update instructions (split pane):**
- Title: "Update Warp Plugin for Gemini CLI"
- Subtitle: "Run the following command, then restart Gemini CLI."
- Steps:
1. "Update the Warp extension" — command: `gemini extensions update gemini-warp`
- Post-install notes: "Restart Gemini CLI to activate the update."
### Version Detection
Two signals, matching the Claude Code pattern:
1. **Filesystem check**: `~/.gemini/extensions/gemini-warp/gemini-extension.json` — read the `version` field to determine if the plugin is installed and whether it's outdated. Symlinks are followed (supports `gemini extensions link`).
2. **Runtime signal**: `plugin_version` field from the `SessionStart` event (emitted by `on-session-start.sh` in the plugin).
The filesystem check eliminates install chip flicker (the chip never appears if the plugin is already on disk) and enables update detection before the plugin connects.
### Notification Listening
Once the plugin is installed, the Gemini session handler works identically to Claude Code / OpenCode: the `DefaultSessionListener` receives structured JSON events and forwards them to the sessions model. No special parsing is needed — the plugin already speaks the same protocol.
### Chip Visibility Logic
Same as Claude Code:
1. Plugin connected, version >= minimum → **no chip**
2. Plugin connected, version < minimum → **update chip**
3. No listener, plugin installed on disk with version >= minimum → **no chip** (wait for connection)
4. No listener, plugin installed on disk with version < minimum → **update chip**
5. No listener, plugin not installed on disk → **install chip** (unless dismissed)
6. Chip dismissed → **no chip** (install and update have independent dismiss state)
7. Notifications disabled in settings → **no chip**
### Dismiss Behavior
Same as Claude Code / OpenCode: install chip and update chip have independent dismiss state. The update chip tracks which minimum version was dismissed; a new minimum causes it to reappear.
## Edge Cases
- **Plugin installed via `gemini extensions link` (symlink):** Filesystem check follows symlinks, so linked extensions are detected as installed. Version is read from the symlinked `gemini-extension.json`.
- **`gemini` CLI not on PATH:** Auto-install fails; user sees error toast and can use the manual instructions pane. The manual instructions pane commands will also need `gemini` on PATH, but the user can adapt (e.g. use full path or install Gemini CLI).
- **Extension already installed:** `gemini extensions install` may fail or warn. This is fine — the user already has the plugin.
- **SSH sessions:** Same experience as local. The `gemini` CLI must be available on the remote host. Filesystem checks don't apply to remote sessions (same as Claude Code).
## Success Criteria
1. Starting a Gemini CLI session in Warp when the plugin is not installed shows a green install chip.
2. Clicking the install chip runs `gemini extensions install` and shows a success/failure toast.
3. After restart, the plugin connects, notifications appear in the inbox, and the chip disappears.
4. When the plugin reports a version below `MINIMUM_PLUGIN_VERSION`, an update chip appears.
5. Clicking the update chip runs `gemini extensions update gemini-warp` and shows a success/failure toast.
6. The info (ⓘ) button opens a split pane with manual instructions (not a modal).
7. Dismissing the chips works identically to other agents (independent dismiss state, version-tracked update dismiss).
8. Gemini notifications (stop, blocked, permission request) surface in the agent inbox when HOANotifications is enabled.
9. The install/update chip is gated behind both `HOANotifications` and `GeminiNotifications` feature flags. Notification listening only requires `HOANotifications` (consistent with other agents).
## Validation
- Unit tests: `GeminiPluginManager` trait implementations (minimum version, can_auto_install, install/update instructions).
- Unit tests: `is_installed()` / `needs_update()` filesystem detection with temp directories.
- Unit tests: `is_agent_supported` returns `true` for `CLIAgent::Gemini` when feature flags are enabled.
- Unit tests: `plugin_manager_for(CLIAgent::Gemini)` returns `Some(...)` when flags are enabled.
- Manual testing: full install flow with a real Gemini CLI session.
- Manual testing: update flow — verify `gemini extensions update` pulls new version.
- Manual testing: notification flow — verify stop, blocked, permission request notifications appear.
+217
View File
@@ -0,0 +1,217 @@
# Tech Spec: Gemini CLI Plugin Install & Update Flow
See `specs/APP-4067/PRODUCT.md` for the product spec.
## 1. Problem
`plugin_manager_for(CLIAgent::Gemini)` returns `None` (`plugin_manager/mod.rs:189`), and `create_handler` in the listener also returns `None` for `CLIAgent::Gemini` (`listener/mod.rs:53`). This means Gemini sessions get no install/update chip and their structured OSC 777 notifications are silently dropped.
Gemini CLI has first-class `gemini extensions install/update` commands, so the implementation follows the Claude Code pattern: auto-install, auto-update, and filesystem-based version detection.
## 2. Relevant Code
- `plugin_manager/mod.rs` — `CliAgentPluginManager` trait, `plugin_manager_for()` / `plugin_manager_for_with_shell()` factory, `compare_versions`
- `plugin_manager/claude.rs` — reference auto-install implementation with filesystem detection and `LocalCommandExecutor`
- `plugin_manager/opencode.rs` — reference manual-only implementation (simpler, no filesystem checks)
- `listener/mod.rs:41-61` — `is_agent_supported()` and `create_handler()` — must add `CLIAgent::Gemini`
- `agent_input_footer/mod.rs (793-873)` — `plugin_chip_kind()` determines which chip to show
- `agent_input_footer/mod.rs (877-893)` — `should_use_manual_mode()`
- `agent_input_footer/mod.rs (925+)` — `handle_plugin_operation()` shared async handler
- `crates/warp_features/src/lib.rs:760-769` — existing `HOANotifications`, `OpenCodeNotifications`, `CodexNotifications` flags
## 3. Current State
The plugin manager infrastructure is fully generalized across Claude (auto-install + filesystem detection), OpenCode (manual-only), and Codex (manual-only, no update support). Adding a new auto-install agent requires:
1. A new struct implementing `CliAgentPluginManager` with `can_auto_install() == true`.
2. `is_installed()` and `needs_update()` filesystem checks.
3. `install()` and `update()` methods that shell out CLI commands via `LocalCommandExecutor`.
4. Wiring into `plugin_manager_for_with_shell()`.
5. Adding the agent to `create_handler()` and `is_agent_supported()` in the listener.
6. A feature flag to gate the rollout.
## 4. Proposed Changes
### 4a. Feature Flag (`crates/warp_features/src/lib.rs`)
Add `GeminiNotifications` to the `FeatureFlag` enum after `CodexNotifications`:
```rust
/// Enables the install/update chip for the Gemini CLI Warp extension.
/// Requires HOANotifications to also be enabled.
GeminiNotifications,
```
Add to `DOGFOOD_FLAGS`.
### 4b. Gemini Plugin Manager (`plugin_manager/gemini.rs`)
New file. Follows the Claude Code pattern closely.
**Constants:**
- `EXTENSION_REPO: &str = "https://github.com/warpdotdev/gemini-cli-warp"` — install source.
- `EXTENSION_NAME: &str = "gemini-warp"` — the installed directory name under `~/.gemini/extensions/`. Used for `gemini extensions update gemini-warp`.
- `MINIMUM_PLUGIN_VERSION: &str = "1.0.0"` — matches current plugin version.
**Struct:**
```rust
pub(super) struct GeminiPluginManager {
executor: LocalCommandExecutor,
path_env_var: Option<String>,
}
```
Same `new(shell_path, shell_type, path_env_var)` constructor pattern as `ClaudeCodePluginManager`.
**Filesystem detection:**
`gemini_extensions_dir()` — returns `~/.gemini/extensions` (no env var override like Claude's `CLAUDE_HOME`, Gemini CLI doesn't support one).
`is_installed()` — checks if `~/.gemini/extensions/gemini-warp/gemini-extension.json` exists and parses as valid JSON. `fs::read_to_string` follows symlinks, so `gemini extensions link` is handled.
`installed_version()` — reads `~/.gemini/extensions/gemini-warp/gemini-extension.json`, parses the `version` field. The JSON structure is flat: `{"name": "warp", "version": "1.0.0", ...}`.
`needs_update()` — calls `installed_version()`, compares against `MINIMUM_PLUGIN_VERSION` using `compare_versions`. Returns `true` if version is lower, or if installed but no version field.
**Auto-install/update:**
`install()`:
```
gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent
```
`--consent` skips the interactive security confirmation prompt.
`update()`:
```
gemini extensions update gemini-warp
```
Both delegate to the shared `run_cli_command_logged()` helper in `mod.rs` via a thin `run_logged()` wrapper method. The shared helper takes a CLI name, args, executor, and env vars, runs the command via `LocalCommandExecutor::execute_local_command_in_login_shell`, and returns `Result<(), PluginInstallError>`.
**Instructions (fallback):**
Install instructions: single step — `gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent`.
Update instructions: single step — `gemini extensions update gemini-warp`. Post-install note: "Restart Gemini CLI to activate the update."
### 4c. Factory Registration (`plugin_manager/mod.rs`)
Add `pub(crate) mod gemini;` to module declarations.
Add match arm in `plugin_manager_for_with_shell()` before the wildcard:
```rust
CLIAgent::Gemini
if FeatureFlag::GeminiNotifications.is_enabled()
&& FeatureFlag::HOANotifications.is_enabled() =>
{
Some(Box::new(GeminiPluginManager::new(
shell_path,
shell_type,
path_env_var,
)))
}
```
### 4d. Listener Registration (`listener/mod.rs`)
**`is_agent_supported()`** — add `CLIAgent::Gemini`:
```rust
matches!(
agent,
CLIAgent::Claude | CLIAgent::OpenCode | CLIAgent::Codex | CLIAgent::Gemini
)
```
**`create_handler()`** — add `CLIAgent::Gemini` alongside Claude/OpenCode (same structured JSON protocol, uses `DefaultSessionListener`):
```rust
CLIAgent::Claude | CLIAgent::OpenCode | CLIAgent::Gemini => {
Some(Box::new(DefaultSessionListener))
}
```
No feature flag checks needed here — the call sites (`register_cli_agent_listener` in `terminal/view.rs`) already gate on `HOANotifications`.
### 4e. Chip Behavior
Since `can_auto_install() == true` and `is_installed()` does a filesystem check, the chip behavior matches Claude Code exactly:
- Installed + up to date → no chip (no flicker)
- Installed + outdated → update chip immediately
- Not installed → install chip immediately
- The debounce guard (`plugin_chip_ready`) only applies to `!manager.can_auto_install()` agents, so Gemini skips it
## 5. End-to-End Flow
### Install
1. User starts Gemini CLI in Warp. `CLIAgent::Gemini` detected, session created.
2. `plugin_manager_for(Gemini)` returns `Some(GeminiPluginManager)`.
3. Footer: `plugin_chip_kind()` → no listener, `is_installed()` checks `~/.gemini/extensions/gemini-warp/gemini-extension.json` → not found → `PluginChipKind::Install`.
4. `should_use_manual_mode()` → `false` (auto-install, local, no prior failure).
5. User clicks → `handle_plugin_operation()` → `gemini extensions install https://github.com/warpdotdev/gemini-cli-warp --consent`.
6. Success toast → user restarts Gemini → plugin hooks fire, `SessionStart` reports `plugin_version: "1.0.0"` → chip disappears.
### Update
1. `MINIMUM_PLUGIN_VERSION` bumped to `"1.1.0"`.
2. On session start, `is_installed()` → `true`, `needs_update()` → `true` (on-disk version `"1.0.0"` < `"1.1.0"`) → `PluginChipKind::Update`.
3. User clicks → `gemini extensions update gemini-warp` → success → restart → version `"1.1.0"` → chip gone.
### Auto-install failure fallback
1. `gemini` not on PATH → `install()` returns `Err`.
2. Error toast + `has_plugin_auto_failed` set.
3. Next render: `should_use_manual_mode()` → `true`. Chip becomes instructions button.
4. User clicks → split pane with manual instructions.
## 6. Risks and Mitigations
**Risk: `gemini` not on PATH in the login shell.** `npm install -g` with nvm may not be on PATH in non-interactive login shells. **Mitigation:** `path_env_var` from the terminal session captures the user's interactive PATH. Manual instructions fallback also covers this.
**Risk: `--consent` not available in older Gemini CLI versions.** **Mitigation:** Extensions framework (and `--consent`) shipped in v0.4.x. Users on older versions wouldn't have extensions support at all, so this is a non-issue.
**Risk: `~/.gemini` directory doesn't exist yet.** A fresh Gemini CLI install may not have created the extensions directory. **Mitigation:** `is_installed()` returns `false` when the path doesn't exist, which is correct — the install chip shows.
## 7. Testing and Validation
### Unit tests (`plugin_manager/gemini_tests.rs`)
- `can_auto_install_is_true`
- `minimum_version` — returns `"1.0.0"`
- `install_instructions_has_steps`
- `update_instructions_has_steps`
- `installed_when_extension_present` — write valid `gemini-extension.json` to temp dir, verify `true`
- `not_installed_when_extension_missing` — empty temp dir, verify `false`
- `not_installed_when_json_invalid` — invalid JSON in manifest, verify `false`
- `installed_version_returns_version_when_present` — verify version string extraction
- `installed_version_returns_none_when_no_version_field` — verify `None` when version field missing
- `installed_version_returns_none_when_file_missing` — verify `None` when no manifest
- `needs_update_logic_true_when_version_outdated` — version `"0.9.0"` against minimum `"1.0.0"`, verify update needed
- `needs_update_logic_false_when_version_current` — version `"1.0.0"` against minimum `"1.0.0"`, verify no update needed
### Unit tests (`plugin_manager/mod_tests.rs`)
- `returns_manager_for_gemini` — requires both `GeminiNotifications` and `HOANotifications` enabled
- Remove `CLIAgent::Gemini` from `returns_none_for_unsupported_agents`
### Manual testing
- Start Gemini CLI session → install chip appears (not installed on disk)
- Click install → auto-install succeeds → toast → restart → notifications work
- Bump minimum version → update chip appears (before plugin connects)
- Click update → auto-update succeeds → toast
- Disconnect `gemini` from PATH → auto fails → manual instructions pane works
- Feature flags off → no chip, no listener
## 8. Follow-Ups
- **Publish `warpdotdev/gemini-warp` to GitHub** — must happen before shipping to external users.
- **Platform plugin / Oz harness support** — future work.
- **Promote `GeminiNotifications` from dogfood** — after validation.
## 9. Files Changed
- **New:** `plugin_manager/gemini.rs` — `GeminiPluginManager`, filesystem detection, install/update via `LocalCommandExecutor`, instructions
- **New:** `plugin_manager/gemini_tests.rs` — unit tests
- **Modified:** `plugin_manager/mod.rs` — add `pub(crate) mod gemini;`, wire `CLIAgent::Gemini` into factory, add `install_success_message` / `update_success_message` default trait methods, extract shared `run_cli_command_logged` and `path_env_from_var` helpers
- **Modified:** `plugin_manager/claude.rs` — override `install_success_message` and `update_success_message` with Claude-specific reload-plugins messages, refactor to use shared `run_cli_command_logged` helper
- **Modified:** `plugin_manager/mod_tests.rs` — add Gemini factory test, update unsupported agents test
- **Modified:** `listener/mod.rs` — add `CLIAgent::Gemini` to `is_agent_supported()` and `create_handler()`
- **Modified:** `agent_input_footer/mod.rs` — `handle_install_plugin` / `handle_update_plugin` now query the plugin manager for per-agent success messages
- **Modified:** `crates/warp_features/src/lib.rs` — add `GeminiNotifications` variant + `DOGFOOD_FLAGS`