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.
- {sigit_code-1.7.1 → sigit_code-1.7.2}/Cargo.lock +2 -2
- {sigit_code-1.7.1 → sigit_code-1.7.2}/Cargo.toml +2 -2
- {sigit_code-1.7.1 → sigit_code-1.7.2}/PKG-INFO +1 -1
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/README.md +2 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/chat.rs +139 -9
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/headless.rs +8 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/inline_tool_calls.rs +307 -36
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/main.rs +32 -6
- sigit_code-1.7.2/crates/cli/src/program_status.rs +149 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/headless_mode.rs +72 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/Cargo.toml +1 -0
- sigit_code-1.7.2/crates/sigit-agent/README.md +70 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/README.md +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/Cargo.toml +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/account.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/backend.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/client_fs.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/client_terminal.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/commands.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/frontmatter.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/harness_markup.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/hooks.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/instructions.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/mcp.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/models.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/permissions.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/provider.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/repetition.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/session_store.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/settings.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/setup.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/skills.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/subagents.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/tools.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/src/workspace.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_audio.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_client_fs.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_client_terminal.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_concurrent_sessions.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_elicitation.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_embedded_context.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_endpoint_errors.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_error_codes.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_harness_markup.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_images.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_logout.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_multi_root.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_permissions.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_session_list.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_session_load.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_stop_reasons.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_tool_call_name.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/acp_tool_stdin.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/cli/tests/version_flag.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/auth.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/browser_auth.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/credentials.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/lib.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/profile.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/src/prompt.rs +0 -0
- {sigit_code-1.7.1 → sigit_code-1.7.2}/crates/sigit-agent/tests/embedded.rs +0 -0
- {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.
|
|
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.
|
|
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.
|
|
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.
|
|
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"] }
|
|
@@ -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(
|
|
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
|
|
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.
|
|
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(
|
|
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::{
|
|
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();
|