mirror of
https://github.com/CyberSecurityUP/NeuroSploit.git
synced 2026-10-03 22:46:57 +02:00
fix(cli): make a parked one-shot run resumable — accept /continue on stdin
The one-shot `neurosploit run|whitebox|greybox <target>` path dropped the pause/resume handles and never read stdin, so when the pool parked on quota/auth exhaustion it printed "type /continue …" into a void — nothing accepted it and the process hung forever on the parked task; only Ctrl-C worked. This is exactly the "can't /continue, it's stuck" a run that exhausts during recon hits. run_mode now, at a real terminal, reads stdin and accepts: /continue [provider:model] resume (optionally switching model) /model provider:model switch provider/model, then resume Ctrl-C still stops and offers a partial report; it also clears the pause so a parked task can't swallow the interrupt. Over a pipe (CI/web) stdin is skipped. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
1bfaf1837c
commit
d0219f65f5
1 file changed
+66
-10
@@ -1348,21 +1348,77 @@ pub(crate) fn finalize_run(mut out: RunOutput, workdir: &Path) -> RunOutput {
|
||||
|
||||
async fn run_mode(base: &Path, cfg: RunConfig, mcp: bool, mode: Mode) -> anyhow::Result<RunOutput> {
|
||||
subscription_preflight(&cfg).await;
|
||||
let Spawned { mut task, mut rx, cancel, workdir, .. } = spawn_engagement(base, cfg, mcp, mode);
|
||||
// Keep the pause/resume handles: a one-shot run that parks on quota/auth
|
||||
// exhaustion must still be resumable. Without reading stdin here the pool's
|
||||
// "type /continue" notice would be a dead end — nothing would accept it, and
|
||||
// the process would hang forever on the parked task.
|
||||
let Spawned { mut task, mut rx, cancel, paused, resume, fallback, workdir, .. } =
|
||||
spawn_engagement(base, cfg, mcp, mode);
|
||||
let printer = tokio::spawn(async move {
|
||||
while let Some(line) = rx.recv().await { render_line(&line); }
|
||||
});
|
||||
|
||||
// Read operator input ONLY at a real terminal: `/continue [provider:model]`
|
||||
// to resume a parked run, `/model provider:model` to switch then resume.
|
||||
// Over a pipe (CI, web) there is no one to type, so we skip it and rely on
|
||||
// the auto-resume / Ctrl-C paths instead.
|
||||
use std::io::IsTerminal as _;
|
||||
let interactive = std::io::stdin().is_terminal();
|
||||
let mut stdin_lines = interactive.then(|| {
|
||||
use tokio::io::AsyncBufReadExt as _;
|
||||
tokio::io::BufReader::new(tokio::io::stdin()).lines()
|
||||
});
|
||||
|
||||
let resume_run = |arg: &str| {
|
||||
let arg = arg.trim();
|
||||
if !arg.is_empty() {
|
||||
let m = ModelRef::parse(arg);
|
||||
println!(" \x1b[1;35m▶ resuming with\x1b[0m {}:{}", m.provider, m.model);
|
||||
if let Ok(mut fb) = fallback.lock() { fb.push(m); }
|
||||
} else {
|
||||
println!(" \x1b[1;35m▶ resuming\x1b[0m — retrying with the current model(s).");
|
||||
}
|
||||
paused.store(false, std::sync::atomic::Ordering::Relaxed);
|
||||
resume.notify_waiters();
|
||||
};
|
||||
|
||||
let mut cancelled = false;
|
||||
let out: RunOutput = tokio::select! {
|
||||
r = &mut task => r.unwrap_or_default(),
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
cancelled = true;
|
||||
cancel.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
println!("\n \x1b[33m⏸ stopping — finishing in-flight work… (Ctrl-C again to abort now)\x1b[0m");
|
||||
tokio::select! {
|
||||
r = &mut task => r.unwrap_or_default(),
|
||||
_ = tokio::signal::ctrl_c() => { task.abort(); println!(" \x1b[31m✗ aborted.\x1b[0m"); RunOutput::default() }
|
||||
let out: RunOutput = loop {
|
||||
tokio::select! {
|
||||
r = &mut task => break r.unwrap_or_default(),
|
||||
_ = tokio::signal::ctrl_c() => {
|
||||
cancelled = true;
|
||||
cancel.store(true, std::sync::atomic::Ordering::Relaxed);
|
||||
paused.store(false, std::sync::atomic::Ordering::Relaxed); // don't let a parked task hold the Ctrl-C
|
||||
resume.notify_waiters();
|
||||
println!("\n \x1b[33m⏸ stopping — finishing in-flight work… (Ctrl-C again to abort now)\x1b[0m");
|
||||
break tokio::select! {
|
||||
r = &mut task => r.unwrap_or_default(),
|
||||
_ = tokio::signal::ctrl_c() => { task.abort(); println!(" \x1b[31m✗ aborted.\x1b[0m"); RunOutput::default() }
|
||||
};
|
||||
}
|
||||
line = async { match stdin_lines.as_mut() { Some(l) => l.next_line().await, None => Ok(None) } }, if stdin_lines.is_some() => {
|
||||
match line {
|
||||
Ok(Some(l)) => {
|
||||
let t = l.trim();
|
||||
let (cmd, arg) = t.split_once(char::is_whitespace).unwrap_or((t, ""));
|
||||
match cmd {
|
||||
"/continue" | "/resume" => {
|
||||
if paused.load(std::sync::atomic::Ordering::Relaxed) { resume_run(arg); }
|
||||
else { println!(" run is not paused — it's still working."); }
|
||||
}
|
||||
"/model" => {
|
||||
// Switch provider/model and resume if parked.
|
||||
if arg.trim().is_empty() { println!(" usage: /model <provider:model>"); }
|
||||
else { resume_run(arg); }
|
||||
}
|
||||
"" => {}
|
||||
other => println!(" while a run is active only /continue [provider:model], /model <provider:model> and Ctrl-C are accepted (got {other})"),
|
||||
}
|
||||
}
|
||||
Ok(None) => { stdin_lines = None; } // EOF — stop polling stdin
|
||||
Err(_) => { stdin_lines = None; }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user