agent conversation fix
This commit is contained in:
@ -9,9 +9,11 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::input::InputMediator;
|
||||
use domain::{AgentId, NodeId, Project, SessionId};
|
||||
|
||||
use crate::error::AppError;
|
||||
use crate::orchestrator::OrchestratorService;
|
||||
use crate::terminal::{CloseTerminal, CloseTerminalInput, LiveSessionKind, LiveSessions};
|
||||
|
||||
/// Input for [`AttachLiveAgent::execute`].
|
||||
@ -125,13 +127,36 @@ pub struct StopLiveAgentOutput {
|
||||
pub struct StopLiveAgent {
|
||||
live: Arc<LiveSessions>,
|
||||
close: Arc<CloseTerminal>,
|
||||
input: Option<Arc<dyn InputMediator>>,
|
||||
waits: Option<Arc<OrchestratorService>>,
|
||||
}
|
||||
|
||||
impl StopLiveAgent {
|
||||
/// Builds the use case from the live-session registry and the close primitive.
|
||||
#[must_use]
|
||||
pub fn new(live: Arc<LiveSessions>, close: Arc<CloseTerminal>) -> Self {
|
||||
Self { live, close }
|
||||
Self {
|
||||
live,
|
||||
close,
|
||||
input: None,
|
||||
waits: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Wires cancellation helpers used for user-driven stop cascades.
|
||||
///
|
||||
/// Stopping A while A waits on B should stop B too. The orchestrator service is
|
||||
/// only used as a read-only provider of active wait edges; the input mediator is
|
||||
/// used to preempt the running turn before the session is torn down.
|
||||
#[must_use]
|
||||
pub fn with_cascade(
|
||||
mut self,
|
||||
input: Arc<dyn InputMediator>,
|
||||
waits: Arc<OrchestratorService>,
|
||||
) -> Self {
|
||||
self.input = Some(input);
|
||||
self.waits = Some(waits);
|
||||
self
|
||||
}
|
||||
|
||||
/// Tears down the agent's live session.
|
||||
@ -143,20 +168,40 @@ impl StopLiveAgent {
|
||||
&self,
|
||||
input: StopLiveAgentInput,
|
||||
) -> Result<StopLiveAgentOutput, AppError> {
|
||||
self.stop_dependencies(input.agent_id).await;
|
||||
if let Some(mediator) = &self.input {
|
||||
mediator.preempt(input.agent_id);
|
||||
}
|
||||
self.stop_one(input.agent_id).await
|
||||
}
|
||||
|
||||
async fn stop_dependencies(&self, agent_id: AgentId) {
|
||||
let Some(waits) = &self.waits else {
|
||||
return;
|
||||
};
|
||||
for dep in waits.active_wait_dependencies(agent_id) {
|
||||
if let Some(mediator) = &self.input {
|
||||
mediator.preempt(dep);
|
||||
}
|
||||
let _ = self.stop_one(dep).await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn stop_one(&self, agent_id: AgentId) -> Result<StopLiveAgentOutput, AppError> {
|
||||
// PTY first: delegate to the existing close primitive (removes + kills).
|
||||
if let Some(session_id) = self.live.pty.session_for_agent(&input.agent_id) {
|
||||
if let Some(session_id) = self.live.pty.session_for_agent(&agent_id) {
|
||||
self.close
|
||||
.execute(CloseTerminalInput { session_id })
|
||||
.await?;
|
||||
return Ok(StopLiveAgentOutput {
|
||||
agent_id: input.agent_id,
|
||||
agent_id,
|
||||
session_id,
|
||||
kind: LiveSessionKind::Pty,
|
||||
});
|
||||
}
|
||||
// Structured: remove from the registry first (so the uniqueness guard no
|
||||
// longer sees a live session), then shut the session down out of the lock.
|
||||
if let Some(session_id) = self.live.structured.session_id_for_agent(&input.agent_id) {
|
||||
if let Some(session_id) = self.live.structured.session_id_for_agent(&agent_id) {
|
||||
if let Some(session) = self.live.structured.remove(&session_id) {
|
||||
session
|
||||
.shutdown()
|
||||
@ -164,14 +209,13 @@ impl StopLiveAgent {
|
||||
.map_err(|e| AppError::Process(e.to_string()))?;
|
||||
}
|
||||
return Ok(StopLiveAgentOutput {
|
||||
agent_id: input.agent_id,
|
||||
agent_id,
|
||||
session_id,
|
||||
kind: LiveSessionKind::Structured,
|
||||
});
|
||||
}
|
||||
Err(AppError::NotFound(format!(
|
||||
"running session for agent {}",
|
||||
input.agent_id
|
||||
"running session for agent {agent_id}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,11 +13,11 @@ pub use actions::{
|
||||
AttachLiveAgent, AttachLiveAgentInput, AttachLiveAgentOutput, StopLiveAgent,
|
||||
StopLiveAgentInput, StopLiveAgentOutput,
|
||||
};
|
||||
pub use reconcile::{ReconcileLiveState, ReconcileLiveStateInput};
|
||||
pub use live::{
|
||||
GetLiveStateLean, LeanLiveEntry, LeanLiveState, UpdateLiveState, UpdateLiveStateInput,
|
||||
LIVE_STATE_MAX_ENTRIES, LIVE_STATE_TTL_MS,
|
||||
};
|
||||
pub use reconcile::{ReconcileLiveState, ReconcileLiveStateInput};
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -173,7 +173,11 @@ mod tests {
|
||||
run(store.clone(), HashSet::new(), 999).await;
|
||||
|
||||
let after = store.load().await.unwrap();
|
||||
assert_eq!(after.entries.len(), 1, "still one keyed row (upsert, not append)");
|
||||
assert_eq!(
|
||||
after.entries.len(),
|
||||
1,
|
||||
"still one keyed row (upsert, not append)"
|
||||
);
|
||||
let row = &after.entries[0];
|
||||
assert_eq!(row.status, WorkStatus::Idle, "orphan downgraded to idle");
|
||||
assert_eq!(row.progress.as_deref(), Some(STALE_AT_RESTART_MARKER));
|
||||
@ -225,6 +229,9 @@ mod tests {
|
||||
|
||||
let row = &store.load().await.unwrap().entries[0];
|
||||
assert_eq!(row.status, WorkStatus::Idle);
|
||||
assert_eq!(row.updated_at_ms, 3, "idle row never reconciled, not restamped");
|
||||
assert_eq!(
|
||||
row.updated_at_ms, 3,
|
||||
"idle row never reconciled, not restamped"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user