MCPcopy Create free account
hub / github.com/atomicdotdev/atomic / handle_turn_end

Method handle_turn_end

atomic-agent/src/turn/orchestrator/turn.rs:134–366  ·  view source on GitHub ↗

Handle a TurnEnd event (Stop). Records an Atomic change for the turn (status → add → record), then transitions the session back to Idle. The recording workflow lets the repository figure out what changed: 1. `repo.status()` — find modified, deleted, and untracked files 2. `repo.add()` — track any new files the agent created 3. `repo.record(all: true)` — record everything that's dirty This avoid

(
        &mut self,
        mut event: TurnEvent,
    )

Source from the content-addressed store, hash-verified

132 /// snapshots between TurnStart and TurnEnd. Instead, we ask the
133 /// repository what changed since the last recorded state.
134 pub(super) async fn handle_turn_end(
135 &mut self,
136 mut event: TurnEvent,
137 ) -> AgentResult<DispatchResult> {
138 // Clone so `event` stays mutable for enrichment below while the
139 // id is borrowed throughout this function.
140 let session_id_owned = event.session_id.clone();
141 let session_id = session_id_owned.as_str();
142 let _turn_end_lock = match self.try_turn_end_lock(session_id) {
143 TurnEndLock::Acquired(guard) => Some(guard),
144 TurnEndLock::Busy => {
145 log::warn!(
146 "Turn end for session {} is already being recorded; skipping duplicate Stop hook",
147 session_id
148 );
149 return Ok(DispatchResult::new(session_id, phase::Phase::Idle)
150 .with_warning("duplicate Stop hook skipped: turn already recording"));
151 }
152 TurnEndLock::Unavailable => None,
153 };
154
155 // Fast gate: check if anything changed since the last record.
156 // This bypasses the entire status machinery (TREE scan, filesystem
157 // walk, etc.) and just checks the pristine database mtime.
158 // If the DB hasn't been written since the last record, nothing
159 // in the working copy could have been recorded — but files may
160 // have been edited. We check the working copy for recent mtimes
161 // by scanning only the repo root (not recursively) and common
162 // source directories.
163 if !self.has_working_copy_changes() {
164 log::info!(
165 "Turn end for session {} — no changes detected, skipping record",
166 session_id
167 );
168 return Ok(DispatchResult::new(session_id, phase::Phase::Idle));
169 }
170
171 let mut session = self.load_or_create_session(session_id, &event)?;
172
173 // Extract model/provider from the TurnEnd event's raw_json.
174 // OpenCode sends model and provider in every stop payload.
175 // This is the last chance to capture the info before recording,
176 // in case TurnStart didn't have it (e.g., session was created
177 // outside the plugin, or the chat.message hook didn't fire).
178 if let Some(ref raw) = event.raw_json {
179 if let Some(model) = raw.get("model").and_then(|v| v.as_str()) {
180 if !model.is_empty() {
181 session.model = model.to_string();
182 }
183 }
184 if let Some(provider) = raw.get("provider").and_then(|v| v.as_str()) {
185 if !provider.is_empty() {
186 session.agent_vendor = provider.to_string();
187 }
188 }
189 }
190
191 // Update the transcript path from the Stop event. Claude Code's Stop

Callers 1

dispatchMethod · 0.80

Calls 15

transitionFunction · 0.85
apply_common_actionsFunction · 0.85
record_turnFunction · 0.85
try_turn_end_lockMethod · 0.80
set_transcript_pathMethod · 0.80
enrich_opencode_turnMethod · 0.80
cancel_turnMethod · 0.80
as_refMethod · 0.80
as_u64Method · 0.80

Tested by

no test coverage detected