A duplicate Stop can be acknowledged only if its scoped files have no unrecorded changes. Never use unrelated working-tree edits as evidence of a new turn, and never silently accept an invalid or stale manifest.
(
root: &Path,
options: &TurnRecordOptions<'_>,
)
| 154 | /// unrecorded changes. Never use unrelated working-tree edits as evidence of |
| 155 | /// a new turn, and never silently accept an invalid or stale manifest. |
| 156 | pub(crate) fn has_pending_changes( |
| 157 | root: &Path, |
| 158 | options: &TurnRecordOptions<'_>, |
| 159 | ) -> AgentResult<bool> { |
| 160 | let Some(files) = manifest(options)? else { |
| 161 | return Ok(false); |
| 162 | }; |
| 163 | if files.is_empty() { |
| 164 | return Ok(false); |
| 165 | } |
| 166 | validate(root, &files, options)?; |
| 167 | let fail = |error: atomic_repository::RepositoryError| AgentError::RecordFailed { |
| 168 | session_id: options.session.session_id.clone(), |
| 169 | turn_number: options.turn_number, |
| 170 | reason: format!("cannot verify duplicate Stop's scoped files: {error}"), |
| 171 | }; |
| 172 | let repo = |
| 173 | Repository::open_readonly_wait(root, std::time::Duration::from_secs(10)).map_err(fail)?; |
| 174 | let status = repo |
| 175 | .status(StatusOptions::default().with_untracked(true)) |
| 176 | .map_err(fail)?; |
| 177 | let scoped = filter(status, Some(&files)); |
| 178 | Ok(!scoped.is_clean() || scoped.has_untracked() || scoped.has_conflicts()) |
| 179 | } |
| 180 | |
| 181 | pub(super) fn filter(status: RepositoryStatus, files: Option<&FileManifest>) -> RepositoryStatus { |
| 182 | let Some(files) = files else { |
no test coverage detected