feat(cli): --continue/-c flag forwards to TUI resume path

Other agent: root_tui_passthrough() builds forwarded args, rejects
--continue + -p combo (directs to codewhale exec --continue).
Tests: parses_top_level_continue, top_level_continue_rejects_one_shot.

Session picker: formatting cleanup on test calls.
This commit is contained in:
Hunter Bown
2026-05-24 15:36:54 -05:00
parent ea0a129f15
commit 698722c946
2 changed files with 79 additions and 19 deletions
+56 -15
View File
@@ -102,6 +102,9 @@ struct Cli {
/// YOLO mode: auto-approve all tools
#[arg(long)]
yolo: bool,
/// Continue the most recent interactive session for this workspace.
#[arg(short = 'c', long = "continue")]
continue_session: bool,
#[arg(short = 'p', long = "prompt", value_name = "PROMPT")]
prompt_flag: Option<String>,
#[arg(
@@ -555,26 +558,42 @@ fn run() -> Result<()> {
Some(Commands::Update) => update::run_update(),
None => {
let resolved_runtime = resolve_runtime_for_dispatch(&mut store, &runtime_overrides);
let mut forwarded = Vec::new();
let prompt = cli.prompt_flag.iter().chain(cli.prompt.iter()).fold(
String::new(),
|mut acc, part| {
if !acc.is_empty() {
acc.push(' ');
}
acc.push_str(part);
acc
},
);
if !prompt.is_empty() {
forwarded.push("--prompt".to_string());
forwarded.push(prompt);
}
let forwarded = root_tui_passthrough(&cli)?;
delegate_to_tui(&cli, &resolved_runtime, forwarded)
}
}
}
fn root_tui_passthrough(cli: &Cli) -> Result<Vec<String>> {
let mut forwarded = Vec::new();
if cli.continue_session {
forwarded.push("--continue".to_string());
}
let prompt =
cli.prompt_flag
.iter()
.chain(cli.prompt.iter())
.fold(String::new(), |mut acc, part| {
if !acc.is_empty() {
acc.push(' ');
}
acc.push_str(part);
acc
});
if !prompt.is_empty() {
if cli.continue_session {
bail!(
"`codewhale --continue` resumes the interactive TUI. Use `codewhale exec --continue <PROMPT>` to continue a session non-interactively."
);
}
forwarded.push("--prompt".to_string());
forwarded.push(prompt);
}
Ok(forwarded)
}
fn resolve_runtime_for_dispatch(
store: &mut ConfigStore,
runtime_overrides: &CliRuntimeOverrides,
@@ -2651,6 +2670,27 @@ mod tests {
assert!(cli.prompt.is_empty());
}
#[test]
fn parses_top_level_continue_for_interactive_resume() {
let cli = parse_ok(&["codewhale", "--continue"]);
assert!(cli.continue_session);
assert!(cli.prompt_flag.is_none());
assert!(cli.prompt.is_empty());
assert_eq!(root_tui_passthrough(&cli).unwrap(), vec!["--continue"]);
}
#[test]
fn top_level_continue_rejects_one_shot_prompt() {
let cli = parse_ok(&["codewhale", "--continue", "-p", "follow up"]);
let err = root_tui_passthrough(&cli).expect_err("prompted continue should be rejected");
assert!(
err.to_string()
.contains("codewhale exec --continue <PROMPT>")
);
}
#[test]
fn parses_split_top_level_prompt_words_for_windows_cmd_shims() {
let cli = parse_ok(&["deepseek", "hello", "world"]);
@@ -2711,6 +2751,7 @@ mod tests {
"--mouse-capture",
"--no-mouse-capture",
"--skip-onboarding",
"--continue",
"--prompt",
] {
assert!(
+23 -4
View File
@@ -1069,7 +1069,9 @@ mod tests {
"A very long title that should be truncated by the list pane width",
)];
let width = 24;
let lines = build_list_lines(&sessions, 0, width, 0, 5, false, "", "recent", false, false, "", None);
let lines = build_list_lines(
&sessions, 0, width, 0, 5, false, "", "recent", false, false, "", None,
);
for line in lines {
let rendered_width: usize = line.spans.iter().map(|span| span.content.width()).sum();
@@ -1086,7 +1088,9 @@ mod tests {
test_session(1, "first session"),
test_session(2, "second session"),
];
let lines = build_list_lines(&sessions, 1, 80, 0, 5, false, "", "recent", false, false, "", None);
let lines = build_list_lines(
&sessions, 1, 80, 0, 5, false, "", "recent", false, false, "", None,
);
let selected_line = lines
.iter()
@@ -1111,7 +1115,20 @@ mod tests {
let mut forked = test_session(1, "forked path");
forked.parent_session_id = Some("parent-session-abcdef".to_string());
forked.forked_from_message_count = Some(3);
let lines = build_list_lines(&[forked], 0, 120, 0, 5, false, "", "recent", false, false, "", None);
let lines = build_list_lines(
&[forked],
0,
120,
0,
5,
false,
"",
"recent",
false,
false,
"",
None,
);
let rendered = lines
.iter()
@@ -1128,7 +1145,9 @@ mod tests {
test_session(1, "first session"),
test_session(2, "second session"),
];
let lines = build_list_lines(&sessions, 0, 80, 0, 5, false, "", "recent", false, false, "", None);
let lines = build_list_lines(
&sessions, 0, 80, 0, 5, false, "", "recent", false, false, "", None,
);
let rendered = lines
.iter()