fix: unknown --profile fails config commands, MCP reload panel refreshes, and CI reds from 34bf6568f2

- cli: 'codewhale --profile <missing> config <cmd>' now exits 1 naming the available profiles instead of printing the unrelated config.
- tui: /mcp reload marks MCP initializing until the supervised pass finishes, so the open Extensions panel re-reads the settled snapshot instead of staying on interim not-connected rows.
- cucumber PTY tests: 14 macOS-only failures came from tests that waited for the launch-time provider picker, which composer-first removed. Launch-card tests now start on the card; the three that need a live offline shell reach it by sending a first message, choosing Explore Offline and clearing the returned draft; the pointer test no longer sends a prompt whose 'Saved for later' status made its no-receipt baseline false.
- runtime-contract budget regenerated for the task_shell_start removal (Act tools 52 -> 51, bytes and tokens only shrink); Lint failed on the identity change.

Evidence: nextest --test cucumber --features long-running-tests on launch_card_pty|active_composer_pointer_pty|screen_mode_inline_pty|work_bar_keys_pty: 14 tests run: 14 passed, 0 failed. Focused cli+tui mcp/profile filter: 215 passed. Binary check: --profile bogus config list exits 1; plain config list exits 0. cargo fmt --check clean. Not run: full suite, clippy for this slice.

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
CodeWhale BotandClaude Sonnet 5.5 committed 2026-10-10 06:21:37 -07:00
1 parent 26249dfdb7
commit 94549b79e0
7 files changed
+61 -89

No files matched your search

