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
@@ -0,0 +1,62 @@
# Tombstone metadata for cloud agent runs — Product Spec
Linear: placeholder. Figma: none.
## Summary
For cloud agent runs (Oz and non-Oz harnesses like Claude Code), make the conversation-ended tombstone show `Run time`, `Credits used`, and the artifact buttons row (plans, PRs, files, screenshots) from the `AmbientAgentTask` when one is present, matching the conversation details panel for the same run.
Today the tombstone reads run time, credits, and artifacts off the in-memory `AIConversation`. For non-Oz cloud runs there is no `AIConversation`, so all three render empty. For cloud Oz runs the conversation values diverge from the details panel: run time excludes queue/sandbox lifecycle, credits exclude compute cost, and artifacts can lag the task-side copy.
## Goals
- Non-Oz cloud tombstones render `Run time`, `Credits used`, and the artifact buttons row populated from the `AmbientAgentTask`.
- Cloud Oz tombstones converge on the same task-derived values, matching `ConversationDetailsData::from_task` numerically and on artifacts.
- Hide the `Continue locally` button (tombstone + details panel) for tasks whose harness is non-Oz, since forking a non-Oz cloud run into a local Warp conversation is unsupported.
- Client-only change: no harness, server, schema, or endpoint work.
## Non-goals
- `Directory:` / `Branch:` segments on non-Oz tombstones.
- Implementing `Continue locally` for third-party harnesses (button is hidden, not wired up).
- Splitting credits into Inference + Compute sub-rows like the details panel does. The tombstone keeps a single aggregated `Credits used: X.X` segment because the `•`-separated row has no room.
## Behavior
The tombstone metadata row segments behave as follows. Source-of-truth rules mirror `ConversationDetailsData::from_task`.
- **Run time** (`Run time: <human-readable>`):
- Cloud (task present): `AmbientAgentTask::run_time()`. Omitted if `started_at` missing.
- Local (no task): unchanged. Exchange-derived.
- **Credits used** (`Credits used: <formatted via format_credits>`):
- Cloud (task present): `AmbientAgentTask::credits_used()` (`inference + compute`). Omitted if `request_usage` missing.
- Local (no task): unchanged. `conversation.credits_spent()`.
- **Source / Skill**: unchanged (already enriched from the task).
- **Working directory**: Oz unchanged (`AIConversation::initial_working_directory()`); non-Oz not rendered.
- **Artifacts row** (plan, branch, PR, screenshot, file buttons):
- Cloud (task present, non-empty `task.artifacts`): `task.artifacts`. Wins over conversation artifacts.
- Cloud (task present, empty `task.artifacts`): falls back to whatever `from_conversation` set (avoids blanking Oz when the task is partially populated).
- Local (no task): unchanged. `conversation.artifacts()`.
- **`Continue locally` button** (tombstone desktop + details panel):
- Hide rule is purely on harness: hide iff `harness == Some(non-Oz)`. `None` (unknown / not yet loaded / plain conversation) and `Some(Oz)` both show the button.
- Local (no task): unchanged — harness stays `None`, shown when AI is enabled.
- Cloud Oz task: shown both before and after task load.
- Cloud non-Oz task (Claude, Gemini): button shows briefly until task fetch resolves, then hides.
- Tombstone wasm `Open in Warp` button: unchanged. Opens the same conversation in the desktop client, where the same hide rules then apply.
Empty-row, error, and snapshot/transcript-viewer behaviors are unchanged.
## Success criteria
- For any cloud run with an `AmbientAgentTask` (Oz or non-Oz), the tombstone's `Run time` and `Credits used` strings equal what `ConversationDetailsData::from_task` produces for the same task.
- Non-Oz cloud tombstones with a populated `task.artifacts` render the same artifact buttons as the details panel for the same run.
- Cloud Oz tombstone numbers will visibly change: both increase. New values match the details panel and Oz task list.
- Cloud non-Oz tombstones and details panels do not render `Continue locally` once the task is loaded.
- Cloud Oz tombstones and details panels still render `Continue locally`.
- Local Oz tombstone behavior is unchanged.
- Non-Oz tombstones remain feature-flagged behind `FeatureFlag::AgentHarness`.
## Validation
- Unit tests on `enrich_from_task` covering: task wins for run time / credits / artifacts when present; falls back to `from_conversation` values when the corresponding task field is missing.
- Unit tests on `enrich_from_task` covering harness extraction: snapshot absent → harness stays `None`; snapshot without explicit harness → defaults to Oz; snapshot with explicit harness propagates.
- Manual: cloud Claude Code run shows non-empty `Run time`, `Credits used`, and artifact buttons matching the details panel, and `Continue locally` is hidden in both surfaces; cloud Oz run shows updated numbers and artifacts also matching the details panel, with `Continue locally` shown; local Oz run unchanged.
## Follow-ups
- Wasm parity (cloud tombstones on web don't currently fetch the task; pre-existing gap).
- `Directory:` / `Branch:` on non-Oz, via a future `POST /harness-support/report-context` sibling endpoint that piggy-backs in parallel with the existing transcript upload.
- Possible `Download conversation` button surfacing the harness JSON.
+87
View File
@@ -0,0 +1,87 @@
# Tombstone metadata for cloud agent runs — Tech Spec
Pairs with `PRODUCT.md`.
## Problem
`ConversationEndedTombstoneView` reads `run_time` / `credits` / `artifacts` off the in-memory `AIConversation`. Non-Oz cloud runs don't materialize one (they produce a `CLIAgentConversation`), so all three stay empty. Cloud Oz uses values that under-report relative to the details panel: exchange-derived run time excludes the cloud lifecycle; `credits_spent()` excludes compute cost; conversation-side artifacts can lag the task copy. The artifact buttons row is also constructed eagerly in the constructor and never refreshed, so the async task fetch has nothing to push artifacts into.
Separately, both the tombstone and `ConversationDetailsPanel` unconditionally surface the `Continue locally` button on non-transcript runs. `ContinueConversationLocally` only forks Oz-harness conversations, so for non-Oz tasks the button is dead UI.
## Relevant code
- `app/src/terminal/view/shared_session/conversation_ended_tombstone_view.rs` — `TombstoneDisplayData`, `from_conversation`, `enrich_from_task` (`#[cfg(not(target_family = "wasm"))]`), `render_metadata_row`, `render_action_buttons`.
- `app/src/ai/conversation_details_panel.rs` — `ConversationDetailsData::from_task` (~line 310), `ConversationDetailsData::harness`, `ConversationDetailsPanel::continue_locally_conversation_id`. Consistency reference: sets both fields straight from the task.
- `app/src/ai/ambient_agents/task.rs` — `AmbientAgentTask::run_time()` (`Option<chrono::Duration>`), `credits_used()` (`Option<f32>`), `AgentConfigSnapshot::harness` (`Option<HarnessConfig>`), `HarnessConfig::harness_type` (`Harness`).
- `crates/warp_cli/src/agent.rs` — `Harness` enum (`Oz`, `Claude`, `Gemini`).
- `format_credits` (`app/src/ai/blocklist/view_util.rs`) and `human_readable_precise_duration` (`app/src/util/time_format.rs`) — already imported by the tombstone.
## Change
Extend `enrich_from_task` to overwrite `run_time`, `credits`, and `artifacts` from the task whenever the task has values, and refresh the artifact buttons view after the async task fetch resolves. Mirrors `ConversationDetailsData::from_task`.
```rust path=null start=null
#[cfg(not(target_family = "wasm"))]
fn enrich_from_task(&mut self, task: AmbientAgentTask) {
// existing: title / source / skill / error ...
if let Some(d) = task.run_time() {
self.run_time = Some(human_readable_precise_duration(d));
}
if let Some(c) = task.credits_used() {
self.credits = Some(format_credits(c));
}
if !task.artifacts.is_empty() {
self.artifacts = task.artifacts;
}
}
```
The constructor now always materializes `artifact_buttons_view` (passing whatever `display_data.artifacts` start with, possibly empty), and the spawn callback that handles the task fetch calls `update_artifacts(&me.display_data.artifacts, ctx)` after enrichment. Render gates the row on `display_data.artifacts.is_empty()`.
### Continue Locally harness gate
`TombstoneDisplayData` gains one field: `harness: Option<Harness>`, defaulting to `None`. `enrich_from_task` sets it from `task.agent_config_snapshot`: snapshot present + explicit harness → `Some(harness)`; snapshot present + no explicit harness → `Some(Oz)` (default); snapshot absent → stays `None`.
`render_action_buttons` (cfg `not(wasm)`) and `ConversationDetailsPanel::continue_locally_conversation_id` both apply the same gate: `!matches!(harness, Some(h) if h != Harness::Oz)`. `None` and `Some(Oz)` both show the button; only confirmed non-Oz hides it. No `is_task` flag is needed — plain conversations and pre-load tasks naturally fall into the `None` bucket.
`ConversationDetailsData::from_task` already populates `harness`, so no upstream wiring changes are required for the panel.
Wasm `Open in Warp` is intentionally untouched: it just opens the same conversation in the desktop client, where the same gate hides `Continue locally`.
Notes:
- All three metadata fields are unguarded: when the task has a value, it wins. When it doesn't, whatever `from_conversation` populated (or default-empty for non-Oz) survives.
- Local Oz runs have no `task_id`, so `enrich_from_task` never runs and `from_conversation` remains the only source. No regression there.
- Tombstone keeps a single aggregated `Credits used: X.X` segment; the panel's inference/compute split (`render_credits_with_split`) is panel-only.
- The `artifact_buttons_view` field is no longer `Option`. The view returns `Empty` when its button list is empty, but we still render-gate to avoid the surrounding margin container.
- The harness gate is permissive on `None`: cloud non-Oz tasks briefly show the button until the fetch resolves and switches `harness` to `Some(non-Oz)`.
- No new fetches, no schema work, no GraphQL changes.
## Risks
- **Cloud Oz numbers visibly change** (both up). Intentional; matches the details panel for the same run. No flag-gating; if surprising in dogfood, can hide behind `FeatureFlag::AgentHarness` after the fact.
- **Brief flash** as `from_conversation` values render before the async task fetch resolves and overwrites them. Same race the panel has today; acceptable.
- **`Continue locally` flashes briefly on non-Oz tombstones** before the task fetch resolves. Conscious trade-off: hiding eagerly would also hide the button on local conversations and cloud Oz runs (we don't know the harness yet), which is worse.
- **Snapshot missing harness on a non-Oz run** mis-shows `Continue locally` because `Some(snapshot) + harness == None` defaults to `Some(Oz)`. In practice the server always populates `harness` when a non-Oz run is dispatched; if that ever changes the gate becomes too permissive.
- **Wasm**: `enrich_from_task` is `cfg(not wasm)`, so cloud tombstones on web are unchanged. Pre-existing gap shared with the panel. The wasm `Open in Warp` button stays visible because it just routes the user to the desktop tombstone, which then enforces the gate.
- **Partial task data**: if `run_time()` or `credits_used()` is `None`, we leave whatever `from_conversation` set (avoids blanking Oz when the task is partially populated).
## Tests
Unit tests next to `conversation_ended_tombstone_view.rs`:
1. Oz with task: run time / credits overwritten by task values.
2. Oz with task missing those fields: `from_conversation` values preserved.
3. Non-Oz happy path: empty defaults populated from a fully-populated task.
4. Task artifacts populate empty defaults.
5. Task artifacts override conversation artifacts.
6. Empty `task.artifacts` preserve conversation artifacts.
7. Task without `agent_config_snapshot` leaves `harness == None`.
8. Snapshot without explicit harness defaults to `Some(Harness::Oz)`.
9. Snapshot with explicit harness propagates `Some(harness)` for each variant.
Details panel tests (`conversation_details_panel_tests.rs`) already cover `from_task` populating `harness` for each variant and `from_conversation_metadata` passing it through, which is the input to the new `continue_locally_conversation_id` gate.
No cross-surface test: tests 1 and 3 already assert tombstone output equals `human_readable_precise_duration(task.run_time())` and `format_credits(task.credits_used())`, which is the same data path the panel feeds into `ConversationDetailsData::from_task`. A literal panel-vs-tombstone string comparison would just rerun the same formatters and require widening visibility on private panel internals.
Manual: cloud Claude Code (`Continue locally` hidden in both surfaces once the task loads), cloud Oz (numbers should match the details panel; `Continue locally` shown), local Oz (unchanged). Run `./script/presubmit` before PR.
## Follow-ups
- Wasm parity (fetch the task and run `enrich_from_task` on web; share with the panel's wasm path).
- `Directory:` / `Branch:` for non-Oz via a future `POST /harness-support/report-context` sibling endpoint piggy-backed under `try_join!` with the existing uploads.
- Shared helper translating `AmbientAgentTask` → display strings if a third surface ever needs it.
- If/when `ContinueConversationLocally` learns to fork non-Oz runs, drop the harness gate (or invert it to whatever the new constraint is).