first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
# Increment 1: entry schema and merge builder
|
||||
## Context
|
||||
This increment adds the normalized projection API behind existing UI code. `AgentConversationsModel` already stores tasks and conversations separately in `app/src/ai/agent_conversations_model.rs (844-1901)`, and `ConversationOrTask` currently provides display helpers over borrowed raw data in `app/src/ai/agent_conversations_model.rs (399-813)`. The migration should add the new API without removing or migrating those call sites yet.
|
||||
The builder must understand metadata-only conversations. `AIConversationMetadata` can represent local persisted conversations or cloud-only metadata in `app/src/ai/blocklist/history_model.rs (49-157)`. `merge_cloud_conversation_metadata` creates a local `AIConversationId` for new cloud-only metadata and records the server token in `server_token_to_conversation_id` in `app/src/ai/blocklist/history_model/conversation_loader.rs (334-453)`.
|
||||
## Proposed changes
|
||||
Add a module near `agent_conversations_model.rs`, for example `app/src/ai/agent_conversations_model/entry.rs` if splitting the file is practical, or keep the first pass in `agent_conversations_model.rs` to reduce churn. Define:
|
||||
- `AgentConversationEntry`
|
||||
- `AgentConversationEntryId`
|
||||
- `AgentConversationIdentity`
|
||||
- `AgentConversationDisplayData`
|
||||
- `AgentConversationProvenance`
|
||||
- `AgentConversationBackingData`
|
||||
- `AgentConversationCapabilities`
|
||||
Add an internal builder that consumes current model state and `AppContext`. The builder should:
|
||||
1. build a map from server token to local conversation id from history metadata and loaded conversations;
|
||||
2. build a map from run id to local conversation id using `conversation_id_for_agent_id`;
|
||||
3. create one entry for each `AmbientAgentTask`, keyed by `AgentConversationEntryId::AmbientRun(task.run_id())`;
|
||||
4. attach a matching local conversation id by run id first, then server token;
|
||||
5. attach server token from the task, matching conversation metadata, or loaded conversation;
|
||||
6. create `Conversation` entries for metadata/local conversations not already attached to an ambient run.
|
||||
The display-data derivation should reuse existing `ConversationOrTask` behavior where possible during this increment, but the new type should not expose `ConversationOrTask` publicly. It is acceptable for the builder to use temporary private helpers to avoid duplicating all display logic in the first PR.
|
||||
Capabilities should be conservative in this increment. `can_open` should mean the entry has at least one of: open ambient run id, local conversation id, or server token. `can_share`, `can_delete`, and `can_fork_locally` can mirror current behavior but should remain data booleans, not actions.
|
||||
Add a new read API:
|
||||
```rust
|
||||
pub fn get_entries(
|
||||
&self,
|
||||
filters: &AgentManagementFilters,
|
||||
app: &AppContext,
|
||||
) -> Vec<AgentConversationEntry>
|
||||
```
|
||||
Returning a `Vec` is acceptable for the first pass because entries are owned snapshots and list sizes are small. Keep the existing `get_tasks_and_conversations` API for current UI.
|
||||
## Testing and validation
|
||||
Add unit tests in `app/src/ai/agent_conversations_model_tests.rs` for builder identity and dedupe behavior:
|
||||
- task-only entry uses `AmbientRun` id and `AmbientRun` provenance;
|
||||
- local-only entry uses `Conversation` id and `LocalInteractive` provenance;
|
||||
- cloud metadata-only entry uses `Conversation` id and `CloudSyncedConversation` provenance with `has_cloud_data = true` and `has_loaded_conversation = false`;
|
||||
- task plus local conversation matched by run id produces one `AmbientRun` entry with both ids attached;
|
||||
- task plus local conversation matched by server token produces one `AmbientRun` entry with both ids attached;
|
||||
- unrelated task and conversation produce two entries;
|
||||
- child-agent cloud metadata skipped today by metadata merge remains skipped from entries.
|
||||
Run the focused test module after implementation. This increment should not change rendered UI, so existing conversation list and management view behavior should remain unchanged.
|
||||
## Risks and mitigations
|
||||
### Duplicates from incomplete indexing
|
||||
Build token and run-id indices before constructing entries. Keep the “ambient run owns row identity” rule centralized in one builder function.
|
||||
### Too much display duplication
|
||||
It is fine to temporarily delegate to private helpers based on `ConversationOrTask` as long as the public API is normalized. Increment 4 should remove the old helper path.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Increment 2: navigation resolver and conversation list migration
|
||||
## Context
|
||||
The conversation list currently caches `ConversationOrTaskId`s in `ConversationListViewModel` and filters out task rows whose `get_session_status()` is not available in `app/src/workspace/view/conversation_list/view_model.rs (31-183)`. Rendering checks `conversation.get_open_action(None, app)` for cursor/clickability in `app/src/workspace/view/conversation_list/item.rs (377-407)`, and activation recomputes the action in `app/src/workspace/view/conversation_list/view.rs (535-548)`.
|
||||
This can fail when a visible task row shadows a local conversation row but lacks fresh session/conversation fields. Increment 2 replaces that path with normalized entries and a dynamic navigation resolver.
|
||||
## Proposed changes
|
||||
Add an entry navigation resolver near `AgentConversationsModel`, for example:
|
||||
```rust
|
||||
pub enum AgentConversationNavigationSubject {
|
||||
Entry(AgentConversationEntryId),
|
||||
ServerToken(ServerConversationToken),
|
||||
}
|
||||
|
||||
pub fn resolve_open_action(
|
||||
subject: AgentConversationNavigationSubject,
|
||||
restore_layout: Option<RestoreConversationLayout>,
|
||||
app: &AppContext,
|
||||
) -> Option<WorkspaceAction>
|
||||
```
|
||||
The resolver should re-read current state at click time. For `Entry`, resolve the latest `AgentConversationEntry` or equivalent identity refs from `AgentConversationsModel`. For `ServerToken`, resolve through `BlocklistAIHistoryModel::find_conversation_id_by_server_token` and fall back to transcript loading where the caller supports it.
|
||||
Default resolver order for listed entries:
|
||||
1. if an ambient run id is attached and `ActiveAgentViewsModel` has an open ambient session, focus/open that tab;
|
||||
2. if a local conversation id is attached and currently open, focus it;
|
||||
3. if the ambient run has an active execution with parseable `session_id`, open ambient shared session;
|
||||
4. if a local conversation id is attached, restore/navigate to the local conversation with the requested layout;
|
||||
5. if a server token is attached, open the cloud transcript viewer;
|
||||
6. otherwise return `None`.
|
||||
The resolver may initially return existing `WorkspaceAction` variants. If focusing an already-open ambient session still relies on `WorkspaceAction::OpenAmbientAgentSession` plus workspace fallback, keep that behavior but ensure the resolver prefers the open ambient identity before transcript fallback.
|
||||
Migrate `ConversationListViewModel` to cache `AgentConversationEntryId` and `ConversationEntry { id, highlight_indices }`. It should source entries from `AgentConversationsModel::get_entries` with the same personal/all status defaults currently used by the conversation list. Do not filter out completed cloud entries simply because `get_session_status()` is unavailable; filter based on normalized `capabilities.can_open`.
|
||||
Migrate `render_item` props to take `AgentConversationEntry` or a lightweight view data struct instead of `ConversationOrTask`. The leading icon can continue to use existing helper behavior if Increment 1 exposes enough display data; otherwise add a normalized icon helper that consumes `AgentConversationEntry`.
|
||||
Migrate click/Enter to call `resolve_open_action(Entry(entry.id), None, ctx)`.
|
||||
## Testing and validation
|
||||
Add unit tests for the resolver:
|
||||
- task row with matching local conversation but missing task `conversation_id` restores local conversation;
|
||||
- task row with `session_link` but no parseable `session_id` does not claim session-open capability and falls back to local/server token when available;
|
||||
- active ambient session is preferred over transcript opening;
|
||||
- active local conversation is preferred over restoring into a new tab;
|
||||
- server-token-only navigation can open transcript when no local id is known.
|
||||
Add conversation-list view-model tests if existing harness support is sufficient:
|
||||
- cloud metadata-only entries appear when openable by token;
|
||||
- stale/unavailable session status does not hide a restorable local conversation attached to a task;
|
||||
- search still matches titles from normalized display data.
|
||||
Manual validation:
|
||||
- open a local cloud-mode conversation that also has a task row and verify clicking the list item focuses/restores the local conversation even if the task has no active session;
|
||||
- open a live cloud task and verify clicking the list item focuses/joins the live session;
|
||||
- open a completed cloud run and verify clicking opens/restores transcript/local conversation consistently.
|
||||
## Risks and mitigations
|
||||
### Workspace action gaps
|
||||
The existing workspace actions may not express “focus open ambient session by task id” directly. If needed, add a small focused action or keep using `OpenAmbientAgentSession` with the workspace’s `find_tab_with_ambient_agent_conversation` fallback.
|
||||
### UI state churn
|
||||
Changing list item IDs can reset hover/selection state. Use `AgentConversationEntryId` as a stable key and preserve row state maps by that key.
|
||||
@@ -0,0 +1,45 @@
|
||||
# Increment 3: Agent Management and details migration
|
||||
## Context
|
||||
Agent Management currently calls `get_tasks_and_conversations` and then maps each `ConversationOrTask` into card state, artifacts, action buttons, copy links, and details-panel data in `app/src/ai/agent_management/view.rs (938-1357)`. Cards use `get_open_action(Some(NewTab), app)` for clickability in `app/src/ai/agent_management/view.rs (1710-1755)` and dispatch it from `AgentManagementViewAction::OpenSession` in `app/src/ai/agent_management/view.rs (2324-2379)`.
|
||||
This surface also owns filtering by status, source, environment, harness, creator, created date, and artifacts. Those filters should move to normalized entries so they are based on the same merged identity/display data that drives navigation.
|
||||
## Proposed changes
|
||||
Replace the management list’s `ManagementCardItemId` task/conversation split with `AgentConversationEntryId`. Card construction should use `AgentConversationEntry` fields directly:
|
||||
- title from `display.title`;
|
||||
- status from `display.status`;
|
||||
- source from `display.source`;
|
||||
- environment from `display.environment_id`;
|
||||
- harness from `display.harness`;
|
||||
- artifacts from `display.artifacts`;
|
||||
- creator from `display.creator`;
|
||||
- request usage and runtime from `display`.
|
||||
Move filtering to the entry builder or add an entry-specific filter function. Prefer one filtering path that can be shared by the conversation list and Agent Management, with owner-specific behavior parameterized by `AgentManagementFilters`.
|
||||
Replace card click and details-panel open actions with `resolve_open_action(Entry(entry.id), Some(RestoreConversationLayout::NewTab), ctx)`.
|
||||
Replace copy-link derivation with a sibling resolver:
|
||||
```rust
|
||||
pub fn resolve_copy_link(
|
||||
subject: AgentConversationNavigationSubject,
|
||||
app: &AppContext,
|
||||
) -> Option<String>
|
||||
```
|
||||
This should share identity resolution with `resolve_open_action` and avoid a separate `link_preference()` policy. Link policy can prefer a live session URL for active executions and otherwise use the server conversation token when available.
|
||||
Update details panel construction so both task-backed and conversation-backed entries go through one normalized path. Keep raw task-only fields where they are truly run-specific, but they should come from attached run identity rather than from a separate card variant.
|
||||
## Testing and validation
|
||||
Add unit tests for entry filtering:
|
||||
- stale raw `InProgress` task with terminal matching conversation filters into Done/Failed according to `AgentRunDisplayStatus`;
|
||||
- environment filter includes local/cloud conversation entries as “None” and filters task environments correctly;
|
||||
- harness filter handles task harness, local Oz conversation, and metadata-only cloud conversation;
|
||||
- artifact filter works for artifacts sourced from task, loaded conversation, and metadata.
|
||||
Add tests for copy-link resolver:
|
||||
- active joinable session returns session link;
|
||||
- non-active cloud-backed entry returns conversation link;
|
||||
- local-only unsynced conversation returns no link;
|
||||
- task with no token but attached local synced conversation returns conversation link.
|
||||
Manual validation:
|
||||
- Agent Management card click and details “Open” use the same destination as the conversation list for the same logical run;
|
||||
- copy-link button and card click no longer disagree for completed cloud runs;
|
||||
- details panel displays one coherent entry when task and local conversation both exist.
|
||||
## Risks and mitigations
|
||||
### Details panel field regressions
|
||||
Some fields are genuinely task/run-specific. Keep `AgentConversationIdentity.ambient_run_id` available and fetch raw task data only for fields not represented in `display`.
|
||||
### Filter behavior changes
|
||||
Normalized filtering may intentionally move stale task rows between status buckets. Preserve current behavior only where it matches the derived display status policy.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Increment 4: cleanup and hardening
|
||||
## Context
|
||||
After conversation list and Agent Management migrate to `AgentConversationEntry`, `ConversationOrTask` should no longer be the public model consumed by list/navigation/details surfaces. The old `link_preference()` helper in `app/src/ai/agent_conversations_model.rs (624-660)` should stop being a source of truth for open and copy-link behavior.
|
||||
This increment removes transitional APIs, hardens event invalidation, and adds regression coverage around the originally inconsistent cases.
|
||||
## Proposed changes
|
||||
Remove or narrow public access to:
|
||||
- `ConversationOrTask`
|
||||
- `ConversationOrTaskId` usage in list/navigation surfaces
|
||||
- `ConversationOrTask::get_open_action`
|
||||
- `ConversationOrTask::session_or_conversation_link`
|
||||
- `ConversationOrTask::link_preference`
|
||||
If some internal helpers remain useful, make them private to the entry builder and rename them so they cannot be mistaken for the public entry API.
|
||||
Audit all `get_open_action`, `session_or_conversation_link`, and `get_session_status` call sites. Remaining navigation should go through `resolve_open_action`, and remaining copy-link behavior should go through `resolve_copy_link`.
|
||||
Review event handling in `AgentConversationsModel`:
|
||||
- task updates should invalidate/re-emit entry updates;
|
||||
- conversation status updates should refresh derived status and capabilities;
|
||||
- server token assignment should update merged identity;
|
||||
- cloud metadata merge should update entries;
|
||||
- active view open/close/focus should update active/open capabilities without requiring stale nav data.
|
||||
If existing `AgentConversationsModelEvent` variants are too ambiguous, add a normalized event such as:
|
||||
```rust
|
||||
pub enum AgentConversationsModelEvent {
|
||||
EntriesChanged,
|
||||
EntryDisplayDataChanged { id: AgentConversationEntryId },
|
||||
EntryArtifactsChanged { id: AgentConversationEntryId },
|
||||
}
|
||||
```
|
||||
Only do this if it reduces caller complexity; avoid event churn if all migrated consumers can simply rebuild from `get_entries`.
|
||||
Update comments and docs in the model to describe the new ownership boundary: raw task/conversation caches are source data, while `AgentConversationEntry` is the UI/navigation projection.
|
||||
## Testing and validation
|
||||
Add regression tests named around the fixed behaviors:
|
||||
- task shadows local conversation but missing task token still opens via local conversation;
|
||||
- stale active execution no longer forces session open when no parseable session id exists;
|
||||
- completed cloud run with token remains openable even without session link;
|
||||
- copy-link and open resolver use consistent source priority;
|
||||
- metadata-only cloud conversation can be opened by server token without a loaded `AIConversation`;
|
||||
- server token assignment after an entry is first built updates identity/copy-link behavior.
|
||||
Run all focused tests touched during increments 1-3 plus a targeted compile/check. Before review, run repository-required formatting and linting commands.
|
||||
## Risks and mitigations
|
||||
### Hidden call sites
|
||||
Use grep for removed method names and old ID types. Keep this increment small and mechanical where possible.
|
||||
### Event overengineering
|
||||
Prefer simple rebuild-on-event behavior until performance requires finer invalidation. The entry list is small enough that correctness is more important than micro-optimizing derived state.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Agent conversation entry normalization migration
|
||||
## Context
|
||||
Linear: https://linear.app/warpdotdev/issue/APP-4382/normalize-agent-conversation-entries-for-list-and-navigation-surfaces
|
||||
`AgentConversationsModel` currently exposes `ConversationOrTask`, a wrapper over either `AmbientAgentTask` or `ConversationMetadata`, to list, details, filtering, and navigation surfaces in `app/src/ai/agent_conversations_model.rs (399-813)`. The wrapper centralizes several display helpers, but still encodes important behavior as “task vs conversation” decisions. The most problematic example is `link_preference()` and `get_open_action()` in `app/src/ai/agent_conversations_model.rs (624-813)`: task rows choose between session, conversation transcript, or no action from cached task fields, while local conversation rows restore/navigate from `ConversationNavigationData`.
|
||||
The model also intentionally hides local conversation rows when a task appears to represent the same logical run in `app/src/ai/agent_conversations_model.rs (1382-1497)`. That shadowing is useful for preserving cloud-run affordances, but it means the visible task row can discard better local-conversation navigation data. A task row with a stale or incomplete `session_id`, `session_link`, `conversation_id`, or `is_sandbox_running` can therefore be unopenable even when the hidden conversation representation is restorable.
|
||||
The underlying sources are already separate. `BlocklistAIHistoryModel` owns loaded `AIConversation` contents in `conversations_by_id` and lightweight historical/cloud metadata in `all_conversations_metadata` in `app/src/ai/blocklist/history_model.rs (49-214)`. Cloud metadata can exist without a loaded conversation via `AIConversationMetadata` and `merge_cloud_conversation_metadata` in `app/src/ai/blocklist/history_model/conversation_loader.rs (334-453)`, while `load_conversation_data` loads content only on demand in `app/src/ai/blocklist/history_model/conversation_loader.rs (145-236)`. `ActiveAgentViewsModel` separately tracks which local conversations and ambient sessions are currently open in `app/src/ai/active_agent_views_model.rs (45-531)`.
|
||||
This migration introduces a projection-layer `AgentConversationEntry` for list/navigation/detail surfaces. It should not replace `AIConversation`, `BlocklistAIHistoryModel`, or transcript content APIs. Its job is to merge provenance and lightweight display data into one logical row so UI surfaces no longer choose between task and conversation representations.
|
||||
## Proposed changes
|
||||
### Target model boundary
|
||||
Add a normalized entry API to `AgentConversationsModel` while keeping raw task and conversation storage as implementation details:
|
||||
```rust
|
||||
pub struct AgentConversationEntry {
|
||||
pub id: AgentConversationEntryId,
|
||||
pub identity: AgentConversationIdentity,
|
||||
pub display: AgentConversationDisplayData,
|
||||
pub provenance: AgentConversationProvenance,
|
||||
pub backing: AgentConversationBackingData,
|
||||
pub capabilities: AgentConversationCapabilities,
|
||||
}
|
||||
```
|
||||
`AgentConversationEntry` is a projection. It carries owned IDs and display snapshots, not borrowed references to `AmbientAgentTask` or `AIConversation`. Callers that need transcript contents still go through `BlocklistAIHistoryModel` and loader APIs.
|
||||
Use local/client identities for listed entries:
|
||||
```rust
|
||||
pub enum AgentConversationEntryId {
|
||||
AmbientRun(AmbientAgentTaskId),
|
||||
Conversation(AIConversationId),
|
||||
}
|
||||
```
|
||||
Do not use `ServerConversationToken` as a normal list identity. Server tokens are global fetch/navigation handles; cloud-only metadata rows already receive a local `AIConversationId` when metadata is merged.
|
||||
The entry identity should preserve every known reference:
|
||||
```rust
|
||||
pub struct AgentConversationIdentity {
|
||||
pub local_conversation_id: Option<AIConversationId>,
|
||||
pub ambient_run_id: Option<AmbientAgentTaskId>,
|
||||
pub server_conversation_token: Option<ServerConversationToken>,
|
||||
pub parent_conversation_id: Option<AIConversationId>,
|
||||
pub parent_run_id: Option<AmbientAgentTaskId>,
|
||||
}
|
||||
```
|
||||
Keep provenance semantic and move loadability into backing:
|
||||
```rust
|
||||
pub enum AgentConversationProvenance {
|
||||
LocalInteractive,
|
||||
AmbientRun,
|
||||
CloudSyncedConversation,
|
||||
}
|
||||
|
||||
pub struct AgentConversationBackingData {
|
||||
pub has_loaded_conversation: bool,
|
||||
pub has_local_persisted_data: bool,
|
||||
pub has_cloud_data: bool,
|
||||
pub has_ambient_run: bool,
|
||||
}
|
||||
```
|
||||
`AgentConversationDisplayData` should centralize list/detail fields currently read from `ConversationOrTask`: title, initial query, created/updated timestamps, `AgentRunDisplayStatus`, creator, working directory, source, environment, harness, request usage, run time, and artifacts. The display status should continue to use the derived `AgentRunDisplayStatus` algorithm instead of raw `AmbientAgentTaskState` or raw `ConversationStatus`.
|
||||
`AgentConversationCapabilities` should expose eligibility booleans for UI affordances such as open, copy link, share, delete, fork locally, continue locally, and cancel. It must not store the final `WorkspaceAction`. Open actions should be resolved dynamically from entry identity at click time.
|
||||
### Increment sequence
|
||||
Increment 1, `INCREMENT-1-entry-schema-and-builder.md`, adds the entry schema, merge builder, and tests without migrating UI. It introduces `AgentConversationsModel::get_entries(...)` or equivalent behind existing code paths.
|
||||
Increment 2, `INCREMENT-2-navigation-and-conversation-list.md`, adds a dynamic navigation resolver and migrates the conversation list to normalized entries. This is the first user-visible behavior fix for task rows that shadow restorable conversations.
|
||||
Increment 3, `INCREMENT-3-agent-management-and-details.md`, migrates Agent Management cards, filters, action buttons, and details panel data to normalized entries.
|
||||
Increment 4, `INCREMENT-4-cleanup-and-hardening.md`, removes or narrows `ConversationOrTask`, deletes `link_preference()` as a source of truth, and adds regression coverage for previously inconsistent entrypoints.
|
||||
## End-to-end flow
|
||||
1. `AgentConversationsModel` keeps its existing task and conversation caches.
|
||||
2. The entry builder indexes tasks by stable run id, conversations by local id, and metadata by server token.
|
||||
3. For each ambient run, the builder creates one `AmbientRun` entry and attaches matching local conversation/server-token data when available.
|
||||
4. For each local/cloud metadata conversation not already attached to a run, the builder creates one `Conversation` entry.
|
||||
5. List/detail surfaces render from `AgentConversationEntry`.
|
||||
6. On click, the navigation resolver re-reads current task, history, active-view, and workspace state before producing a `WorkspaceAction`.
|
||||
## Testing and validation
|
||||
The migration should be implemented as stacked PRs. Each increment spec lists its focused test coverage. Across the full migration, add tests for:
|
||||
- local conversation only
|
||||
- cloud metadata only with `has_local_data = false`
|
||||
- task only with active joinable session
|
||||
- task only with terminal state and server token
|
||||
- task plus local conversation by run id
|
||||
- task plus local conversation by server token
|
||||
- task with stale in-progress state but terminal local conversation status
|
||||
- task with `session_link` but no parseable `session_id`
|
||||
- existing open local conversation focus
|
||||
- existing open ambient session focus
|
||||
- copied link and open action using the same resolved identity data
|
||||
Before opening PRs, run the focused `agent_conversations_model` tests for each increment and a targeted app check. Follow repo PR workflow before review.
|
||||
## Risks and mitigations
|
||||
### Merge identity regressions
|
||||
Wrong merge rules can duplicate rows or hide rows. Mitigate by writing the merge tests before migrating UI and keeping merge precedence explicit: ambient run owns the UI identity when present, but local conversation/server token refs remain attached.
|
||||
### Recreating stale navigation in a new type
|
||||
If entries cache `WorkspaceAction`, they will reproduce the current bug. Store identities and capabilities only; resolve final actions at click time.
|
||||
### Overreaching into transcript content
|
||||
`AgentConversationEntry` should not expose exchanges, block data, or content mutation methods. Keep transcript operations in `BlocklistAIHistoryModel` and loader paths.
|
||||
### Incomplete event invalidation
|
||||
Entries depend on task updates, history events, metadata updates, and active view changes. Initially prefer rebuilding entries on any relevant `AgentConversationsModelEvent` instead of maintaining fine-grained derived caches.
|
||||
## Parallelization
|
||||
Increment 1 should be implemented first and mostly sequentially because later increments depend on the entry schema. After that, Increment 2 and Increment 3 can be implemented on separate stacked branches if the entry API is stable. Increment 4 should wait until both UI migrations land.
|
||||
Reference in New Issue
Block a user