Handle a SessionEnd event. Transitions the session to Ended, saves it, and creates an attestation covering all changes recorded during the session. The attestation is a graph-level audit node — it captures agent identity, timing, and which changes were recorded. Cost and token data are left at zero (they're not available from the hook) and can be enriched later via `atomic agent attest --enrich`
(
&mut self,
event: TurnEvent,
)
| 17 | /// data are left at zero (they're not available from the hook) and |
| 18 | /// can be enriched later via `atomic agent attest --enrich`. |
| 19 | pub(super) async fn handle_session_end( |
| 20 | &mut self, |
| 21 | event: TurnEvent, |
| 22 | ) -> AgentResult<DispatchResult> { |
| 23 | let session_id = &event.session_id; |
| 24 | |
| 25 | let mut session = match self.session_store.load(session_id)? { |
| 26 | Some(s) => s, |
| 27 | None => { |
| 28 | // Session not found — nothing to end |
| 29 | log::info!("SessionEnd for unknown session {} — ignoring", session_id); |
| 30 | return Ok(DispatchResult::new(session_id, Phase::Ended)); |
| 31 | } |
| 32 | }; |
| 33 | |
| 34 | // If a turn is still active, cancel the watcher |
| 35 | if session.is_turn_active() { |
| 36 | if let Err(e) = self.watcher.cancel_turn().await { |
| 37 | log::warn!( |
| 38 | "Failed to cancel turn watcher for session {}: {}", |
| 39 | session_id, |
| 40 | e |
| 41 | ); |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | // State machine transition |
| 46 | let result = phase::transition( |
| 47 | session.phase, |
| 48 | Event::SessionStop, |
| 49 | TransitionContext::default(), |
| 50 | ); |
| 51 | phase::apply_common_actions(&mut session, &result); |
| 52 | |
| 53 | self.session_store.save(&session)?; |
| 54 | |
| 55 | log::info!( |
| 56 | "Session {} ended after {} turn{}", |
| 57 | session_id, |
| 58 | session.turn_count, |
| 59 | if session.turn_count == 1 { "" } else { "s" }, |
| 60 | ); |
| 61 | |
| 62 | // Create an attestation for this session's changes. |
| 63 | // |
| 64 | // The attestation is a graph-level audit node that captures which |
| 65 | // changes were recorded, by which agent, and when. Cost and token |
| 66 | // data are left at zero — they're not available from the hook |
| 67 | // payload. They can be enriched later when `claude --resume` data |
| 68 | // is available. |
| 69 | if session.turn_count > 0 { |
| 70 | self.create_session_attestation(&session); |
| 71 | } |
| 72 | |
| 73 | // Switch back to the user's original view. session-start switched |
| 74 | // to the agent view so that all file writes happened there. Now |
| 75 | // that the session is over, restore the user's view. |
| 76 | if let Some(ref parent) = session.parent_view { |
no test coverage detected