Initial public release of Warp.
Repo-Sync-Origin: warpdotdev/warp-internal@12af1d983b
This commit is contained in:
@@ -0,0 +1,19 @@
|
||||
# PRODUCT.md
|
||||
## Summary
|
||||
Surface the execution harness (Warp Agent, Claude Code, Gemini CLI) in the conversation details sidebar as its own labeled field, matching the panel's other metadata fields ("Run ID", "Credits used", "Run time", etc.).
|
||||
## Figma
|
||||
None provided. Visual treatment should match the panel's existing metadata fields: a small "Harness" label in the sub-text color above a value row. The value row is a small leading logo tinted with the harness's brand color followed by the harness display name.
|
||||
## Behavior
|
||||
1. The conversation details panel renders a "Harness" field in the sidebar whenever the harness for that conversation or task is known. The field is rendered in the same label-over-value style as sibling fields like "Run ID", "Credits used", and "Run time".
|
||||
2. The label is the literal string "Harness", colored with the panel's sub-text color. The value row sits directly below the label and shows, left-to-right: a small leading harness icon, then the harness's user-visible display name in the theme foreground. Mapping:
|
||||
* Warp Agent (the `Oz` harness) → `Icon::Warp`, "Warp Agent", tinted with the theme foreground (same icon + treatment used elsewhere for first-party Warp skills).
|
||||
* Claude → `Icon::ClaudeLogo`, "Claude Code", tinted with the Claude brand orange.
|
||||
* Gemini → `Icon::GeminiLogo`, "Gemini CLI", tinted with the Gemini brand blue.
|
||||
3. Third-party harness logos render with their brand color rather than the theme foreground so their visual identity is preserved (e.g. the Claude logo is orange, not white-on-dark).
|
||||
4. Local (non-ambient) conversations and cloud tasks whose config does not explicitly set a harness render as "Warp Agent", matching the system default.
|
||||
5. Cloud tasks whose config explicitly sets a harness render that harness, regardless of whether the run succeeded, failed, or is still in progress.
|
||||
6. If the harness value is not yet known (specifically: the cloud-mode details panel for a shared ambient session, opened before the async task-data fetch resolves), the row is omitted entirely — no placeholder icon, no "Unknown" label, no reserved empty slot. Once the task payload arrives and a harness can be determined, the row appears; sibling rows above and below must not visually jump or reflow in a way that moves other content out from under the user's cursor.
|
||||
7. The harness row is read-only. It has no click target, no copy button, no tooltip beyond what the icon naturally conveys, and nothing about it changes the currently selected harness for future runs.
|
||||
8. The row's label text is selectable for copy, consistent with other metadata rows in the panel.
|
||||
9. The harness row's position is stable across panel modes (conversation vs. task) and across repeated `set_conversation_details` calls for the same conversation; it does not reorder relative to sibling rows between renders.
|
||||
10. The harness row is rendered identically across every surface that hosts the conversation details panel (management view details pane, transcript viewer side panel, shared-session views).
|
||||
@@ -0,0 +1,64 @@
|
||||
# TECH.md
|
||||
Companion to `specs/REMOTE-1455/PRODUCT.md`.
|
||||
## Context
|
||||
Add a harness row to the conversation details panel. Behavior lives in `PRODUCT.md`; this plan covers where the row plugs in and how we source the harness value.
|
||||
Relevant code:
|
||||
- `app/src/ai/conversation_details_panel.rs (75-100)` — `PanelMode` enum (`Conversation` / `Task`) that backs the panel.
|
||||
- `app/src/ai/conversation_details_panel.rs (177-198)` — `ConversationDetailsData` struct. All new data the row needs must flow through here.
|
||||
- `app/src/ai/conversation_details_panel.rs (222-414)` — constructors: `from_conversation`, `from_task`, `from_task_id`, `from_conversation_metadata`.
|
||||
- `app/src/ai/conversation_details_panel.rs (950-1028)` — `render_skill_section`, the closest existing precedent for a single icon + label row.
|
||||
- `app/src/ai/conversation_details_panel.rs (1426-1781)` — `View::render` composes the sidebar; sections are appended to a `Flex::column` in a fixed order with `FIELD_SPACING` / `HEADER_SPACING` margins. This is where the new row is inserted.
|
||||
- `app/src/ai/ambient_agents/task.rs (57-83)` — `AgentConfigSnapshot.harness: Option<HarnessConfig>`, where `HarnessConfig { harness_type: String }` — the source of truth for a task's harness once the snapshot is loaded.
|
||||
- `crates/warp_cli/src/agent.rs (118-138)` — `Harness` enum (`Oz` / `Claude` / `Gemini`) with `clap::ValueEnum`; `Harness::from_str` (via `ValueEnum`) is the canonical parser for `harness_type` strings (values: `oz`, `claude`, `gemini`).
|
||||
- `app/src/terminal/view/ambient_agent/harness_selector.rs (59-75)` — existing `display_name` / `icon_for` helpers used by the harness selector dropdown. We reuse and centralize these so the two surfaces cannot diverge.
|
||||
- `app/src/ai/conversation_details_panel_tests.rs` — existing unit-test harness (`App::test`) used for `ConversationDetailsData`.
|
||||
## Proposed changes
|
||||
### 1. Centralize harness display metadata
|
||||
Extract the display name + icon + brand color mapping out of `harness_selector.rs` into a small shared module (`app/src/ai/harness_display.rs`), exposing:
|
||||
- `pub fn display_name(harness: Harness) -> &'static str` → `"Warp Agent"`, `"Claude Code"`, `"Gemini CLI"`.
|
||||
- `pub fn icon_for(harness: Harness) -> Icon` → `Warp` / `ClaudeLogo` / `GeminiLogo`. Oz maps to `Icon::Warp` (not `Icon::OzCloud`) so first-party Warp surfaces visually match the existing skill row.
|
||||
- `pub fn brand_color(harness: Harness) -> Option<ColorU>` → `None` for Oz (caller falls back to theme foreground), `CLAUDE_ORANGE` for Claude, `GEMINI_BLUE` for Gemini. `CLAUDE_ORANGE` is re-used from `crate::ai::blocklist`; `GEMINI_BLUE` mirrors the value already in `terminal::cli_agent`.
|
||||
- `pub fn parse_harness_type(raw: &str) -> Option<Harness>` — thin wrapper around `Harness::from_str(raw, /* ignore_case */ true)`. Unknown strings return `None`.
|
||||
- `impl From<AIAgentHarness> for Harness` — 1:1 map from the `ServerAIConversationMetadata.harness` enum (`Oz` / `ClaudeCode` / `Gemini`) to `Harness`. Used by the conversation-sourced constructors so they can resolve the real harness instead of hardcoding Oz.
|
||||
Update `harness_selector.rs` to call into this module. This renames the selector's `"Oz"` label to `"Warp Agent"` and swaps its leading icon from `Icon::OzCloud` to `Icon::Warp`, matching `PRODUCT.md` invariant 2 and keeping the two surfaces in sync.
|
||||
### 2. Data model: thread harness through `ConversationDetailsData`
|
||||
Add `harness: Option<Harness>` to `ConversationDetailsData`. Resolution per constructor:
|
||||
- `from_conversation` (WASM) — read `conversation.server_metadata().map(|m| Harness::from(m.harness))`; fall back to `Some(Harness::Oz)` when the conversation has no server metadata (pure local run).
|
||||
- `from_conversation_metadata` — takes `harness: Option<Harness>` as an explicit parameter. The management view caller resolves it via `BlocklistAIHistoryModel::get_server_conversation_metadata(&conversation_id).map(|m| Harness::from(m.harness))`, falling back to `Some(Harness::Oz)` for pure local conversations. This covers the edge case where a `ManagementCardItemId::Conversation` card represents a cloud-task-backed conversation whose task row isn't in `self.tasks` (shadowing missed): the server-side `AIAgentHarness` on the merged metadata is the authoritative source.
|
||||
- `from_task` — compute from `task.agent_config_snapshot`:
|
||||
- `Some(config)` with `config.harness = Some(HarnessConfig { harness_type })` → `parse_harness_type(&harness_type)`; unknown strings resolve to `None`.
|
||||
- `Some(config)` with `config.harness = None` → `Some(Harness::Oz)` (invariant 3: explicit default).
|
||||
- `None` (snapshot not loaded) → `None` (invariant 5: omit until known).
|
||||
- `from_task_id` → `None`.
|
||||
`Harness` is `Copy`, so `Option<Harness>` stays cheap and keeps `ConversationDetailsData: Clone`.
|
||||
### 3. Rendering: `render_harness_section`
|
||||
Add `fn render_harness_section(&self, appearance: &Appearance) -> Option<Box<dyn Element>>` structured like `render_simple_field` (label-over-value) but with an icon in the value row:
|
||||
- Returns `None` when `self.data.harness.is_none()` — enforces invariant 6 (no placeholder, no reserved slot).
|
||||
- Emits a `Flex::column` with two children separated by `LABEL_VALUE_GAP`:
|
||||
1. A "Harness" `Text` label, colored with `blended_colors::text_sub(theme, theme.surface_1())` at `ui_font_size` (invariant 2).
|
||||
2. A value `Flex::row` containing a 16px `ConstrainedBox`'d `Icon` (margin-right 4px) and a selectable `Text` with the harness display name in `theme.foreground()` (invariants 2, 8).
|
||||
- Icon tint = `brand_color(harness).map(Into::into).unwrap_or(theme.foreground())`. Warp Agent picks up the theme foreground; Claude and Gemini render in their brand colors (invariants 2 and 3).
|
||||
- No click target, no copy button, no tooltip (invariant 7).
|
||||
Insert in `View::render` directly below the Status section and above Artifacts / Directory / Run ID, wrapped in `Container::with_margin_bottom(FIELD_SPACING)` so the field has the same outer spacing as sibling `render_simple_field` fields (invariant 9). The placement is the same across both `PanelMode::Conversation` and `PanelMode::Task`. Because the slot is conditional, when a `from_task_id` stub later resolves into a full task, the panel only grows downward from that row — nothing above it moves (invariant 6's "no content moves out from under the cursor").
|
||||
No new actions, mouse states, copy-feedback entries, or events. `handle_action` and `PanelMouseStates` are untouched.
|
||||
### 4. No telemetry, no new surfaces
|
||||
Row is read-only metadata. No telemetry, no changes to `ConversationDetailsPanelAction`, `ConversationDetailsPanelEvent`, or any caller of `set_conversation_details`.
|
||||
## Testing and validation
|
||||
Behavior invariants from `PRODUCT.md` map as follows:
|
||||
- Invariants 1, 4, 5, 6 — unit tests on `ConversationDetailsData.harness`, added to `app/src/ai/conversation_details_panel_tests.rs`:
|
||||
- `from_conversation` with no `server_metadata` → `Some(Harness::Oz)`; with `server_metadata.harness = AIAgentHarness::ClaudeCode` → `Some(Harness::Claude)` (and analogous for Gemini).
|
||||
- `from_conversation_metadata` is a pass-through: each of `Some(Oz)` / `Some(Claude)` / `Some(Gemini)` / `None` round-trips into the data struct. (Resolution logic lives in the caller; that's exercised end-to-end via manual verification rather than a dedicated unit test, since it requires a populated `BlocklistAIHistoryModel`.)
|
||||
- `from_task_id` → `None`.
|
||||
- `from_task` with `agent_config_snapshot = None` → `None`.
|
||||
- `from_task` with `agent_config_snapshot = Some { harness: None, .. }` → `Some(Harness::Oz)`.
|
||||
- `from_task` with `harness_type` each of `"oz"`, `"claude"`, `"gemini"` → matching `Harness` variant.
|
||||
- `from_task` with an unknown `harness_type` string → `None`.
|
||||
- Parametrize one of the above across each `AmbientAgentTaskState` variant to lock invariant 5 ("regardless of run status").
|
||||
- Invariants 2, 3 (icon + label + brand color mapping) — unit tests on the new `harness_display` module covering `parse_harness_type` edge cases. The `display_name` / `icon_for` / `brand_color` mappings are enum match arms and don't get dedicated tests; instead the harness selector's existing item rows and the details row resolve from the same shared helper so they can't drift.
|
||||
- Invariants 7, 8, 9, 10 — covered structurally: `render_harness_section` has no click handler / mouse state, uses `with_selectable(true)`, is inserted at a fixed offset in `View::render`, and the panel is the sole renderer across all hosting surfaces. Verified by a manual smoke check rather than a dedicated test.
|
||||
- Manual verification — `cargo run` and, in order: (a) open a local conversation's details panel → row shows "Warp Agent" + `Icon::Warp` in theme foreground; (b) open a cloud task launched with default config → row shows "Warp Agent"; (c) open a cloud task launched with `--harness claude` → row shows "Claude Code" + `ClaudeLogo` tinted Claude orange; (d) open a shared task that still resolves via `from_task_id` before the task loads → confirm no placeholder row, then confirm the row appears once the task payload arrives without rows above visibly jumping.
|
||||
- Pre-PR — `./script/presubmit` (cargo fmt, cargo clippy, cargo nextest) per repo rules. No WASM-specific paths are added, so WASM build should be unaffected.
|
||||
## Risks and mitigations
|
||||
- **Renaming "Oz" → "Warp Agent" in the existing harness selector.** Separate user-visible change on another surface. Required for invariant 2; centralizing in `harness_display.rs` prevents drift. If contested, we can parameterize per-surface without re-splitting the spec.
|
||||
- **Unknown `harness_type` strings from newer server versions.** Treating unknown strings as `None` means future harnesses temporarily render no row on older clients — strictly better than a wrong label. Mitigation: add the new `Harness` variant in `warp_cli` when the server does.
|
||||
- **`from_task_id` → `from_task` transition causing layout jump.** Placing the row below Creator means growth is downward-only, so nothing above moves. If product later wants the row higher, we'd need a reserved slot or crossfade; defer until asked.
|
||||
Reference in New Issue
Block a user