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:
@ -22,6 +22,23 @@ pub enum Direction {
|
||||
Column,
|
||||
}
|
||||
|
||||
/// Preferred live view for an agent leaf.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum PreferredView {
|
||||
/// Native terminal/TUI view.
|
||||
#[default]
|
||||
Tui,
|
||||
/// Structured chat view.
|
||||
Chat,
|
||||
}
|
||||
|
||||
/// Returns `true` when the preferred view is the default `Tui`.
|
||||
#[allow(clippy::trivially_copy_pass_by_ref)]
|
||||
fn is_default_preferred_view(view: &PreferredView) -> bool {
|
||||
*view == PreferredView::Tui
|
||||
}
|
||||
|
||||
/// Returns `true` when a boolean is `false`. Used as a `skip_serializing_if`
|
||||
/// predicate so that default (`false`) flags are omitted from the serialized
|
||||
/// form, preserving backward/forward compatibility with leaves that predate the
|
||||
@ -64,6 +81,10 @@ pub struct LeafCell {
|
||||
/// last closed. Used to decide whether to auto-resume the agent on reopen.
|
||||
#[serde(default, skip_serializing_if = "is_false")]
|
||||
pub agent_was_running: bool,
|
||||
/// Preferred live view for this cell. Additive and defaulted to TUI for old
|
||||
/// layouts; meaningful only when [`Self::agent`] is set.
|
||||
#[serde(default, skip_serializing_if = "is_default_preferred_view")]
|
||||
pub preferred_view: PreferredView,
|
||||
}
|
||||
|
||||
impl LeafCell {
|
||||
@ -81,6 +102,7 @@ impl LeafCell {
|
||||
conversation_id: None,
|
||||
engine_session_id: None,
|
||||
agent_was_running: false,
|
||||
preferred_view: PreferredView::Tui,
|
||||
}
|
||||
}
|
||||
|
||||
@ -105,6 +127,13 @@ impl LeafCell {
|
||||
self.engine_session_id = engine_session_id;
|
||||
self
|
||||
}
|
||||
|
||||
/// Wither additif : pose la vue live préférée de la cellule.
|
||||
#[must_use]
|
||||
pub fn with_preferred_view(mut self, preferred_view: PreferredView) -> Self {
|
||||
self.preferred_view = preferred_view;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A weighted child within a [`SplitContainer`]. The `weight` is a *relative*
|
||||
@ -567,6 +596,37 @@ impl LayoutTree {
|
||||
Ok(tree)
|
||||
}
|
||||
|
||||
/// Sets the preferred live view on the leaf `target`.
|
||||
///
|
||||
/// Pure: returns a new validated tree.
|
||||
///
|
||||
/// # Errors
|
||||
/// - [`LayoutError::NodeNotFound`] if `target` is not a leaf in the tree.
|
||||
pub fn set_cell_preferred_view(
|
||||
&self,
|
||||
target: NodeId,
|
||||
preferred_view: PreferredView,
|
||||
) -> Result<Self, LayoutError> {
|
||||
let mut found = false;
|
||||
let root = map_node(&self.root, &mut |node| {
|
||||
if let LayoutNode::Leaf(leaf) = node {
|
||||
if leaf.id == target {
|
||||
found = true;
|
||||
let mut leaf = leaf.clone();
|
||||
leaf.preferred_view = preferred_view;
|
||||
return LayoutNode::Leaf(leaf);
|
||||
}
|
||||
}
|
||||
node.clone()
|
||||
});
|
||||
if !found {
|
||||
return Err(LayoutError::NodeNotFound(target));
|
||||
}
|
||||
let tree = Self { root };
|
||||
tree.validate()?;
|
||||
Ok(tree)
|
||||
}
|
||||
|
||||
/// Sets (or, with `None`, clears) the persistent CLI `conversation_id` on
|
||||
/// the leaf `target`.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user