mirror of
https://github.com/ultraworkers/claw-code.git
synced 2026-05-30 11:45:20 +08:00
fix: interactive_only hint omits --resume for non-resume-safe commands (#829)
Commands like /commit, /pr, /issue, /bughunter, /ultraplan are interactive-only and NOT resume-safe. Previously the generic interactive_only error always suggested 'claw --resume SESSION.jsonl /commit', which would just re-trigger interactive_only. Fix: check commands::resume_supported_slash_commands() in the SlashCommand::Ok(Some(cmd)) arm. Resume-safe commands get the full --resume suggestion; non-resume-safe commands only say 'Start claw'. Also update two existing unit tests whose assertions checked for the old 'interactive-only' substring (now 'interactive_only:' prefix). Two new integration tests: - non_resume_safe_interactive_only_hint_omits_resume_suggestion - resume_safe_interactive_only_hint_includes_resume_suggestion 572 tests pass, 1 pre-existing worker_boot failure unrelated.
This commit is contained in:
parent
fdfb9f4dc1
commit
ac5b19dee1
@ -1755,12 +1755,26 @@ fn parse_direct_slash_cli_action(
|
|||||||
}
|
}
|
||||||
Ok(Some(command)) => Err({
|
Ok(Some(command)) => Err({
|
||||||
let _ = command;
|
let _ = command;
|
||||||
format!(
|
let command_name = &rest[0];
|
||||||
// #738: newline before remediation so split_error_hint populates hint field
|
// #829: only suggest --resume when the command is actually
|
||||||
"slash command {command_name} is interactive-only.\nStart `claw` and run it there, or use `claw --resume SESSION.jsonl {command_name}` / `claw --resume {latest} {command_name}` when the command is marked [resume] in /help.",
|
// resume-safe. Non-resume-safe commands (e.g. /commit, /pr)
|
||||||
command_name = rest[0],
|
// previously suggested --resume, which just re-triggered
|
||||||
latest = LATEST_SESSION_REFERENCE,
|
// interactive_only on a second invocation.
|
||||||
)
|
let bare_name = command_name.trim_start_matches('/');
|
||||||
|
let is_resume_safe = commands::resume_supported_slash_commands()
|
||||||
|
.iter()
|
||||||
|
.any(|spec| spec.name == bare_name);
|
||||||
|
if is_resume_safe {
|
||||||
|
format!(
|
||||||
|
// #738: newline before remediation so split_error_hint populates hint field
|
||||||
|
"interactive_only: slash command {command_name} requires a live session.\nStart `claw` and run it there, or use `claw --resume SESSION.jsonl {command_name}` / `claw --resume {latest} {command_name}`.",
|
||||||
|
latest = LATEST_SESSION_REFERENCE,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
format!(
|
||||||
|
"interactive_only: slash command {command_name} requires a live REPL session.\nStart `claw` and run it there."
|
||||||
|
)
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
Ok(None) => Err(format!("unknown subcommand: {}", rest[0])),
|
Ok(None) => Err(format!("unknown subcommand: {}", rest[0])),
|
||||||
Err(error) => Err(error.to_string()),
|
Err(error) => Err(error.to_string()),
|
||||||
@ -13906,8 +13920,15 @@ mod tests {
|
|||||||
);
|
);
|
||||||
let error = parse_args(&["/status".to_string()])
|
let error = parse_args(&["/status".to_string()])
|
||||||
.expect_err("/status should remain REPL-only when invoked directly");
|
.expect_err("/status should remain REPL-only when invoked directly");
|
||||||
assert!(error.contains("interactive-only"));
|
// #829: prefix changed from "interactive-only" to "interactive_only:"
|
||||||
assert!(error.contains("claw --resume SESSION.jsonl /status"));
|
assert!(
|
||||||
|
error.contains("interactive_only:"),
|
||||||
|
"expected interactive_only: prefix, got: {error}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
error.contains("claw --resume SESSION.jsonl /status"),
|
||||||
|
"expected --resume suggestion for resume-safe /status, got: {error}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -13929,8 +13950,9 @@ mod tests {
|
|||||||
for alias in ["/plugin", "/plugins", "/marketplace"] {
|
for alias in ["/plugin", "/plugins", "/marketplace"] {
|
||||||
let error = parse_args(&[alias.to_string()])
|
let error = parse_args(&[alias.to_string()])
|
||||||
.expect_err("valid plugin slash aliases are local/interactive, never prompts");
|
.expect_err("valid plugin slash aliases are local/interactive, never prompts");
|
||||||
|
// #829: prefix changed from "interactive-only" to "interactive_only:"
|
||||||
assert!(
|
assert!(
|
||||||
error.contains("interactive-only"),
|
error.contains("interactive_only:") || error.contains("interactive-only"),
|
||||||
"{alias} should reject as an interactive plugin command outside the REPL, got: {error}"
|
"{alias} should reject as an interactive plugin command outside the REPL, got: {error}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4015,3 +4015,42 @@ fn approve_deny_outside_repl_emits_interactive_only() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #829: interactive_only hint must NOT suggest --resume for non-resume-safe commands
|
||||||
|
#[test]
|
||||||
|
fn non_resume_safe_interactive_only_hint_omits_resume_suggestion() {
|
||||||
|
let root = unique_temp_dir("non-resume-hint-829");
|
||||||
|
std::fs::create_dir_all(&root).expect("create temp dir");
|
||||||
|
// /commit, /pr, /issue, /bughunter, /ultraplan are not resume-safe
|
||||||
|
for cmd in &["/commit", "/pr", "/issue", "/bughunter", "/ultraplan"] {
|
||||||
|
let output = run_claw(&root, &["--output-format", "json", cmd], &[]);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let j: serde_json::Value = serde_json::from_str(stdout.trim())
|
||||||
|
.unwrap_or_else(|_| panic!("{cmd} must emit JSON (#829), got: {stdout:?}"));
|
||||||
|
assert_eq!(
|
||||||
|
j["error_kind"], "interactive_only",
|
||||||
|
"{cmd} must emit interactive_only (#829): {j}"
|
||||||
|
);
|
||||||
|
let hint = j["hint"].as_str().unwrap_or("");
|
||||||
|
assert!(
|
||||||
|
!hint.contains("--resume"),
|
||||||
|
"{cmd} hint must not suggest --resume for non-resume-safe command (#829): hint={hint:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #829: resume-safe commands should still suggest --resume in the hint
|
||||||
|
#[test]
|
||||||
|
fn resume_safe_interactive_only_hint_includes_resume_suggestion() {
|
||||||
|
let root = unique_temp_dir("resume-hint-829");
|
||||||
|
std::fs::create_dir_all(&root).expect("create temp dir");
|
||||||
|
let output = run_claw(&root, &["--output-format", "json", "/diff"], &[]);
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
let j: serde_json::Value = serde_json::from_str(stdout.trim())
|
||||||
|
.unwrap_or_else(|_| panic!("/diff must emit JSON (#829), got: {stdout:?}"));
|
||||||
|
let hint = j["hint"].as_str().unwrap_or("");
|
||||||
|
assert!(
|
||||||
|
hint.contains("--resume"),
|
||||||
|
"/diff hint must suggest --resume (it is resume-safe) (#829): hint={hint:?}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user