feat(chat): livre la CLI custom de chat agent (#147) et corrige Cancel
Implémente la vue chat structurée par cellule agent (toggle TUI/CLI custom, préférence persistée `preferred_view`, reattach live, composer + pièces jointes) avec le socle backend AgentSession/ChatBridge (UserPrompt, cancel_current_turn, routage interrupt_agent, commande cancel_agent_chat). Corrige le bug bloquant relevé par QA : le bouton Cancel de CustomAgentChatView interrompait tout le tour via closeAgentChat au lieu de n'annuler que le tour courant via cancelAgentChat, ce qui tuait la session contrairement au contrat produit validé. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -10,7 +10,7 @@ use std::sync::Arc;
|
||||
use domain::ports::{EventBus, FileSystem, ProjectStore};
|
||||
use domain::{
|
||||
AgentId, Direction, DomainEvent, LayoutError, LayoutId, LayoutTree, LeafCell, NodeId,
|
||||
ProjectId, SessionId,
|
||||
PreferredView, ProjectId, SessionId,
|
||||
};
|
||||
|
||||
use crate::error::AppError;
|
||||
@ -98,6 +98,13 @@ pub enum LayoutOperation {
|
||||
/// Conversation id to record, or `None` to clear.
|
||||
conversation_id: Option<String>,
|
||||
},
|
||||
/// Persist the preferred live view for an agent leaf.
|
||||
SetCellPreferredView {
|
||||
/// The hosting leaf.
|
||||
target: NodeId,
|
||||
/// Preferred live view.
|
||||
preferred_view: PreferredView,
|
||||
},
|
||||
/// Persist opaque state for a plugin-provided custom layout leaf.
|
||||
SetPluginLayoutState {
|
||||
/// The custom plugin layout node.
|
||||
@ -130,6 +137,10 @@ impl LayoutOperation {
|
||||
target,
|
||||
conversation_id,
|
||||
} => tree.set_cell_conversation(*target, conversation_id.clone()),
|
||||
Self::SetCellPreferredView {
|
||||
target,
|
||||
preferred_view,
|
||||
} => tree.set_cell_preferred_view(*target, *preferred_view),
|
||||
Self::SetPluginLayoutState { target, state } => {
|
||||
tree.set_plugin_layout_state(*target, state.clone())
|
||||
}
|
||||
|
||||
@ -590,6 +590,7 @@ fn agent_leaf(
|
||||
conversation_id: conversation_id.map(str::to_owned),
|
||||
engine_session_id: None,
|
||||
agent_was_running,
|
||||
preferred_view: domain::PreferredView::Tui,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,8 +20,8 @@ use domain::ports::{
|
||||
use domain::{
|
||||
AgentId, ContentHash, Direction, LayoutId, LayoutNode, LayoutTree, LeafCell, LocalPath, NodeId,
|
||||
PluginBundleUrl, PluginId, PluginInstallSource, PluginLifecycleState, PluginPackageRef,
|
||||
PluginRegistry, PluginRegistryEntry, Project, ProjectId, ProjectPath, RelativePath, RemoteRef,
|
||||
RemovalOutcome, SessionId, StagedPluginPackage,
|
||||
PluginRegistry, PluginRegistryEntry, PreferredView, Project, ProjectId, ProjectPath,
|
||||
RelativePath, RemoteRef, RemovalOutcome, SessionId, StagedPluginPackage,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -350,6 +350,7 @@ fn single_leaf(node_id: NodeId) -> LayoutTree {
|
||||
conversation_id: None,
|
||||
engine_session_id: None,
|
||||
agent_was_running: false,
|
||||
preferred_view: domain::PreferredView::Tui,
|
||||
})
|
||||
}
|
||||
|
||||
@ -936,6 +937,49 @@ async fn mutate_set_cell_conversation_missing_leaf_is_not_found() {
|
||||
assert_eq!(err.code(), "NOT_FOUND", "got {err:?}");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn mutate_set_cell_preferred_view_persists_chat_then_tui_default() {
|
||||
let env = mut_env(pid(54)).await;
|
||||
|
||||
env.mutate
|
||||
.execute(MutateLayoutInput {
|
||||
project_id: env.project_id,
|
||||
layout_id: None,
|
||||
operation: LayoutOperation::SetCellPreferredView {
|
||||
target: nid(1),
|
||||
preferred_view: PreferredView::Chat,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.expect("set_cell_preferred_view records chat");
|
||||
|
||||
let tree_json = active_tree_json(&env.fs);
|
||||
assert_eq!(tree_json["root"]["node"]["preferredView"], "chat");
|
||||
|
||||
let out = env
|
||||
.mutate
|
||||
.execute(MutateLayoutInput {
|
||||
project_id: env.project_id,
|
||||
layout_id: None,
|
||||
operation: LayoutOperation::SetCellPreferredView {
|
||||
target: nid(1),
|
||||
preferred_view: PreferredView::Tui,
|
||||
},
|
||||
})
|
||||
.await
|
||||
.expect("set_cell_preferred_view restores tui");
|
||||
|
||||
match &out.layout.root {
|
||||
LayoutNode::Leaf(l) => assert_eq!(l.preferred_view, PreferredView::Tui),
|
||||
_ => panic!("expected leaf root"),
|
||||
}
|
||||
let tree_json = active_tree_json(&env.fs);
|
||||
assert!(
|
||||
tree_json["root"]["node"].get("preferredView").is_none(),
|
||||
"default TUI view is omitted from persisted JSON"
|
||||
);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Named-layout management (#4)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@ -332,6 +332,7 @@ fn agent_leaf(
|
||||
conversation_id: conversation_id.map(str::to_owned),
|
||||
engine_session_id: None,
|
||||
agent_was_running,
|
||||
preferred_view: domain::PreferredView::Tui,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -163,6 +163,7 @@ fn agent_leaf(node: NodeId, agent: Option<AgentId>, conv: Option<&str>, running:
|
||||
conversation_id: conv.map(str::to_string),
|
||||
engine_session_id: None,
|
||||
agent_was_running: running,
|
||||
preferred_view: domain::PreferredView::Tui,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -183,6 +183,7 @@ fn agent_leaf(node: NodeId, agent: Option<AgentId>) -> LeafCell {
|
||||
conversation_id: None,
|
||||
engine_session_id: None,
|
||||
agent_was_running: false,
|
||||
preferred_view: domain::PreferredView::Tui,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -267,6 +267,7 @@ fn tab(n: u128) -> Tab {
|
||||
conversation_id: None,
|
||||
engine_session_id: None,
|
||||
agent_was_running: false,
|
||||
preferred_view: domain::PreferredView::Tui,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user