Skip to content

Commit 7bc08e2

Browse files
authored
Merge pull request #5862 from Hmbown/fix/0912-ux-20260902
Codewhale 0.9.12: Fleet-only UX (workbar, startup, underwater default, provider, settings, logo, hover, roles, retro theme)
2 parents aad2624 + 0f90457 commit 7bc08e2

190 files changed

Lines changed: 14885 additions & 12980 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

config.example.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,15 @@ check_interval_hours = 24
126126
# ─────────────────────────────────────────────────────────────────────────────────
127127
# Hotbar slots (#2061 / #2064)
128128
# ─────────────────────────────────────────────────────────────────────────────────
129-
# Optional 1-8 sidebar hotbar bindings. Since #3807 a missing `hotbar` key
129+
# Optional 1-8 workbar hotbar bindings. Since #3807 a missing `hotbar` key
130130
# renders NO bar — the Hotbar is hidden until you configure [[hotbar]]
131131
# tables here (or via `/hotbar`). An explicit `hotbar = []` also disables it.
132132
#
133133
# Invalid slots are skipped with a warning, duplicate slots use the last entry,
134134
# and unknown actions are preserved so the UI can show a disabled entry.
135135
# Slash commands can be bound as slash.<name>, for example slash.workflow.
136136
# `/hotbar on` writes the default slots: slash.workflow, slash.goal, slash.auto,
137-
# then Plan/Work/Operate, palette, and sidebar. Commands that require arguments
137+
# then Plan/Work/Operate, palette, and workbar toggle. Commands that require arguments
138138
# pre-fill the composer instead of running incomplete.
139139
#
140140
# [[hotbar]]

crates/cli/src/lib.rs

Lines changed: 48 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ struct Cli {
8080
long,
8181
value_name = "PROVIDER",
8282
value_parser = parse_provider_identifier,
83-
help = "Provider selector; exec/pod also accept configured custom provider identifiers"
83+
help = "Provider selector; exec/fleet also accept configured custom provider identifiers"
8484
)]
8585
provider: Option<String>,
8686
#[arg(long)]
@@ -203,25 +203,17 @@ path used by stream-json wrappers.
203203
")]
204204
Exec(TuiPassthroughArgs),
205205
/// Manage durable Agent fleet runs.
206-
///
207-
/// `fleet` is the canonical spelling. `codewhale pod` remains accepted as
208-
/// a compatibility alias for the identical command: the durable ledger,
209-
/// receipts, config tables, and `--fleet` workflow flag keep the Fleet
210-
/// serialization name.
211206
#[command(
212207
name = "fleet",
213-
alias = "pod",
214208
after_help = "\
215209
Examples:
216210
codewhale fleet init
217211
codewhale fleet run tasks.json --max-workers 4
218212
codewhale fleet status
219213
220-
`codewhale pod` is a compatibility alias for this command and dispatches
221-
identically, as `/pod` does for the `/fleet` slash command. What keeps the
222-
Fleet name is everything that has to stay readable across versions: the
223-
durable ledger `.codewhale/fleet.jsonl`, saved rosters `fleets/<name>.toml`,
224-
the `[fleet]` and `[fleets.*]` config tables, and `workflow run --fleet`."
214+
The durable ledger `.codewhale/fleet.jsonl`, saved rosters `fleets/<name>.toml`,
215+
the `[fleet]` and `[fleets.*]` config tables, and `workflow run --fleet` keep
216+
the Fleet name across versions."
225217
)]
226218
Fleet(TuiPassthroughArgs),
227219
/// Internal model-free Workflow tool dispatcher used by Lane Runtime.
@@ -496,7 +488,7 @@ fn top_level_provider_override(
496488

497489
let expected = ProviderKind::names_hint();
498490
bail!(
499-
"invalid value '{provider}' for '--provider <PROVIDER>': expected one of {expected}; configured custom providers are accepted only by exec and pod"
491+
"invalid value '{provider}' for '--provider <PROVIDER>': expected one of {expected}; configured custom providers are accepted only by exec and fleet"
500492
)
501493
}
502494

