sigit-code 1.7.1__tar.gz → 1.7.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. {sigit_code-1.7.1 → sigit_code-1.7.2}/Cargo.lock +2 -2
  2. {sigit_code-1.7.1 → sigit_code-1.7.2}/Cargo.toml +2 -2
  3. {sigit_code-1.7.1 → sigit_code-1.7.2}/PKG-INFO +1 -1
  4. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/README.md +2 -0
  5. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/chat.rs +139 -9
  6. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/headless.rs +8 -0
  7. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/inline_tool_calls.rs +307 -36
  8. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/main.rs +32 -6
  9. sigit_code-1.7.2/crates/cli/src/program_status.rs +149 -0
  10. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/headless_mode.rs +72 -0
  11. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/Cargo.toml +1 -0
  12. sigit_code-1.7.2/crates/sigit-agent/README.md +70 -0
  13. {sigit_code-1.7.1 → sigit_code-1.7.2}/README.md +0 -0
  14. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/Cargo.toml +0 -0
  15. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/account.rs +0 -0
  16. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/backend.rs +0 -0
  17. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/client_fs.rs +0 -0
  18. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/client_terminal.rs +0 -0
  19. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/commands.rs +0 -0
  20. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/frontmatter.rs +0 -0
  21. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/harness_markup.rs +0 -0
  22. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/hooks.rs +0 -0
  23. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/instructions.rs +0 -0
  24. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/mcp.rs +0 -0
  25. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/models.rs +0 -0
  26. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/permissions.rs +0 -0
  27. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/provider.rs +0 -0
  28. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/repetition.rs +0 -0
  29. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/session_store.rs +0 -0
  30. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/settings.rs +0 -0
  31. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/setup.rs +0 -0
  32. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/skills.rs +0 -0
  33. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/subagents.rs +0 -0
  34. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/tools.rs +0 -0
  35. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/workspace.rs +0 -0
  36. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_audio.rs +0 -0
  37. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_client_fs.rs +0 -0
  38. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_client_terminal.rs +0 -0
  39. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_concurrent_sessions.rs +0 -0
  40. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_elicitation.rs +0 -0
  41. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_embedded_context.rs +0 -0
  42. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_endpoint_errors.rs +0 -0
  43. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_error_codes.rs +0 -0
  44. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_harness_markup.rs +0 -0
  45. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_images.rs +0 -0
  46. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_logout.rs +0 -0
  47. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_multi_root.rs +0 -0
  48. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_permissions.rs +0 -0
  49. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_session_list.rs +0 -0
  50. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_session_load.rs +0 -0
  51. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_stop_reasons.rs +0 -0
  52. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_tool_call_name.rs +0 -0
  53. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_tool_stdin.rs +0 -0
  54. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/version_flag.rs +0 -0
  55. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/auth.rs +0 -0
  56. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/browser_auth.rs +0 -0
  57. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/credentials.rs +0 -0
  58. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/lib.rs +0 -0
  59. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/profile.rs +0 -0
  60. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/prompt.rs +0 -0
  61. {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/tests/embedded.rs +0 -0
  62. {sigit_code-1.7.1 → sigit_code-1.7.2}/pyproject.toml +0 -0
@@ -5945,7 +5945,7 @@ checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
5945
5945
 
5946
5946
  [[package]]
5947
5947
  name = "sigit"
5948
- version = "1.7.1"
5948
+ version = "1.7.2"
5949
5949
  dependencies = [
5950
5950
  "agent-client-protocol",
5951
5951
  "anyhow",
@@ -5976,7 +5976,7 @@ dependencies = [
5976
5976
 
5977
5977
  [[package]]
5978
5978
  name = "sigit-agent"
5979
- version = "1.7.1"
5979
+ version = "1.7.2"
5980
5980
  dependencies = [
5981
5981
  "agent-client-protocol",
5982
5982
  "anyhow",
@@ -3,7 +3,7 @@ resolver = "3"
3
3
  members = ["crates/cli", "crates/sigit-*"]
4
4
 
5
5
  [workspace.package]
6
- version = "1.7.1"
6
+ version = "1.7.2"
7
7
  edition = "2024"
8
8
  license = "Apache-2.0"
9
9
  repository = "https://github.com/getsigit/sigit"
@@ -16,7 +16,7 @@ authors = ["Seto Elkahfi <seto@sigit.si>", "paydii <193906237+paydii@users.norep
16
16
  # crates.io, so this resolves from the registry rather than the onde-ed checkout next door.
17
17
  ed-acp = { version = "1.3.0", default-features = false }
18
18
  ed-acp-testkit = "1.3.0"
19
- sigit-agent = { version = "1.7.1", default-features = false, path = "crates/sigit-agent" }
19
+ sigit-agent = { version = "1.7.2", default-features = false, path = "crates/sigit-agent" }
20
20
 
21
21
  # ACP protocol SDK
22
22
  agent-client-protocol = { version = "2.2", features = ["unstable_session_fork"] }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sigit-code
3
- Version: 1.7.1
3
+ Version: 1.7.2
4
4
  Classifier: Development Status :: 4 - Beta
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -157,6 +157,8 @@ Run `sigit` in a terminal and you get the same model and system prompt as the ed
157
157
 
158
158
  Terminal mode currently needs Unix terminal behavior, so it works on macOS and Linux only.
159
159
 
160
+ The interactive UI emits [OSC 7501 program status](https://www.superlogical.com/rex/docs/build/program-status) reports so supporting terminals can show working, waiting for permission or input, browser sign-in, completion, and errors across tabs. User-driven quit reports idle; terminals expire active work on process exit and retain completion or error reports. Status sequences are never written to headless/JSON or ACP output.
161
+
160
162
  ## Headless and CI mode
161
163
 
162
164
  Use `sigit run` to execute the same agent from scripts, CI, or another siGit Factory client:
@@ -519,6 +519,7 @@ mod tui {
519
519
  use crate::models::{
520
520
  InferenceKind, ModelCacheHealth, ModelPickerItem, ModelSource, build_model_picker_items,
521
521
  };
522
+ use crate::program_status::{Reporter, Status};
522
523
  use crate::session_store::SessionEntry;
523
524
  use ratatui::{
524
525
  Frame,
@@ -731,6 +732,8 @@ mod tui {
731
732
  // ── App state ─────────────────────────────────────────────────────────────
732
733
 
733
734
  struct App {
735
+ program_status: Reporter,
736
+ outcome: Status,
734
737
  messages: Vec<ChatMessage>,
735
738
  input: String,
736
739
  cursor: usize,
@@ -855,7 +858,11 @@ mod tui {
855
858
  }
856
859
 
857
860
  impl App {
858
- fn new(load_model_name: String, backend: Arc<dyn InferenceBackend>) -> Self {
861
+ fn new(
862
+ load_model_name: String,
863
+ backend: Arc<dyn InferenceBackend>,
864
+ program_status: Reporter,
865
+ ) -> Self {
859
866
  let is_remote = backend.is_remote();
860
867
  let items = build_model_picker_items();
861
868
  let tool_calling = items
@@ -890,6 +897,8 @@ mod tui {
890
897
  model_load_cancelled: false,
891
898
  switching_model_id: None,
892
899
  download_progress: None,
900
+ program_status,
901
+ outcome: Status::Idle,
893
902
  is_loading: true,
894
903
  load_tick: 0,
895
904
  load_error: None,
@@ -959,6 +968,34 @@ mod tui {
959
968
  self.history_sessions.get(self.history_index)
960
969
  }
961
970
 
971
+ fn finish_login(&mut self, result: Result<String, String>) {
972
+ self.outcome = if result.is_ok() {
973
+ Status::Done
974
+ } else {
975
+ Status::Error
976
+ };
977
+ self.program_status.report(self.outcome);
978
+ self.messages.push(ChatMessage::system(
979
+ result.unwrap_or_else(|message| message),
980
+ ));
981
+ }
982
+
983
+ fn status(&self) -> Status {
984
+ if self.quit {
985
+ Status::Idle
986
+ } else if self.load_error.is_some() {
987
+ Status::Error
988
+ } else if self.pending_approval.is_some() {
989
+ Status::Permission
990
+ } else if self.is_loading || self.is_busy() || self.inference_rx.is_some() {
991
+ Status::Working
992
+ } else if self.show_model_picker || self.history_pending_delete.is_some() {
993
+ Status::Question
994
+ } else {
995
+ self.outcome
996
+ }
997
+ }
998
+
962
999
  fn is_busy(&self) -> bool {
963
1000
  self.is_streaming() || self.thinking || self.switching_model
964
1001
  }
@@ -1000,11 +1037,13 @@ mod tui {
1000
1037
  }
1001
1038
 
1002
1039
  fn start_thinking(&mut self) {
1040
+ self.outcome = Status::Idle;
1003
1041
  self.thinking = true;
1004
1042
  self.thinking_tick = 0;
1005
1043
  }
1006
1044
 
1007
1045
  fn stop_thinking(&mut self) {
1046
+ self.outcome = Status::Idle;
1008
1047
  self.thinking = false;
1009
1048
  self.inference_rx = None;
1010
1049
  // Dropping a pending reply channel reads as a denial on the
@@ -2990,6 +3029,7 @@ mod tui {
2990
3029
  )));
2991
3030
  }
2992
3031
  None => {
3032
+ app.outcome = Status::Auth;
2993
3033
  app.messages.push(ChatMessage::system(
2994
3034
  "siGit Code Cloud needs an account. Use \
2995
3035
  `/login <email> <password>`, or create one at sigit.si.",
@@ -3041,13 +3081,22 @@ mod tui {
3041
3081
  }
3042
3082
  },
3043
3083
  SlashCommand::Login(arg) => {
3044
- let message = match arg.as_deref().and_then(crate::account::parse_login_args) {
3084
+ let password_login = arg
3085
+ .as_deref()
3086
+ .and_then(crate::account::parse_login_args)
3087
+ .is_some();
3088
+ app.program_status.report(if password_login {
3089
+ Status::Working
3090
+ } else {
3091
+ Status::Auth
3092
+ });
3093
+ let result = match arg.as_deref().and_then(crate::account::parse_login_args) {
3045
3094
  Some((email, password)) => {
3046
3095
  match crate::account::authenticate(&email, &password).await {
3047
- Ok(email) => format!(
3096
+ Ok(email) => Ok(format!(
3048
3097
  "Signed in as {email}. siGit Code Cloud applies to your next session."
3049
- ),
3050
- Err(error) => format!("Login failed: {error}"),
3098
+ )),
3099
+ Err(error) => Err(format!("Login failed: {error}")),
3051
3100
  }
3052
3101
  }
3053
3102
  // A bare `/login` opens the browser instead of asking for
@@ -3059,7 +3108,7 @@ mod tui {
3059
3108
  crate::browser_login_message().await
3060
3109
  }
3061
3110
  };
3062
- app.messages.push(ChatMessage::system(message));
3111
+ app.finish_login(result);
3063
3112
  }
3064
3113
  SlashCommand::Logout => {
3065
3114
  let message = crate::account::end_session().await;
@@ -3436,13 +3485,22 @@ mod tui {
3436
3485
  backend: Arc<dyn InferenceBackend>,
3437
3486
  load_rx: std_mpsc::Receiver<Result<(), String>>,
3438
3487
  load_model_name: String,
3488
+ program_status: Reporter,
3439
3489
  ) -> Result<()>
3440
3490
  where
3441
3491
  // ratatui 0.30 gives each backend its own error type; `?` into anyhow needs it
3442
3492
  // to be thread-safe.
3443
3493
  B::Error: Send + Sync + 'static,
3444
3494
  {
3445
- event_loop(terminal, engine, backend, load_rx, load_model_name).await
3495
+ event_loop(
3496
+ terminal,
3497
+ engine,
3498
+ backend,
3499
+ load_rx,
3500
+ load_model_name,
3501
+ program_status,
3502
+ )
3503
+ .await
3446
3504
  }
3447
3505
 
3448
3506
  async fn event_loop<B: ratatui::backend::Backend>(
@@ -3451,13 +3509,14 @@ mod tui {
3451
3509
  backend: Arc<dyn InferenceBackend>,
3452
3510
  load_rx: std_mpsc::Receiver<Result<(), String>>,
3453
3511
  load_model_name: String,
3512
+ program_status: Reporter,
3454
3513
  ) -> Result<()>
3455
3514
  where
3456
3515
  // ratatui 0.30 gives each backend its own error type; `?` into anyhow needs it
3457
3516
  // to be thread-safe.
3458
3517
  B::Error: Send + Sync + 'static,
3459
3518
  {
3460
- let mut app = App::new(load_model_name, backend);
3519
+ let mut app = App::new(load_model_name, backend, program_status);
3461
3520
  let mut event_stream = EventStream::new();
3462
3521
 
3463
3522
  // 10 fps is plenty for spinners
@@ -3482,6 +3541,7 @@ mod tui {
3482
3541
  if let Some(rx) = app.model_load_rx.as_mut() {
3483
3542
  match rx.try_recv() {
3484
3543
  Ok(ModelLoadUpdate::Loaded(model_name)) => {
3544
+ app.outcome = Status::Done;
3485
3545
  engine.clear_history().await;
3486
3546
  app.context_tokens_used = None;
3487
3547
  if let Some(tc) = app.pending_tool_calling.take() {
@@ -3527,6 +3587,7 @@ mod tui {
3527
3587
  }
3528
3588
  }
3529
3589
  Ok(ModelLoadUpdate::Error(error)) => {
3590
+ app.outcome = Status::Error;
3530
3591
  app.switching_model = false;
3531
3592
  app.switching_model_id = None;
3532
3593
  app.download_progress = None;
@@ -3544,6 +3605,7 @@ mod tui {
3544
3605
  app.model_load_cancelled = false;
3545
3606
  app.model_load_rx = None;
3546
3607
  if !was_cancelled {
3608
+ app.outcome = Status::Error;
3547
3609
  app.messages.push(ChatMessage::system(
3548
3610
  "error loading model: loader task disconnected".to_string(),
3549
3611
  ));
@@ -3552,6 +3614,10 @@ mod tui {
3552
3614
  }
3553
3615
  }
3554
3616
 
3617
+ // Polling model loads can change state immediately before select
3618
+ // goes idle, so report after applying those updates.
3619
+ app.program_status.report(app.status());
3620
+
3555
3621
  if app.quit {
3556
3622
  break;
3557
3623
  }
@@ -3599,6 +3665,7 @@ mod tui {
3599
3665
  }
3600
3666
  Some(InferenceUpdate::Response(text)) => {
3601
3667
  app.stop_thinking();
3668
+ app.outcome = Status::Done;
3602
3669
  app.messages.push(ChatMessage::assistant(text));
3603
3670
  }
3604
3671
  Some(InferenceUpdate::ContextUsage(tokens)) => {
@@ -3607,6 +3674,7 @@ mod tui {
3607
3674
  Some(InferenceUpdate::Error(msg)) => {
3608
3675
  app.finalize_stream();
3609
3676
  app.stop_thinking();
3677
+ app.outcome = Status::Error;
3610
3678
  app.messages.push(ChatMessage::system(format!("error: {msg}")));
3611
3679
  }
3612
3680
  Some(InferenceUpdate::ApprovalRequest { tool, args, reply }) => {
@@ -3627,6 +3695,7 @@ mod tui {
3627
3695
  // task finished, possibly with no text to show
3628
3696
  app.finalize_stream();
3629
3697
  app.stop_thinking();
3698
+ app.outcome = Status::Done;
3630
3699
  }
3631
3700
  }
3632
3701
  }
@@ -3709,6 +3778,9 @@ mod tui {
3709
3778
  };
3710
3779
 
3711
3780
  if let Event::Key(key) = event {
3781
+ if key.kind == KeyEventKind::Press {
3782
+ app.outcome = Status::Idle;
3783
+ }
3712
3784
  // loading phase — only quit keys work
3713
3785
  if app.is_loading {
3714
3786
  if key.kind == KeyEventKind::Press {
@@ -3816,6 +3888,10 @@ mod tui {
3816
3888
  app.stop_thinking();
3817
3889
  app.messages.push(ChatMessage::system("(cancelled)"));
3818
3890
  }
3891
+ // A visible stream can have cleared `thinking`;
3892
+ // cancellation must drop its receiver and clear
3893
+ // thinking so the tab cannot stay on working.
3894
+ app.stop_thinking();
3819
3895
  if app.switching_model {
3820
3896
  // flag before drop so Disconnected handler stays quiet
3821
3897
  app.model_load_cancelled = true;
@@ -3953,8 +4029,62 @@ mod tui {
3953
4029
  }
3954
4030
  #[cfg(test)]
3955
4031
  mod tui_tests {
3956
- use super::{compact_tokens, context_gauge_color, context_gauge_label};
4032
+ use super::{
4033
+ App, OpenAiBackend, Status, compact_tokens, context_gauge_color, context_gauge_label,
4034
+ };
3957
4035
  use ratatui::style::Color;
4036
+ use std::sync::Arc;
4037
+
4038
+ #[test]
4039
+ fn login_status_uses_the_result_instead_of_message_wording() {
4040
+ let backend = Arc::new(OpenAiBackend::new("http://localhost/v1", "", "test", None));
4041
+ let mut app = App::new(
4042
+ "test".into(),
4043
+ backend,
4044
+ super::Reporter::new(std::io::sink()),
4045
+ );
4046
+ app.finish_loading();
4047
+ app.finish_login(Ok("Welcome back.".into()));
4048
+ assert_eq!(app.status(), Status::Done);
4049
+ app.finish_login(Err("Signed in could not be completed.".into()));
4050
+ assert_eq!(app.status(), Status::Error);
4051
+ }
4052
+
4053
+ #[test]
4054
+ fn program_status_tracks_loading_approval_completion_and_cancellation() {
4055
+ let backend = Arc::new(OpenAiBackend::new("http://localhost/v1", "", "test", None));
4056
+ let mut app = App::new(
4057
+ "test".into(),
4058
+ backend,
4059
+ super::Reporter::new(std::io::sink()),
4060
+ );
4061
+ assert_eq!(app.status(), Status::Working);
4062
+ app.finish_loading();
4063
+ assert_eq!(app.status(), Status::Idle);
4064
+ app.start_thinking();
4065
+ assert_eq!(app.status(), Status::Working);
4066
+ let (reply, _) = tokio::sync::oneshot::channel();
4067
+ app.pending_approval = Some(("edit_file".into(), reply));
4068
+ assert_eq!(app.status(), Status::Permission);
4069
+ app.pending_approval = None;
4070
+ assert_eq!(app.status(), Status::Working);
4071
+ app.stop_thinking();
4072
+ assert_eq!(app.status(), Status::Idle);
4073
+ app.outcome = Status::Done;
4074
+ assert_eq!(app.status(), Status::Done);
4075
+ app.outcome = Status::Error;
4076
+ assert_eq!(app.status(), Status::Error);
4077
+ app.start_thinking();
4078
+ assert_eq!(app.status(), Status::Working);
4079
+ app.stop_thinking();
4080
+ app.show_model_picker = true;
4081
+ assert_eq!(app.status(), Status::Question);
4082
+ app.show_model_picker = false;
4083
+ app.set_load_error("failed".into());
4084
+ assert_eq!(app.status(), Status::Error);
4085
+ app.quit = true;
4086
+ assert_eq!(app.status(), Status::Idle);
4087
+ }
3958
4088
 
3959
4089
  #[test]
3960
4090
  fn context_gauge_reports_share_of_the_window() {
@@ -526,6 +526,14 @@ async fn run_prompt(
526
526
 
527
527
  let allow_tool_calls = round < config.max_tool_rounds;
528
528
 
529
+ // The round that reaches the cap is forced to answer in text. Say so,
530
+ // or the model spends its last reply narrating the step it can no
531
+ // longer take.
532
+ if !allow_tool_calls && let Some(last) = tool_results.last_mut() {
533
+ last.content
534
+ .push_str(&crate::headless_round_cap_note(config.max_tool_rounds));
535
+ }
536
+
529
537
  // Whatever this round says starts a new paragraph rather than
530
538
  // continuing the sentence the tool calls interrupted.
531
539
  reply.interrupt();