diff --git a/crates/aionui-team/src/service.rs b/crates/aionui-team/src/service.rs index d96522258..65e053201 100644 --- a/crates/aionui-team/src/service.rs +++ b/crates/aionui-team/src/service.rs @@ -891,6 +891,11 @@ impl TeamSessionService { |_| {}, ); + let carried_run_id = self + .sessions + .get(team_id) + .and_then(|entry| entry.session.team_run_manager().current_active_run_id()); + let session = match TeamSession::start_with_prompt_dump( team, self.repo.clone(), @@ -965,6 +970,14 @@ impl TeamSessionService { slow_monitor_handle, }; self.sessions.insert(team_id.to_owned(), entry); + // If the previous session had an active team run, carry it forward so + // team tools (e.g. team_send_message) don't fail with "no active team + // run for run-scoped wake" after a session restart. + if let Some(ref run_id) = carried_run_id { + if let Err(e) = session.team_run_manager().resume_run(run_id.clone()) { + tracing::warn!(team_id, error = %e, "failed to resume team run after session restart"); + } + } drop(membership_guard); drop(ensure_guard); diff --git a/crates/aionui-team/src/team_run/manager.rs b/crates/aionui-team/src/team_run/manager.rs index e0aba236e..ba085d485 100644 --- a/crates/aionui-team/src/team_run/manager.rs +++ b/crates/aionui-team/src/team_run/manager.rs @@ -150,6 +150,34 @@ impl TeamRunManager { .map(|run| run.team_run_id.clone()) } + /// Restore a run carried over from a session that was just replaced + /// (e.g. after `ensure_session_inner` recreates the `TeamSession` and its + /// `TeamRunManager`). Without this, `team_send_message` fails with + /// "no active team run for run-scoped wake" because the fresh manager + /// has no run state even though the previous session had one active. + pub(crate) fn resume_run(&self, run_id: String) -> Result<(), TeamError> { + let mut state = self.lock_state(); + if state.is_some() { + return Err(TeamError::InvalidRequest( + "cannot resume: a run is already active".into(), + )); + } + *state = Some(TeamRunRecord { + team_run_id: run_id, + source: TeamRunSource::SystemLifecycle, + has_user_intervention: false, + target_slot_id: String::new(), + target_role: TeamRunTargetRole::Lead, + status: TeamRunStatus::Accepted, + started_at_ms: None, + completed_at_ms: None, + cancel_reason: None, + summary: None, + accepted_emitted: false, + }); + Ok(()) + } + pub(crate) fn apply_work_summary(&self, summary: RunWorkSummary) { let event = { let mut state = self.lock_state();