@@ -517,8 +509,8 @@ fn prepare_raw_provider_tui_dispatch(
517509
reject_exec_global_flags(&args.args)?;
518510
tui_args("exec", args.clone())
519511
}
520-
Some(Commands::Fleet(args)) => tui_args("pod", args.clone()),
521-
_ => unreachable!("raw provider validation only permits Exec and Pod"),
512+
Some(Commands::Fleet(args)) => tui_args("fleet", args.clone()),
513+
_ => unreachable!("raw provider validation only permits Exec and Fleet"),
522514
};
523515

524516
// Dynamic provider config belongs to the TUI schema. Do not parse it
@@ -660,7 +652,7 @@ enum LaneCommand {
660652
/// Workflow name (e.g. `stopship`).
661653
#[arg(long)]
662654
workflow: Option<String>,
663-
/// Pod roster name (e.g. `stopship`); the flag keeps its compatibility spelling.
655+
/// Fleet roster name (e.g. `stopship`); the flag keeps its compatibility spelling.
664656
#[arg(long)]
665657
fleet: Option<String>,
666658
/// Issue id binding.
@@ -703,7 +695,7 @@ enum WorkflowCommand {
703695
Run {
704696
/// Workflow name or path. `stopship` maps to workflows/stopship.workflow.js.
705697
workflow: String,
706-
/// Named Pod roster (e.g. stopship). The flag keeps its compatibility
698+
/// Named Fleet roster (e.g. stopship). The flag keeps its compatibility
707699
/// spelling. Without one, roles resolve against the built-in roster
708700
/// and the session route.
709701
#[arg(long)]
@@ -1076,12 +1068,14 @@ fn run_workflow_command(
10761068
// loaded and validated before the run starts.
10771069
if let Some(name) = fleet.as_deref() {
10781070
let roots = named_fleet_search_roots(&workspace);
1079-
let loaded = codewhale_workflow::load_named_fleet(name, &roots)
1080-
.with_context(|| format!("load Pod `{name}` from {}", display_roots(&roots)))?;
1071+
let loaded =
1072+
codewhale_workflow::load_named_fleet(name, &roots).with_context(|| {
1073+
format!("load Fleet `{name}` from {}", display_roots(&roots))
1074+
})?;
10811075
if workflow == "stopship" || name == "stopship" {
10821076
loaded
10831077
.validate_stopship_roles()
1084-
.with_context(|| format!("validate stopship roles in Pod `{name}`"))?;
1078+
.with_context(|| format!("validate stopship roles in Fleet `{name}`"))?;
10851079
}
10861080
}
10871081

@@ -1957,7 +1951,7 @@ fn run() -> Result<()> {
19571951
}
19581952
Some(Commands::Fleet(args)) => {
19591953
let resolved_runtime = resolve_runtime_for_dispatch(&mut store, &runtime_overrides);
1960-
run_tui_in_process(&cli, &resolved_runtime, tui_args("pod", args))
1954+
run_tui_in_process(&cli, &resolved_runtime, tui_args("fleet", args))
19611955
}
19621956
Some(Commands::WorkflowTool(args)) => {
19631957
let resolved_runtime = resolve_runtime_for_dispatch(&mut store, &runtime_overrides);
@@ -6516,46 +6510,50 @@ verbosity = "project-imported"
65166510
));
65176511
}
65186512

6519-
/// Fleet is the canonical customer-facing top-level command; `pod` is a
6520-
/// compatibility alias that must keep dispatching to the same code path.
6521-
/// The Fleet spelling was always the one in the durable ledger, saved
6522-
/// roster files, config tables, and the `workflow --fleet` flag.
6513+
/// Fleet is the only top-level spelling for durable runs. The retired
6514+
/// `pod` spelling must fail to parse instead of dispatching.
65236515
#[test]
6524-
fn fleet_is_the_canonical_top_level_command_and_pod_stays_a_compatibility_alias() {
6516+
fn fleet_is_the_only_top_level_command_and_pod_is_rejected() {
65256517
for tail in [
65266518
vec!["init"],
65276519
vec!["status"],
65286520
vec!["run", "tasks.json", "--max-workers", "2"],
65296521
] {
6530-
let pod = parse_ok(
6522+
let fleet = parse_ok(
65316523
&std::iter::once("codewhale")
6532-
.chain(["pod"])
6524+
.chain(["fleet"])
65336525
.chain(tail.iter().copied())
65346526
.collect::<Vec<_>>(),
65356527
);
6536-
let fleet = parse_ok(
6528+
let Some(Commands::Fleet(fleet_args)) = &fleet.command else {
6529+
panic!("fleet must parse into the fleet command: {tail:?}");
6530+
};
6531+
assert_eq!(fleet_args.args, tail, "{tail:?}");
6532+
assert!(fleet.prompt.is_empty(), "{tail:?}");
6533+
6534+
let retired = parse_ok(
65376535
&std::iter::once("codewhale")
6538-
.chain(["fleet"])
6536+
.chain(["pod"])
65396537
.chain(tail.iter().copied())
65406538
.collect::<Vec<_>>(),
65416539
);
6542-
let (Some(Commands::Fleet(pod_args)), Some(Commands::Fleet(fleet_args))) =
6543-
(&pod.command, &fleet.command)
6544-
else {
6545-
panic!("both spellings must parse into the same command: {tail:?}");
6546-
};
6547-
assert_eq!(pod_args.args, tail, "{tail:?}");
6548-
assert_eq!(pod_args.args, fleet_args.args, "{tail:?}");
6549-
assert!(pod.prompt.is_empty() && fleet.prompt.is_empty(), "{tail:?}");
6540+
assert!(
6541+
retired.command.is_none(),
6542+
"retired pod must not dispatch to any command: {tail:?}"
6543+
);
6544+
assert_eq!(
6545+
retired.prompt.first().map(String::as_str),
6546+
Some("pod"),
6547+
"retired pod words fall through to prompt text: {tail:?}"
6548+
);
65506549
}
65516550

6552-
// Help advertises fleet. The alias still resolves, but discovery has one
6553-
// canonical answer, so `pod` must not be listed as its own command.
6551+
// Help advertises fleet only.
65546552
let help = help_for(&["codewhale", "--help"]);
65556553
let commands = help
65566554
.lines()
65576555
.map(str::trim_start)
6558-
.filter(|line| line.starts_with("pod") || line.starts_with("fleet"))
6556+
.filter(|line| line.starts_with("fleet"))
65596557
.collect::<Vec<_>>();
65606558
assert_eq!(
65616559
commands.len(),
@@ -6568,29 +6566,28 @@ verbosity = "project-imported"
65686566
"help summary should name fleet: {commands:?}"
65696567
);
65706568
assert!(
6571-
!help.contains("Manage durable Agent Pod runs"),
6572-
"the retired Pod-led summary must be gone from top-level help"
6569+
!help.contains("Manage durable Agent Fleet runs"),
6570+
"the retired Fleet-led summary must be gone from top-level help"
65736571
);
65746572

65756573
let fleet_help = help_for(&["codewhale", "fleet", "--help"]);
65766574
assert!(fleet_help.contains("Manage durable Agent fleet runs"));
65776575
assert!(fleet_help.contains("codewhale fleet run tasks.json --max-workers 4"));
6578-
assert!(fleet_help.contains("codewhale pod` is a compatibility alias"));
65796576

6580-
// Both spellings normalize to the canonical inner command so receipts
6581-
// and any echoed invocation never regress to the compatibility name.
6577+
// The inner command token matches the canonical name so receipts
6578+
// and any echoed invocation never regress to the retired name.
65826579
let args = TuiPassthroughArgs {
65836580
args: vec!["status".into()],
65846581
};
65856582
assert_eq!(
6586-
tui_args("pod", args.clone()),
6587-
vec!["pod".to_string(), "status".to_string()]
6583+
tui_args("fleet", args.clone()),
6584+
vec!["fleet".to_string(), "status".to_string()]
65886585
);
65896586
assert!(command_accepts_raw_provider(Some(&Commands::Fleet(args))));
65906587
}
65916588

65926589
#[test]
6593-
fn exec_and_pod_accept_builtin_and_raw_provider_identifiers() {
6590+
fn exec_and_fleet_accept_builtin_and_raw_provider_identifiers() {
65946591
let builtin = parse_ok(&["codewhale", "--provider", "openrouter", "exec", "Reply OK"]);
65956592
assert_eq!(builtin.provider.as_deref(), Some("openrouter"));
65966593
assert_eq!(
@@ -6612,7 +6609,6 @@ verbosity = "project-imported"
66126609

66136610
for (provider, command) in [
66146611
("lm-studio", vec!["exec", "Reply OK"]),
6615-
("lm-studio", vec!["pod", "status"]),
66166612
("lm-studio", vec!["fleet", "status"]),
66176613
] {
66186614
let argv = std::iter::once("codewhale")
@@ -6696,13 +6692,13 @@ verbosity = "project-imported"
66966692
}
66976693

66986694
#[test]
6699-
fn raw_provider_ids_remain_restricted_to_exec_and_pod() {
6695+
fn raw_provider_ids_remain_restricted_to_exec_and_fleet() {
67006696
let cli = parse_ok(&["codewhale", "--provider", "lm-studio", "model", "list"]);
67016697
let err = top_level_provider_override(cli.provider.as_deref(), cli.command.as_ref())
67026698
.expect_err("model registry commands still require a built-in provider");
67036699
assert!(
67046700
err.to_string()
6705-
.contains("configured custom providers are accepted only by exec and pod")
6701+
.contains("configured custom providers are accepted only by exec and fleet")
67066702
);
67076703

67086704
let err = Cli::try_parse_from(["codewhale", "auth", "set", "--provider", "lm-studio"])

crates/config/src/app_mode.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ pub enum AppMode {
1414
impl AppMode {
1515
/// Productive keyboard cycle: Plan -> Act -> Operate -> Plan.
1616
///
17-
/// Operate joins the visible cycle as the always-on pod operation:
17+
/// Operate joins the visible cycle as the always-on fleet operation:
1818
/// a lead plans slices, then workers execute against an optional burn rate.
1919
pub const CYCLE: [Self; 3] = [Self::Plan, Self::Agent, Self::Operate];
2020

@@ -101,7 +101,7 @@ impl AppMode {
101101
AppMode::Agent => "Act mode - direct work in the current session with tools",
102102
AppMode::Plan => "Plan mode - research and design before implementing",
103103
AppMode::Operate => {
104-
"Operate mode - always-on pod operation: lead plans, optional $/time burn rate, workers follow the plan"
104+
"Operate mode - always-on fleet operation: lead plans, optional $/time burn rate, workers follow the plan"
105105
}
106106
}
107107
}

crates/config/src/lib.rs

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1324,20 +1324,6 @@ pub const DEFAULT_HOTBAR_ACTIONS: [&str; HOTBAR_SLOT_COUNT as usize] = [
13241324
"sidebar.toggle",
13251325
];
13261326

1327-
/// Normalize persisted action ids at the compatibility boundary.
1328-
///
1329-
/// `/pod` is the canonical public command, but existing settings may still
1330-
/// contain the former `slash.pod` hotbar id. Resolution and direct registry
1331-
/// lookup both use this helper so those slots continue to dispatch while any
1332-
/// subsequent save naturally writes the canonical id.
1333-
#[must_use]
1334-
pub fn normalize_hotbar_action_id(action_id: &str) -> &str {
1335-
match action_id {
1336-
"slash.pod" => "slash.fleet",
1337-
other => other,
1338-
}
1339-
}
1340-
13411327
/// On-disk schema for one `[[hotbar]]` table.
13421328
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13431329
#[serde(deny_unknown_fields)]
@@ -1447,7 +1433,7 @@ pub fn resolve_hotbar_bindings(
14471433
.iter()
14481434
.map(|binding| HotbarBinding {
14491435
slot: binding.slot,
1450-
action: normalize_hotbar_action_id(&binding.action).to_string(),
1436+
action: binding.action.clone(),
14511437
label: binding.label.clone(),
14521438
})
14531439
.collect::<Vec<_>>(),

0 commit comments

Comments
 (0)