Replace local input model and improve shell command feedback

This commit is contained in:
2026-09-23 15:38:10 -05:00
parent 35ff43de7f
commit 3bfd7c0a54
33 changed files with 499 additions and 2700 deletions
@@ -1010,6 +1010,17 @@ impl BlocklistAIActionExecutor {
.async_executing_actions
.remove(conversation_id, &action_id)
else {
#[cfg(not(target_family = "wasm"))]
crate::ai::remote_logging::log_model_event(ctx, crate::ai::remote_logging::RemoteLogRecord {
level: crate::ai::remote_logging::RemoteLogLevel::Info,
message: "Async tool result discarded after action removal".to_string(),
context: serde_json::json!({
"event": "tool_result_discarded",
"conversation_id": conversation_id.to_string(),
"action_id": action_id.to_string(),
"reason": "action_no_longer_running",
}),
});
log::warn!("try_to_execute_action: async action completed but not found in executing map, conversation_id={conversation_id}, action_id={action_id:?}");
return;
};
@@ -77,8 +77,64 @@ impl ShellCommandExecutor {
&mut self,
_: ModelHandle<ModelEventDispatcher>,
event: &ModelEvent,
_ctx: &mut ModelContext<Self>,
ctx: &mut ModelContext<Self>,
) {
#[cfg(not(target_family = "wasm"))]
if let ModelEvent::Exit { reason } = event {
let context = {
let model = self.terminal_model.lock();
let block = model.block_list().active_block();
serde_json::json!({
"event": "shell_terminal_exited",
"reason": format!("{reason:?}"),
"block_id": block.id().to_string(),
"conversation_id": block.ai_conversation_id().map(|id| id.to_string()),
"action_id": block.requested_command_action_id().map(|id| id.to_string()),
"completion_waiter_groups": self.block_finished_senders.len(),
"refresh_waiter_groups": self.force_refresh_senders.len(),
})
};
crate::ai::remote_logging::log_model_event(
ctx,
crate::ai::remote_logging::RemoteLogRecord {
level: crate::ai::remote_logging::RemoteLogLevel::Info,
message: "Shell terminal exited".to_string(),
context,
},
);
}
// Log definitive terminal completion independently of tool-result delivery.
#[cfg(not(target_family = "wasm"))]
if let ModelEvent::BlockCompleted(completed) = event {
let context = {
let model = self.terminal_model.lock();
model
.block_list()
.block_with_id(&completed.block_id)
.and_then(|block| {
block.requested_command_action_id().map(|action_id| serde_json::json!({
"event": "shell_command_terminal_completed",
"conversation_id": block.ai_conversation_id().map(|id| id.to_string()),
"action_id": action_id.to_string(),
"block_id": block.id().to_string(),
"exit_code": block.exit_code().value(),
"start_ts": block.start_ts(),
"completed_ts": block.completed_ts(),
"output_rows": block.output_grid().len_displayed(),
}))
})
};
if let Some(context) = context {
crate::ai::remote_logging::log_model_event(
ctx,
crate::ai::remote_logging::RemoteLogRecord {
level: crate::ai::remote_logging::RemoteLogLevel::Info,
message: "Shell command terminal completed".to_string(),
context,
},
);
}
}
// Precmd provides fresh CWD metadata, while BlockCompleted is definitive completion
// evidence for shells that never deliver a subsequent precmd.
if matches!(
@@ -217,6 +273,8 @@ impl ShellCommandExecutor {
// Determine the action we want to take based on the input.
let action_id = input.action.id.clone();
let diagnostic_action_id = action_id.clone();
let conversation_id = input.conversation_id;
let handle = ctx.handle();
match &input.action.action {
@@ -278,6 +336,12 @@ impl ShellCommandExecutor {
// Remove the senders from the maps.
if let Some(handle) = handle.upgrade(ctx) {
handle.update(ctx, |me, ctx| {
log_shell_wait_result(
conversation_id,
&diagnostic_action_id,
&result,
ctx,
);
me.prune_closed_senders(&block_selector);
if matches!(
result,
@@ -313,16 +377,17 @@ impl ShellCommandExecutor {
let exit_code = block.exit_code();
let start_ts = block.start_ts().cloned();
let completed_ts = block.completed_ts().cloned();
let result = ActionResult::CommandFinished {
block_id: block.id().clone(),
output,
exit_code,
start_ts,
completed_ts,
};
drop(model);
log_shell_wait_result(conversation_id, &action_id, &result, ctx);
return ActionExecution::Sync(
AIAgentActionResultType::WriteToLongRunningShellCommand(
WriteToLongRunningShellCommandResult::CommandFinished {
block_id: block.id().clone(),
output,
exit_code,
start_ts,
completed_ts,
},
),
action_result_for_write_to_long_running_shell_command(result),
);
}
// Drop immutable borrow.
@@ -348,7 +413,13 @@ impl ShellCommandExecutor {
move |result, ctx| {
// Remove the senders from the maps.
if let Some(handle) = handle.upgrade(ctx) {
handle.update(ctx, |me, _| {
handle.update(ctx, |me, ctx| {
log_shell_wait_result(
conversation_id,
&diagnostic_action_id,
&result,
ctx,
);
me.prune_closed_senders(&block_selector);
});
}
@@ -369,15 +440,17 @@ impl ShellCommandExecutor {
let exit_code = block.exit_code();
let start_ts = block.start_ts().cloned();
let completed_ts = block.completed_ts().cloned();
return ActionExecution::Sync(AIAgentActionResultType::ReadShellCommandOutput(
ReadShellCommandOutputResult::CommandFinished {
command,
block_id: block_id.clone(),
output,
exit_code,
start_ts,
completed_ts,
},
let result = ActionResult::CommandFinished {
block_id: block_id.clone(),
output,
exit_code,
start_ts,
completed_ts,
};
drop(model);
log_shell_wait_result(conversation_id, &action_id, &result, ctx);
return ActionExecution::Sync(action_result_for_read_shell_command_output(
command, result,
));
}
let command = block.command_with_secrets_unobfuscated(false);
@@ -389,7 +462,13 @@ impl ShellCommandExecutor {
move |result, ctx| {
// Remove the senders from the maps.
if let Some(handle) = handle.upgrade(ctx) {
handle.update(ctx, |me, _| {
handle.update(ctx, |me, ctx| {
log_shell_wait_result(
conversation_id,
&diagnostic_action_id,
&result,
ctx,
);
me.prune_closed_senders(&block_selector);
});
}
@@ -487,6 +566,7 @@ impl ShellCommandExecutor {
cursor: CURSOR_MARKER,
is_alt_screen_active: model.is_alt_screen_active(),
is_preempted: false,
wake_reason: "control_handed_back",
}
}
}
@@ -501,7 +581,13 @@ impl ShellCommandExecutor {
ActionExecution::new_async(transfer_future, move |result, ctx| {
// Clean up.
if let Some(handle) = handle.upgrade(ctx) {
handle.update(ctx, |me, _| {
handle.update(ctx, |me, ctx| {
log_shell_wait_result(
conversation_id,
&diagnostic_action_id,
&result,
ctx,
);
me.prune_closed_senders(&block_selector);
me.control_handback_sender = None;
});
@@ -640,6 +726,11 @@ impl ShellCommandExecutor {
cursor: CURSOR_MARKER,
is_alt_screen_active: selected_block_owns_alt_screen,
is_preempted,
wake_reason: match wake_reason {
WakeReason::BlockFinished => "block_finished",
WakeReason::Timeout => "timeout",
WakeReason::ForceRefresh => "force_refresh",
},
}
}
}
@@ -704,29 +795,29 @@ impl ShellCommandExecutor {
let terminal_model = self.terminal_model.lock();
// Find every pending poll whose selector resolves to this block. Multiple provider polls
// may legitimately wait on the same command and must be refreshed together.
let matching_selector = self
let matching_selectors = self
.force_refresh_senders
.keys()
.find(|selector| {
.filter(|selector| {
selector.get_block(&terminal_model).is_some_and(|block| {
block.id() == block_id
&& block.is_active_and_long_running()
&& !block.finished()
})
})
.cloned();
.cloned()
.collect::<Vec<_>>();
drop(terminal_model);
if let Some(selector) = matching_selector {
let mut refreshed = false;
for selector in matching_selectors {
if let Some(senders) = self.force_refresh_senders.remove(&selector) {
let mut refreshed = false;
for sender in senders {
refreshed |= sender.send(()).is_ok();
}
return refreshed;
}
}
false
refreshed
}
pub(super) fn preprocess_action(
@@ -738,6 +829,57 @@ impl ShellCommandExecutor {
}
}
fn log_shell_wait_result(
conversation_id: crate::AIConversationId,
action_id: &AIAgentActionId,
result: &ActionResult,
ctx: &mut ModelContext<ShellCommandExecutor>,
) {
crate::ai::remote_logging::log_model_event(
ctx,
crate::ai::remote_logging::RemoteLogRecord {
level: crate::ai::remote_logging::RemoteLogLevel::Info,
message: "Shell command wait resolved".to_string(),
context: serde_json::json!({
"event": "shell_command_wait_resolved",
"conversation_id": conversation_id.to_string(),
"action_id": action_id.to_string(),
"result": shell_result_diagnostics(result),
}),
},
);
}
fn shell_result_diagnostics(result: &ActionResult) -> serde_json::Value {
match result {
ActionResult::CommandFinished {
block_id,
output,
exit_code,
start_ts,
completed_ts,
} => serde_json::json!({
"state": "finished", "block_id": block_id.to_string(),
"exit_code": exit_code.value(), "output_bytes": output.len(),
"start_ts": start_ts, "completed_ts": completed_ts,
}),
ActionResult::LongRunningCommandSnapshot {
block_id,
grid_contents,
is_alt_screen_active,
is_preempted,
wake_reason,
..
} => serde_json::json!({
"state": "running", "block_id": block_id.to_string(),
"snapshot_bytes": grid_contents.len(), "alt_screen": is_alt_screen_active,
"preempted": is_preempted, "wake_reason": wake_reason,
}),
ActionResult::Cancelled => serde_json::json!({"state": "cancelled"}),
ActionResult::BlockNotFound => serde_json::json!({"state": "block_not_found"}),
}
}
fn command_for_execution(
command: &str,
shell_type: Option<ShellType>,
@@ -863,6 +1005,7 @@ fn action_result_for_write_to_long_running_shell_command(
cursor,
is_alt_screen_active,
is_preempted,
..
} => AIAgentActionResultType::WriteToLongRunningShellCommand(
WriteToLongRunningShellCommandResult::Snapshot {
block_id,
@@ -909,6 +1052,7 @@ fn action_result_for_read_shell_command_output(
cursor,
is_alt_screen_active,
is_preempted,
..
} => AIAgentActionResultType::ReadShellCommandOutput(
ReadShellCommandOutputResult::LongRunningCommandSnapshot {
command,
@@ -954,6 +1098,7 @@ fn action_result_for_transfer_shell_command_control_to_user(
cursor,
is_alt_screen_active,
is_preempted,
..
} => AIAgentActionResultType::TransferShellCommandControlToUser(
TransferShellCommandControlToUserResult::Snapshot {
block_id,
@@ -1020,6 +1165,7 @@ enum ActionResult {
cursor: &'static str,
is_alt_screen_active: bool,
is_preempted: bool,
wake_reason: &'static str,
},
Cancelled,
BlockNotFound,
@@ -23,6 +23,7 @@ use crate::terminal::model::session::Sessions;
use crate::terminal::model::terminal_model::{BlockIndex, TerminalModel};
use crate::terminal::model_events::{ModelEvent, ModelEventDispatcher};
use crate::terminal::shell::ShellType;
use crate::test_util::settings::initialize_settings_for_tests;
use crate::AIConversationId;
#[test]
@@ -178,16 +179,43 @@ fn force_refresh_block_reports_and_resolves_matching_poll() {
)
});
let action_id = AIAgentActionId::from("refresh-command".to_string());
terminal_model
.lock()
.block_list_mut()
.active_block_mut()
.set_agent_interaction_mode_for_requested_command(
action_id.clone(),
None,
AIConversationId::new(),
);
let (tx, mut rx) = oneshot::channel();
let (action_tx, mut action_rx) = oneshot::channel();
let (second_tx, mut second_rx) = oneshot::channel();
let (unrelated_tx, mut unrelated_rx) = oneshot::channel();
let unrelated_selector = BlockSelector::Id(BlockId::new());
executor.update(&mut app, |executor, _| {
executor
.force_refresh_senders
.insert(BlockSelector::Id(block_id.clone()), vec![tx]);
executor.force_refresh_senders.insert(
BlockSelector::RequestedCommandId(action_id),
vec![action_tx, second_tx],
);
executor
.force_refresh_senders
.insert(unrelated_selector.clone(), vec![unrelated_tx]);
assert!(executor.force_refresh_block(&block_id));
assert!(executor
.force_refresh_senders
.contains_key(&unrelated_selector));
assert!(!executor.force_refresh_block(&block_id));
});
assert!(matches!(rx.try_recv(), Ok(Some(()))));
assert!(matches!(action_rx.try_recv(), Ok(Some(()))));
assert!(matches!(second_rx.try_recv(), Ok(Some(()))));
assert!(matches!(unrelated_rx.try_recv(), Ok(None)));
let (tx, _rx) = oneshot::channel();
executor.update(&mut app, |executor, _| {
@@ -205,6 +233,7 @@ fn force_refresh_block_reports_and_resolves_matching_poll() {
#[test]
fn requested_command_waiter_survives_early_metadata_and_resolves_after_association() {
App::test((), |mut app| async move {
initialize_settings_for_tests(&mut app);
let terminal_view_id = EntityId::new();
let sessions = app.add_model(|_| Sessions::new_for_test());
let (_model_events_tx, model_events_rx) = unbounded();
@@ -393,3 +422,20 @@ fn force_refresh_wakes_on_completion_poll_with_preempted_snapshot() {
));
});
}
#[test]
fn shell_diagnostics_report_snapshot_reason_without_output_contents() {
let result = ActionResult::LongRunningCommandSnapshot {
block_id: BlockId::new(),
grid_contents: "private terminal output".to_string(),
cursor: "cursor",
is_alt_screen_active: false,
is_preempted: true,
wake_reason: "force_refresh",
};
let metadata = super::shell_result_diagnostics(&result);
assert_eq!(metadata["state"], "running");
assert_eq!(metadata["wake_reason"], "force_refresh");
assert_eq!(metadata["snapshot_bytes"], "private terminal output".len());
assert!(!metadata.to_string().contains("private terminal output"));
}
@@ -698,6 +698,19 @@ impl CLISubagentController {
let block_id = block_id.clone();
let refreshed =
executor_handle.update(ctx, |executor, _| executor.force_refresh_block(&block_id));
#[cfg(not(target_family = "wasm"))]
crate::ai::remote_logging::log_model_event(
ctx,
crate::ai::remote_logging::RemoteLogRecord {
level: crate::ai::remote_logging::RemoteLogLevel::Info,
message: "Shell command refresh requested".to_string(),
context: serde_json::json!({
"event": "shell_command_refresh_requested",
"block_id": block_id.to_string(),
"refreshed": refreshed,
}),
},
);
if refreshed {
self.active_subagents_by_block.entry(block_id).or_default();
}
@@ -1419,7 +1419,7 @@ impl RequestedCommandView {
}
Some(AIActionStatus::RunningAsync) if self.action_type.is_requested_command() => {
config = config.with_interaction_mode(InteractionMode::ManuallyExpandable(
self.get_expansion_config(requested_command_block, app),
self.get_expansion_config(app),
));
}
Some(AIActionStatus::Finished(result)) => {
@@ -1447,7 +1447,7 @@ impl RequestedCommandView {
if should_be_expandable {
config = config.with_interaction_mode(InteractionMode::ManuallyExpandable(
self.get_expansion_config(requested_command_block, app),
self.get_expansion_config(app),
));
} else {
// Commands cancelled before execution should be right clickable only.
@@ -1471,7 +1471,7 @@ impl RequestedCommandView {
// for this action, the command ran and the header should be expandable.
if requested_command_block.is_some_and(|block| block.finished()) {
config = config.with_interaction_mode(InteractionMode::ManuallyExpandable(
self.get_expansion_config(requested_command_block, app),
self.get_expansion_config(app),
));
}
}
@@ -1487,11 +1487,7 @@ impl RequestedCommandView {
}
}
fn get_expansion_config(
&self,
requested_command_block: Option<&Block>,
app: &AppContext,
) -> ExpandedConfig {
fn get_expansion_config(&self, app: &AppContext) -> ExpandedConfig {
let command_text_for_callback = self.command_text().to_string();
let mut expansion_config =
ExpandedConfig::new(self.is_header_expanded, self.header_mouse_state.clone())
@@ -1499,16 +1495,11 @@ impl RequestedCommandView {
ctx.dispatch_typed_action(AIBlockAction::StoreRightClickedCommand {
command: command_text_for_callback.clone(),
});
})
.with_toggle_callback(|ctx| {
ctx.dispatch_typed_action(RequestedCommandViewAction::ToggleExpanded);
});
let is_active_agent_monitored_command = requested_command_block
.is_some_and(|block| block.is_agent_monitoring() && !block.finished());
if !is_active_agent_monitored_command {
expansion_config = expansion_config.with_toggle_callback(|ctx| {
ctx.dispatch_typed_action(RequestedCommandViewAction::ToggleExpanded);
});
}
if InputModeSettings::as_ref(app).is_pinned_to_top()
&& self.action_type.is_requested_command()
{
@@ -1723,7 +1714,7 @@ impl View for RequestedCommandView {
command_output_grid
{
let mut output_grid = TerminalGridSnapshotElement::new(
&grid,
grid,
row_count,
colors,
override_colors,
+14 -139
View File
@@ -11,7 +11,6 @@ use futures::stream::AbortHandle;
use galaxy_completer::completer::CompletionContext;
use galaxy_core::features::FeatureFlag;
use galaxyui::{AppContext, Entity, EntityId, ModelContext, ModelHandle, SingletonEntity};
use input_classifier::util::{is_agent_follow_up_input, is_one_off_natural_language_word};
pub use input_classifier::{InputClassifierDecisionSource, InputType};
use instant::Instant;
use parking_lot::FairMutex;
@@ -90,20 +89,15 @@ use super::agent_view::AgentViewEntryOrigin;
use super::context_model::BlocklistAIContextModel;
use super::telemetry_banner::should_collect_ai_ugc_telemetry;
use super::{ConversationSelectionEvent, ConversationSelectionHandle};
use crate::input_classifier::InputClassifierModel;
use crate::settings::{AISettings, AISettingsChangedEvent, InputBoxType, InputSettings};
use crate::terminal::cli_agent_sessions::{
CLIAgentInputState, CLIAgentSessionsModel, CLIAgentSessionsModelEvent,
};
use crate::terminal::input::decorations::ParsedTokensSnapshot;
use crate::terminal::model::rich_content::RichContentType;
use crate::terminal::model::session::SessionId;
use crate::terminal::{History, TerminalModel};
use crate::{report_if_error, send_telemetry_from_ctx, PrivacySettings, TelemetryEvent};
/// Cutoff score for deciding an user input matches a history command entry.
const HISTORY_ENTRY_MATCH_CUTOFF: f32 = 0.9;
/// Duration to temporarily disable autodetection during operations like history selection.
const AUTODETECTION_DISABLE_DURATION_MS: u64 = 250;
@@ -696,7 +690,7 @@ impl BlocklistAIInputModel {
/// nothing.
///
/// When `session_id` is `Some`, history matching is performed. The `completion_context`
/// is always used for alias expansion (callers without a live session should pass an
/// supplies the shell command inventory (callers without a live session should pass an
/// `EmptyCompletionContext`).
pub fn detect_and_set_input_type<C: CompletionContext + Clone + Send + 'static>(
&mut self,
@@ -760,10 +754,7 @@ impl BlocklistAIInputModel {
.into_iter()
.flatten()
.filter_map(|entry| {
if entry
.exit_code
.is_some_and(|code| code.was_command_not_found())
{
if !crate::input_classifier::is_verified_history_command(entry.exit_code) {
return None;
}
Some(entry.command.to_string())
@@ -771,105 +762,27 @@ impl BlocklistAIInputModel {
.collect::<Vec<String>>()
});
let buffer_cloned = input.buffer_text.clone();
let other_buffer_cloned = buffer_cloned.clone();
let other_buffer_cloned = input.buffer_text.clone();
let current_input_type = self.input_type();
let is_udi_enabled = InputSettings::as_ref(ctx).is_universal_developer_input_enabled(ctx);
// Determine if the input is a follow-up to an AI block.
let is_agent_follow_up = {
let model = self.model.lock();
let block_list = model.block_list();
let block_index = block_list.last_non_hidden_block_by_index();
match block_list.last_non_hidden_rich_content_block_after_block(block_index) {
Some((_, content)) => content.content_type == Some(RichContentType::AIBlock),
_ => false,
}
};
let classifier = InputClassifierModel::as_ref(ctx).classifier();
let generation = self.autodetection_generation;
#[cfg(not(target_family = "wasm"))]
let use_local_model = AISettings::as_ref(ctx).should_use_local_model();
let handle = ctx
.spawn(
async move {
#[cfg(not(target_family = "wasm"))]
if use_local_model {
// A cancelled debounce never starts inference. The local worker
// also cancels obsolete requests already queued or generating.
warpui::r#async::Timer::after(std::time::Duration::from_millis(100)).await;
let result = crate::local_inference::classify(
buffer_cloned.to_string(),
history_entries.as_deref().unwrap_or_default(),
is_agent_follow_up,
)
.await;
return match result {
Some(input_type) => (
input_type,
InputClassifierDecisionSource::LocalLanguageModel.into(),
),
None => (
current_input_type,
InputClassifierDecisionSource::InputClassifierFallbackCurrentInput
.into(),
),
};
}
// First check if the token is a natural language word, if current input type is AI
if matches!(current_input_type, InputType::AI)
&& is_one_off_natural_language_word(first_token_str.to_lowercase().as_str())
{
return (
InputType::AI,
InputClassifierDecisionSource::NaturalLanguageOneOffAllowlist.into(),
);
}
// If this is clearly intended to be a follow-up to an AI block, classify it as AI.
if is_agent_follow_up
&& is_agent_follow_up_input(&buffer_cloned.trim().to_lowercase())
{
return (
InputType::AI,
InputTypeAutoDetectionSource::NaturalLanguageAgentFollowUpAllowList,
);
}
// If we have history entries (i.e., a live session), check for
// close matches to short-circuit as shell input.
if let Some(history_entries) = history_entries {
if has_any_close_matches(
&buffer_cloned,
history_entries.iter().map(AsRef::as_ref),
HISTORY_ENTRY_MATCH_CUTOFF,
)
.await
{
return (InputType::Shell, InputTypeAutoDetectionSource::HistoryMatch);
}
}
// Yield so that an attempt to abort the classification is handled. We do this periodically
// so that we can skip doing additional expensive work if the classification is aborted.
futures_lite::future::yield_now().await;
let input =
galaxy_completer::util::expand_aliases(input, &completion_context).await;
futures_lite::future::yield_now().await;
let context = input_classifier::Context {
// Coalesce keystrokes; aborting this task prevents obsolete decisions.
warpui::r#async::Timer::after(std::time::Duration::from_millis(100)).await;
let input_type = crate::input_classifier::detect_input_type(
&input,
&completion_context,
history_entries.as_deref().unwrap_or_default(),
current_input_type,
is_agent_follow_up,
};
let classification =
classifier.detect_input_type(input.clone(), &context).await;
futures_lite::future::yield_now().await;
(classification.input_type, classification.source.into())
);
(
input_type,
InputClassifierDecisionSource::ShellCommandLookup.into(),
)
},
move |me, (new_input_type, decision_source), ctx| {
// In theory, we shouldn't need to check this, as we only run autodetection if the input
@@ -958,41 +871,3 @@ impl BlocklistAIInputEvent {
impl Entity for BlocklistAIInputModel {
type Event = BlocklistAIInputEvent;
}
/// Returns whether the set of possibilities contains any close matches
/// to the provided word, using the given similarity threshold.
///
/// Adapted from [`difflib::get_close_matches`], but an async function with
/// periodic yields such that the operation can be aborted if necessary. Also,
/// unlike the original function, this returns as soon as it finds any match
/// above the threshold, instead of finding _all_ matches above the threshold
/// and returning the top N matches.
async fn has_any_close_matches<'a>(
word: &str,
possibilities: impl Iterator<Item = &'a str>,
cutoff: f32,
) -> bool {
const BATCH_SIZE: usize = 50;
if !(0.0..=1.0).contains(&cutoff) {
panic!("Cutoff must be greater than 0.0 and lower than 1.0");
}
let mut matcher = difflib::sequencematcher::SequenceMatcher::new("", word);
for (idx, i) in possibilities.enumerate() {
// Periodically, yield to the executor so this task can be aborted if
// requested.
if idx % BATCH_SIZE == 0 {
futures_lite::future::yield_now().await;
}
matcher.set_first_seq(i);
// The fast ratio computations produce an upper bound on the value of
// ratio, so if a faster check fails, the slower checks are guaranteed
// to also fail.
if matcher.real_quick_ratio() >= cutoff && matcher.ratio() >= cutoff {
return true;
}
}
false
}