+25
View File
@@ -2618,6 +2618,9 @@ fn run() -> Result<()> {
)
}
Some(Commands::Config(args)) => {
if let Some(profile) = cli.profile.as_deref() {
require_config_profile(store.path(), profile)?;
}
let resolved_runtime =
resolve_runtime_for_diagnostic_dispatch(&store, &runtime_overrides);
let session = start_cli_telemetry(
@@ -5233,6 +5236,28 @@ fn run_auth_migrate(store: &mut ConfigStore, secrets: &Secrets, dry_run: bool) -
Ok(())
}
fn require_config_profile(path: &Path, profile: &str) -> Result<()> {
let table = std::fs::read_to_string(path)
.ok()
.and_then(|text| toml::from_str::<toml::Table>(&text).ok());
let mut available = table
.as_ref()
.and_then(|table| table.get("profiles"))
.and_then(toml::Value::as_table)
.map(|profiles| profiles.keys().cloned().collect::<Vec<_>>())
.unwrap_or_default();
if available.iter().any(|name| name == profile) {
return Ok(());
}
available.sort();
let available = if available.is_empty() {
"none".to_string()
} else {
available.join(", ")
};
bail!("Profile not found (name not shown). Available profiles: {available}")
}
fn run_config_command(
store: &mut ConfigStore,
command: ConfigCommand,
+1
View File
@@ -1135,6 +1135,7 @@ pub(crate) async fn handle_mcp_ui_action(
// the reload-started line.
app.mcp_reload_required = false;
app.mcp_reload_in_flight = true;
app.mcp_initializing = true;
if is_reload {
add_mcp_message(
app,
@@ -81,23 +81,6 @@ fn run_pointer_submit_case(rows: u16, cols: u16) {
.spawn()
.expect("start distributed TUI binary");
// Walk the real onboarding: provider choice → Explore Offline (Ctrl+O)
// → ready screen → Enter into Tideline Startup → New session.
wait_or_panic(
&mut tui,
"Choose your model provider",
STARTUP_WAIT,
&format!("{size}: onboarding provider choice"),
);
tui.send(keys::key::ctrl('o'))
.expect("choose Explore Offline");
wait_or_panic(
&mut tui,
"You're ready.",
SETTLE_WAIT,
&format!("{size}: offline explore ready"),
);
tui.send(keys::key::enter()).expect("leave onboarding");
// PTY reads can split a redraw: the launch header arrives before the
// composer, with onboarding rows still on screen (Buildkite #1861/#1867).
// Wait for the input surface as well as the header before asserting it.
@@ -111,21 +94,7 @@ fn run_pointer_submit_case(rows: u16, cols: u16) {
.unwrap_or_else(|error| panic!("{size}: show the launch card and composer: {error}"));
tui.pump();
assert_startup_contract(tui.frame(), rows, cols, &size);
// Typing goes straight to the composer; Enter sends the first message
// and the session begins (the card dissolved on the first keystroke).
// type_line, not send+enter: a zero-gap PTY write is paste-classified
// and the immediate Enter would be absorbed as a pasted newline.
tui.type_line("start the session")
.expect("type and send the first prompt");
if tui
.wait_for(|frame| !frame.text().contains('\u{2442}'), STARTUP_WAIT)
.is_err()
{
panic!(
"{size}: Startup New session did not enter the live shell\n{}",
tui.diagnostics()
);
}
super::launch_card_pty::explore_offline_from_composer(&mut tui);
tui.wait_for_idle(Duration::from_millis(300), SETTLE_WAIT)
.expect("session shell settles");
// Prove the steady shell before clearing the onboarding seed. The clear
+14 -13
View File
@@ -470,7 +470,6 @@ fn start_with_options(
no_color,
keep_composer: false,
},
|_| {},
)
}
@@ -487,13 +486,8 @@ struct Launch<'a> {
keep_composer: bool,
}
/// Launch into `workspace` and walk first-run onboarding to home.
/// `on_provider_picker` sees the real first-run provider picker.
fn start_in(
workspace: SealedWorkspace,
launch: Launch<'_>,
on_provider_picker: impl FnOnce(&mut Harness),
) -> (SealedWorkspace, Harness) {
/// Launch into `workspace`; the composer-first launch card is home.
fn start_in(workspace: SealedWorkspace, launch: Launch<'_>) -> (SealedWorkspace, Harness) {
let Launch {
rows,
cols,
@@ -579,11 +573,6 @@ fn start_in(
.size(rows, cols)
.spawn()
.unwrap();
wait(&mut tui, "Choose your model provider");
on_provider_picker(&mut tui);
tui.send(keys::key::ctrl('o')).unwrap();
wait(&mut tui, "You're ready.");
tui.send(keys::key::enter()).unwrap();
wait(&mut tui, "New session");
if animated || keep_composer {
return (workspace, tui);
@@ -594,6 +583,18 @@ fn start_in(
(workspace, tui)
}
pub(crate) fn explore_offline_from_composer(tui: &mut Harness) {
wait(tui, "Type a message");
tui.type_line("seed").expect("send a first message");
wait(tui, "Choose your model provider");
tui.send(keys::key::ctrl('o')).unwrap();
wait(tui, "You're ready.");
tui.send(keys::key::enter()).unwrap();
tui.wait_for_idle(Duration::from_millis(300), WAIT).unwrap();
tui.send(keys::key::ctrl('u')).unwrap();
tui.wait_for_idle(Duration::from_millis(200), WAIT).unwrap();
}
fn wait(tui: &mut Harness, text: &str) {
if let Err(error) = tui.wait_for(|frame| frame.contains(text), WAIT) {
let transcript = tui.transcript();
@@ -59,20 +59,8 @@ fn offline_queue_late_unbracketed_submit_keeps_composer_and_commands_responsive(
.spawn()
.expect("start offline TUI");
wait_or_panic(
&mut tui,
"Choose your model provider",
STARTUP_WAIT,
"provider",
);
tui.send(keys::key::ctrl('o')).expect("Explore Offline");
wait_or_panic(&mut tui, "You're ready.", SETTLE_WAIT, "offline ready");
tui.send(keys::key::enter()).expect("leave onboarding");
wait_or_panic(&mut tui, "New session", STARTUP_WAIT, "launch card");
tui.wait_for_idle(Duration::from_millis(100), SETTLE_WAIT)
.expect("composer ready");
tui.send(keys::key::ctrl('u'))
.expect("clear suggested prompt");
super::launch_card_pty::explore_offline_from_composer(&mut tui);
tui.send(keys::key::text("late queue draft"))
.expect("raw prompt bytes");
@@ -241,15 +229,11 @@ fn wait_for_bottom_rows_painted(tui: &mut Harness, label: &str) {
}
}
/// Walk the real onboarding into deterministic offline-explore mode: no
/// provider, no credentials, no network.
/// Land on the composer-first launch card with no
/// provider, no credentials and no network.
fn enter_live_shell(tui: &mut Harness) {
wait_or_panic(tui, "Choose your model provider", STARTUP_WAIT, "provider");
tui.send(keys::key::ctrl('o'))
.expect("choose Explore Offline");
wait_or_panic(tui, "You're ready.", SETTLE_WAIT, "offline explore ready");
tui.send(keys::key::enter()).expect("leave onboarding");
wait_or_panic(tui, "New session", STARTUP_WAIT, "launch card");
super::launch_card_pty::explore_offline_from_composer(tui);
// Typing goes straight to the composer; Enter sends the first message
// and the session begins (the card dissolved on the first keystroke).
// type_line, not send+enter: a zero-gap PTY write is paste-classified
@@ -33,12 +33,6 @@ fn work_bar_opens_from_launch_with_legacy_and_enhanced_keys() {
.size(rows, cols)
.spawn()
.expect("start TUI");
tui.wait_for_text("Choose your model provider", Duration::from_secs(15))
.unwrap();
tui.send(keys::key::ctrl('o')).unwrap();
tui.wait_for_text("You're ready.", Duration::from_secs(5))
.unwrap();
tui.send(keys::key::enter()).unwrap();
tui.wait_for_text("New session", Duration::from_secs(15))
.unwrap();
tui.wait_for_idle(Duration::from_millis(250), Duration::from_secs(5))
+16 -18
View File
@@ -90,9 +90,9 @@
"modes": {
"act": {
"active": {
"bytes": 37873,
"bytes": 37703,
"identity_sha256": "e2d6dfb7168d48ee8d2a415feac7ff5b8864e18528c233a697055370144eb138",
"tokens_est": 9469,
"tokens_est": 9426,
"tool_names": [
"agent",
"bash",
@@ -112,9 +112,9 @@
"tools": 14
},
"full": {
"bytes": 85363,
"identity_sha256": "928e6417732837d26f1f26fe41ea6a11f5b8e3bfc4eeb3dd97a309379cffeff9",
"tokens_est": 21341,
"bytes": 84384,
"identity_sha256": "2a2ee97e7c482c625bc50c77db7f0e08d6bc67e2b4e73e7351385418700252bb",
"tokens_est": 21096,
"tool_names": [
"Git",
"Run",
@@ -151,7 +151,6 @@
"session_get",
"session_search",
"speech",
"task_shell_start",
"task_shell_wait",
"tasks",
"terminal/cancel",
@@ -169,14 +168,14 @@
"workflow",
"write"
],
"tools": 52
"tools": 51
}
},
"operate": {
"active": {
"bytes": 37873,
"bytes": 37703,
"identity_sha256": "e2d6dfb7168d48ee8d2a415feac7ff5b8864e18528c233a697055370144eb138",
"tokens_est": 9469,
"tokens_est": 9426,
"tool_names": [
"agent",
"bash",
@@ -196,9 +195,9 @@
"tools": 14
},
"full": {
"bytes": 85363,
"identity_sha256": "928e6417732837d26f1f26fe41ea6a11f5b8e3bfc4eeb3dd97a309379cffeff9",
"tokens_est": 21341,
"bytes": 84384,
"identity_sha256": "2a2ee97e7c482c625bc50c77db7f0e08d6bc67e2b4e73e7351385418700252bb",
"tokens_est": 21096,
"tool_names": [
"Git",
"Run",
@@ -235,7 +234,6 @@
"session_get",
"session_search",
"speech",
"task_shell_start",
"task_shell_wait",
"tasks",
"terminal/cancel",
@@ -253,14 +251,14 @@
"workflow",
"write"
],
"tools": 52
"tools": 51
}
},
"plan": {
"active": {
"bytes": 36343,
"bytes": 36173,
"identity_sha256": "6c432c75bf776d95b367b17308b171c3eaba8e70aa0c2f76bac453b3c3368a21",
"tokens_est": 9086,
"tokens_est": 9044,
"tool_names": [
"agent",
"bash",
@@ -279,9 +277,9 @@
"tools": 13
},
"full": {
"bytes": 55333,
"bytes": 55163,
"identity_sha256": "ac8af1f4988199825be7b00b054c258724a44074b1d4e6de6c92ade7c1cffe63",
"tokens_est": 13834,
"tokens_est": 13791,
"tool_names": [
"Git",
"Web",