c03: delete the four re-export shims and migrate their last consumers

`docs/design/TUI_DECONSTRUCTION.md:176,185-186` forbids landing temporary
re-export shims whose last consumers are deferred: "do not land temporary
re-export shims with their last consumers left for an unspecified future
migration. Keep shims only for genuine external compatibility contracts and
identify that contract."

Four survived the earlier C03 slices — `tui/app/types.rs`, `tui/file_mention.rs`,
`tui/agent_roster.rs`, `tui/approval.rs` — and none named such a contract. There
was nothing to protect: no crate outside `crates/tui` referenced the moved
symbols. So this migrates every remaining internal consumer to the owning crate
and removes all four in one commit, because landing three of four is precisely
the deferred-consumer state the rule exists to prevent.

`crates/tui/src/turn_route_plan.rs` now imports `codewhale_config::AppMode`
directly. `AppModeUi` stays in `tui::app` — it is genuinely UI-specific and not a
shim.

`docs/design/TUI_DECONSTRUCTION.md` is updated in the same commit so the
dependency list describes the tree as it now is rather than as it was.

Also closes the coverage gap this packet's own done-evidence depended on.
`cargo test -p codewhale-config --lib app_mode` previously reported
`0 passed; 0 failed` with 693 filtered out — a filter matching nothing, which
the repo's rules count as a gap and not a pass — while C03 claimed "unchanged
serialization behavior". `AppMode`'s new owner now carries focused tests for the
setting round-trip, parse/label/cycle order, and the fallback for unknown values.

cargo test -p codewhale-config --lib --locked app_mode
  test result: ok. 5 passed; 0 failed; 693 filtered out   (was 0 passed)
cargo test -p codewhale-tui --lib --locked -- approval agent_roster context_reference app_mode
  test result: ok. 298 passed; 0 failed; 12317 filtered out
cargo test -p codewhale-execpolicy -p codewhale-config --locked
  test result: ok. 697 passed; 0 failed; 1 ignored
  test result: ok. 112 passed; 0 failed
  (plus 10, 5, 1, 1 passed across the remaining targets)
cargo check --workspace --all-targets --locked: 0 errors, 0 unused warnings

The fallback test deserves a note, since its first draft asserted the opposite:
`from_setting` returns `Agent` for an empty or unrecognized value, which is the
long-standing shipped behavior and is unchanged here. Mode is not the permission
surface — as `AppMode::parse` documents, the bypass posture the legacy YOLO
spellings imply travels on the permission surface, not on a mode.

Refs: Linear C03 / SHA-6179, GitHub #5316.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01X57KRgwqYkmmtVQScczqiL
This commit is contained in:
CodeWhale BotandClaude Opus 5 committed 2026-09-09 20:46:04 -07:00
1 parent 7ad4e661cd
commit 15b140e1ca
36 files changed
+264 -174

No files matched your search

+99
View File
@@ -122,3 +122,102 @@ impl AppMode {
Self::CYCLE[(index + Self::CYCLE.len() - 1) % Self::CYCLE.len()]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn app_mode_helpers_centralize_parse_labels_and_cycle_order() {
assert_eq!(AppMode::parse("agent"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("act"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("work"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("2"), Some(AppMode::Plan));
assert_eq!(AppMode::parse("auto"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("3"), Some(AppMode::Operate));
assert_eq!(AppMode::parse("operate"), Some(AppMode::Operate));
// Legacy YOLO spellings resolve to Act; the bypass posture they imply
// travels on the permission surface, not on a mode.
assert_eq!(AppMode::parse("YOLO"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("4"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("bypass"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("bypass-permissions"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("multitask"), None);
assert_eq!(AppMode::parse("5"), None);
assert_eq!(AppMode::parse("fast"), None);
assert_eq!(AppMode::from_setting("multitask"), AppMode::Operate);
assert_eq!(AppMode::from_setting("5"), AppMode::Operate);
assert_eq!(AppMode::Agent.as_setting(), "agent");
assert_eq!(AppMode::Plan.display_name(), "Plan");
assert_eq!(AppMode::Agent.number(), '1');
assert_eq!(AppMode::Operate.number(), '3');
assert_eq!(
AppMode::CYCLE,
[AppMode::Plan, AppMode::Agent, AppMode::Operate]
);
assert_eq!(AppMode::Plan.next(), AppMode::Agent);
assert_eq!(AppMode::Agent.next(), AppMode::Operate);
assert_eq!(AppMode::Operate.next(), AppMode::Plan);
assert_eq!(AppMode::Plan.previous(), AppMode::Operate);
assert_eq!(AppMode::Agent.previous(), AppMode::Plan);
assert_eq!(AppMode::Operate.previous(), AppMode::Agent);
}
/// The durable form of a mode is the `as_setting()` string persisted into
/// settings and session records — `AppMode` derives no `Serialize`, so the
/// round-trip that has to hold is string -> mode -> string.
#[test]
fn setting_strings_round_trip_for_every_mode() {
for mode in AppMode::CYCLE {
let setting = mode.as_setting();
assert_eq!(
AppMode::from_setting(setting),
mode,
"from_setting({setting})"
);
assert_eq!(AppMode::parse(setting), Some(mode), "parse({setting})");
}
assert_eq!(AppMode::Agent.as_setting(), "agent");
assert_eq!(AppMode::Plan.as_setting(), "plan");
assert_eq!(AppMode::Operate.as_setting(), "operate");
}
/// `from_setting` is the de-facto default: an absent, empty, or unreadable
/// stored value must land on Act rather than panicking or picking Operate.
#[test]
fn from_setting_falls_back_to_act_for_unknown_values() {
assert_eq!(AppMode::from_setting(""), AppMode::Agent);
assert_eq!(AppMode::from_setting(" "), AppMode::Agent);
assert_eq!(AppMode::from_setting("nonsense"), AppMode::Agent);
assert_eq!(AppMode::from_setting("OPERATE"), AppMode::Operate);
}
#[test]
fn labels_and_descriptions_cover_every_mode() {
assert_eq!(AppMode::Agent.label(), "ACT");
assert_eq!(AppMode::Plan.label(), "PLAN");
assert_eq!(AppMode::Operate.label(), "OPERATE");
assert_eq!(AppMode::Agent.display_name(), "Act");
assert_eq!(AppMode::Plan.display_name(), "Plan");
assert_eq!(AppMode::Operate.display_name(), "Operate");
for mode in AppMode::CYCLE {
assert!(!mode.description().is_empty());
}
}
#[test]
fn operate_shares_the_agent_baseline_and_raises_the_launch_floor() {
assert!(AppMode::Agent.uses_agent_baseline());
assert!(AppMode::Operate.uses_agent_baseline());
assert!(!AppMode::Plan.uses_agent_baseline());
assert_eq!(AppMode::Operate.mode_delegation_launch_floor(), 4);
assert_eq!(AppMode::Agent.mode_delegation_launch_floor(), 1);
assert_eq!(AppMode::Plan.mode_delegation_launch_floor(), 1);
}
}
+68
View File
@@ -62,3 +62,71 @@ impl ApprovalMode {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_approval_mode_labels() {
assert_eq!(ApprovalMode::Auto.label(), "AUTO");
assert_eq!(ApprovalMode::Suggest.label(), "SUGGEST");
assert_eq!(ApprovalMode::Never.label(), "NEVER");
}
#[test]
fn test_approval_mode_from_config_value_accepts_aliases() {
assert_eq!(
ApprovalMode::from_config_value("auto"),
Some(ApprovalMode::Auto)
);
assert_eq!(
ApprovalMode::from_config_value("on-request"),
Some(ApprovalMode::Suggest)
);
assert_eq!(
ApprovalMode::from_config_value("full_access"),
Some(ApprovalMode::Bypass)
);
assert_eq!(
ApprovalMode::from_config_value("deny"),
Some(ApprovalMode::Never)
);
assert_eq!(ApprovalMode::from_config_value("unknown"), None);
}
#[test]
fn permission_cycle_is_a_closed_loop_and_never_is_off_cycle() {
assert_eq!(
ApprovalMode::PERMISSION_CYCLE,
[
ApprovalMode::Suggest,
ApprovalMode::Auto,
ApprovalMode::Bypass
]
);
assert_eq!(
ApprovalMode::Suggest.cycle_permission_next(),
ApprovalMode::Auto
);
assert_eq!(
ApprovalMode::Auto.cycle_permission_next(),
ApprovalMode::Bypass
);
assert_eq!(
ApprovalMode::Bypass.cycle_permission_next(),
ApprovalMode::Suggest
);
// Never is deliberately outside the Shift+Tab cycle; stepping from it
// must land on the safest posture rather than panicking or wrapping.
assert_eq!(
ApprovalMode::Never.cycle_permission_next(),
ApprovalMode::Suggest
);
}
#[test]
fn default_posture_is_suggest() {
assert_eq!(ApprovalMode::default(), ApprovalMode::Suggest);
}
}
@@ -19,8 +19,9 @@ use crate::config_persistence::{
use crate::localization::{MessageId, resolve_locale, tr};
use crate::reasoning_preference::ReasoningEffort;
use crate::settings::Settings;
use crate::tui::app::{App, AppAction, AppMode, OnboardingState, ScreenMode, SettingSelection, VimMode};
use crate::tui::app::{App, AppAction, OnboardingState, ScreenMode, SettingSelection, VimMode};
use anyhow::Result;
use codewhale_config::AppMode;
use codewhale_execpolicy::ApprovalMode;
use std::path::{Path, PathBuf};
@@ -526,8 +526,9 @@ mod tests {
use super::*;
use crate::config::{ApiProvider, Config};
use crate::models::{ContentBlock, Message};
use crate::tui::app::{AppMode, TuiOptions};
use crate::tui::app::TuiOptions;
use crate::tui::history::HistoryCell;
use codewhale_config::AppMode;
#[test]
fn status_keeps_current_session_snapshot_remedy_after_notice_delivery() {
+4 -2
View File
@@ -10,8 +10,9 @@ use crate::config::{
use crate::localization::{Locale, MessageId, tr};
#[cfg(test)]
use crate::reasoning_preference::ReasoningEffort;
use crate::tui::app::{App, AppAction, AppMode};
use crate::tui::app::{App, AppAction};
use crate::tui::views::{HelpView, ModalKind, SubAgentsView, subagent_view_agents};
use codewhale_config::AppMode;
use super::CommandResult;
@@ -754,8 +755,9 @@ mod tests {
use crate::config::Config;
use crate::models::Message;
use crate::models::Role;
use crate::tui::app::{App, AppMode, TuiOptions, TurnCacheRecord};
use crate::tui::app::{App, TuiOptions, TurnCacheRecord};
use crate::tui::history::HistoryCell;
use codewhale_config::AppMode;
use std::ffi::OsString;
use std::path::PathBuf;
use std::time::Instant;
@@ -13,7 +13,8 @@ use crate::commands::traits::{CommandInfo, RegisterCommand};
use crate::localization::MessageId;
use crate::models::ContentBlock;
use crate::tui::app::WORKFLOW_DRAFT_INSTRUCTION_PREFIX;
use crate::tui::app::{App, AppAction, AppMode};
use crate::tui::app::{App, AppAction};
use codewhale_config::AppMode;
#[cfg(test)]
use codewhale_execpolicy::ApprovalMode;
@@ -17,7 +17,8 @@ use crate::localization::{Locale, MessageId, tr};
use crate::models::{ContentBlock, Message, Role, SystemPrompt};
use crate::session_manager::{SessionManager, create_saved_session_with_mode};
use crate::test_support::{EnvVarGuard, TestEnvLock};
use crate::tui::app::{App, AppAction, AppMode, TuiOptions};
use crate::tui::app::{App, AppAction, TuiOptions};
use codewhale_config::AppMode;
/// Owns the global environment lock for as long as its CODEWHALE_HOME guard.
/// Fields are ordered so the guard restores the environment before the lock
@@ -14,8 +14,9 @@ use crate::models::Role;
use crate::reasoning_preference::ReasoningEffort;
use crate::session_manager::create_saved_session_with_id_and_mode;
use crate::test_support::EnvVarGuard;
use crate::tui::app::{App, AppAction, AppMode, TuiOptions, TurnCacheRecord};
use crate::tui::app::{App, AppAction, TuiOptions, TurnCacheRecord};
use crate::tui::history::HistoryCell;
use codewhale_config::AppMode;
fn dispatch_lifecycle(app: &mut App, name: &str, arg: Option<&str>) -> CommandResult {
let command = match arg {
@@ -548,9 +549,9 @@ fn test_load_valid_session_defers_state_restore_to_event_loop() {
app2.session_context_references
.push(crate::session_manager::SessionContextReference {
message_index: 0,
reference: crate::tui::file_mention::ContextReference {
kind: crate::tui::file_mention::ContextReferenceKind::File,
source: crate::tui::file_mention::ContextReferenceSource::AtMention,
reference: codewhale_core::ContextReference {
kind: codewhale_core::ContextReferenceKind::File,
source: codewhale_core::ContextReferenceSource::AtMention,
badge: "file".to_string(),
label: "stale.rs".to_string(),
target: tmpdir.path().join("stale.rs").display().to_string(),
+2 -4
View File
@@ -5,10 +5,8 @@
use std::collections::BTreeMap;
#[allow(unused_imports)]
pub use crate::agent_roster::{
AgentRosterRow, RosterState, all_rows_have_usage, build_agent_roster, display_name,
format_duration, format_tokens, one_line, roster_totals, wall_millis,
use crate::agent_roster::{
AgentRosterRow, all_rows_have_usage, format_duration, format_tokens, roster_totals,
};
/// Absent receipts render as `—`. See the truth rule in the module docs.
+1
View File
@@ -1,5 +1,6 @@
use super::*;
use crate::agent_roster::{RosterState, build_agent_roster};
use crate::tools::subagent::{
AgentRunUsage, AgentWorkerEvent, AgentWorkerRecord, AgentWorkerSpec, AgentWorkerStatus,
AgentWorkerToolProfile, FleetRole,
+6 -6
View File
@@ -11,7 +11,9 @@ use ratatui::layout::Rect;
use ratatui::style::Color;
use serde_json::Value;
use codewhale_config::{ProviderChain, route::RouteLimits};
use codewhale_config::{AppMode, ProviderChain, route::RouteLimits};
use codewhale_core::ContextReference;
use codewhale_execpolicy::ApprovalMode;
use crate::artifacts::ArtifactRecord;
use crate::client::{CacheWarmupKey, PromptInspection};
@@ -35,9 +37,7 @@ use crate::tools::spec::RuntimeToolServices;
use crate::tools::subagent::{AgentWorkerStatus, SubAgentResult};
use crate::tools::todo::{SharedTodoList, TodoList, new_shared_todo_list};
use crate::tui::active_cell::ActiveCell;
use crate::tui::approval::ApprovalMode;
use crate::tui::clipboard::{ClipboardContent, ClipboardHandler};
use crate::tui::file_mention::ContextReference;
use crate::tui::history::{HistoryCell, TranscriptActionOwner, TranscriptRenderOptions};
use crate::tui::hotbar::HotbarActionRegistry;
use crate::tui::motion::MotionPolicy;
@@ -69,7 +69,7 @@ pub(crate) enum RedactionGateNotice {
WriteFailure,
}
pub use types::{
AppAction, AppMode, AppModeUi, AutomationAction, ComposerDensity, ComposerSubmitAction,
AppAction, AppModeUi, AutomationAction, ComposerDensity, ComposerSubmitAction,
ComposerSubmitChord, InitialInput, McpUiAction, QueuedMessage, ScreenMode, SettingSelection,
ShellJobAction, SubmitDisposition, TaskPanelEntry, TaskPanelEntryKind, ToolCollapseMode,
ToolDetailRecord, TranscriptSpacing, TuiOptions, VimMode,
@@ -1912,7 +1912,7 @@ pub struct App {
/// Receipts-only roster of every agent that ran this session (#5479).
/// Refreshed wholesale on each `AgentList` event; shared by `/agents`,
/// the Agents register and the Price view.
pub agent_roster: Vec<crate::tui::agent_roster::AgentRosterRow>,
pub agent_roster: Vec<crate::agent_roster::AgentRosterRow>,
/// Original conversation owner of the retained snapshot. A process boot
/// marker or a worker's parent run is not a conversation identity.
pub agent_roster_session_id: Option<String>,
@@ -2560,7 +2560,7 @@ fn push_enabled_provider_model(
impl App {
/// A retained roster remains readable only in its owning conversation.
pub(crate) fn current_agent_roster(&self) -> &[crate::tui::agent_roster::AgentRosterRow] {
pub(crate) fn current_agent_roster(&self) -> &[crate::agent_roster::AgentRosterRow] {
if self
.current_session_id
.as_deref()
+2 -2
View File
@@ -510,7 +510,7 @@ impl ComposerState {
}
pub fn composer_attachment_count(&self) -> usize {
crate::tui::file_mention::media_attachment_references(&self.input).len()
codewhale_core::media_attachment_references(&self.input).len()
}
pub fn selected_composer_attachment_index(&self) -> Option<usize> {
@@ -809,7 +809,7 @@ impl App {
}
pub fn remove_selected_composer_attachment(&mut self) -> bool {
let references = crate::tui::file_mention::media_attachment_references(&self.input);
let references = codewhale_core::media_attachment_references(&self.input);
let Some(index) = self
.selected_composer_attachment_index()
.filter(|index| *index < references.len())
-38
View File
@@ -3469,44 +3469,6 @@ fn entering_operate_preserves_user_rail_panel() {
);
}
#[test]
fn app_mode_helpers_centralize_parse_labels_and_cycle_order() {
assert_eq!(AppMode::parse("agent"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("act"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("work"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("2"), Some(AppMode::Plan));
assert_eq!(AppMode::parse("auto"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("3"), Some(AppMode::Operate));
assert_eq!(AppMode::parse("operate"), Some(AppMode::Operate));
// Legacy YOLO spellings resolve to Act; the bypass posture they imply
// travels on the permission surface, not on a mode.
assert_eq!(AppMode::parse("YOLO"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("4"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("bypass"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("bypass-permissions"), Some(AppMode::Agent));
assert_eq!(AppMode::parse("multitask"), None);
assert_eq!(AppMode::parse("5"), None);
assert_eq!(AppMode::parse("fast"), None);
assert_eq!(AppMode::from_setting("multitask"), AppMode::Operate);
assert_eq!(AppMode::from_setting("5"), AppMode::Operate);
assert_eq!(AppMode::Agent.as_setting(), "agent");
assert_eq!(AppMode::Plan.display_name(), "Plan");
assert_eq!(AppMode::Agent.number(), '1');
assert_eq!(AppMode::Operate.number(), '3');
assert_eq!(
AppMode::CYCLE,
[AppMode::Plan, AppMode::Agent, AppMode::Operate]
);
assert_eq!(AppMode::Plan.next(), AppMode::Agent);
assert_eq!(AppMode::Agent.next(), AppMode::Operate);
assert_eq!(AppMode::Operate.next(), AppMode::Plan);
assert_eq!(AppMode::Plan.previous(), AppMode::Operate);
assert_eq!(AppMode::Agent.previous(), AppMode::Plan);
assert_eq!(AppMode::Operate.previous(), AppMode::Agent);
}
#[test]
fn test_cycle_scenario() {
// Scenario consolidation of: test_cycle_mode_transitions, test_cycle_mode_reverse_transitions
+5 -6
View File
@@ -3,11 +3,14 @@
//! and the action enums drained by the event loop.
//!
//! Everything here is pure data (plus parsing/labeling helpers that need no
//! `App` state). All items are re-exported from `app.rs` so existing
//! `crate::tui::app::X` paths are unchanged.
//! `App` state). The TUI-owned items are re-exported from `app.rs` so existing
//! `crate::tui::app::X` paths are unchanged; types owned by another crate
//! (such as [`AppMode`]) are named at their own crate path instead.
use super::*;
use codewhale_config::AppMode;
/// What an interactive setting selection actually did.
///
/// The three cases are genuinely different to the user, and the boolean this
@@ -44,10 +47,6 @@ impl SettingSelection {
}
}
/// The user-facing operating mode. Defined in codewhale-config; re-exported
/// here so `crate::tui::app::types::AppMode` keeps working.
pub use codewhale_config::AppMode;
/// Localized, TUI-only presentation of [`AppMode`]. Kept out of
/// codewhale-config so the mode type does not depend on the locale packs.
pub trait AppModeUi {
-5
View File
@@ -75,11 +75,6 @@ pub use policy::{
get_tool_category_for_call,
};
/// Determines when tool executions require user approval. Defined in
/// codewhale-execpolicy (next to `AskForApproval`); re-exported here so
/// `crate::tui::approval::ApprovalMode` keeps working.
pub use codewhale_execpolicy::ApprovalMode;
/// User's decision for a pending approval
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ReviewDecision {
-32
View File
@@ -2476,38 +2476,6 @@ fn workflow_plan_card_edit_plan_and_cancel_keys() {
}
}
// ========================================================================
// ApprovalMode Tests
// ========================================================================
#[test]
fn test_approval_mode_labels() {
assert_eq!(ApprovalMode::Auto.label(), "AUTO");
assert_eq!(ApprovalMode::Suggest.label(), "SUGGEST");
assert_eq!(ApprovalMode::Never.label(), "NEVER");
}
#[test]
fn test_approval_mode_from_config_value_accepts_aliases() {
assert_eq!(
ApprovalMode::from_config_value("auto"),
Some(ApprovalMode::Auto)
);
assert_eq!(
ApprovalMode::from_config_value("on-request"),
Some(ApprovalMode::Suggest)
);
assert_eq!(
ApprovalMode::from_config_value("full_access"),
Some(ApprovalMode::Bypass)
);
assert_eq!(
ApprovalMode::from_config_value("deny"),
Some(ApprovalMode::Never)
);
assert_eq!(ApprovalMode::from_config_value("unknown"), None);
}
#[test]
fn canonical_bash_keeps_original_name_but_uses_shell_approval_semantics() {
let request = ApprovalRequest::new_with_intent(
+2 -3
View File
@@ -6,9 +6,8 @@
#![allow(dead_code)]
use crate::tui::approval::{
ApprovalMode, RiskLevel, ToolCategory, classify_risk, get_tool_category_for_call,
};
use crate::tui::approval::{RiskLevel, ToolCategory, classify_risk, get_tool_category_for_call};
use codewhale_execpolicy::ApprovalMode;
use serde_json::{Value, json};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+2 -1
View File
@@ -9,7 +9,8 @@ use std::hash::{DefaultHasher, Hash, Hasher};
use crate::localization::{Locale, MessageId, tr};
use crate::settings::Settings;
use crate::tui::app::{App, AppMode, StatusToast, StatusToastKind, StatusToastLevel};
use crate::tui::app::{App, StatusToast, StatusToastKind, StatusToastLevel};
use codewhale_config::AppMode;
const MAX_TIPS_PER_SESSION: u8 = 1;
const MAX_LIFETIME_IMPRESSIONS: u8 = 2;
+2 -4
View File
@@ -23,12 +23,12 @@ use crate::models::{SystemPrompt, Tool};
use crate::palette;
use crate::session_manager::SessionContextReference;
use crate::tui::app::{App, ToolDetailRecord};
use crate::tui::file_mention::ContextReferenceSource;
use crate::tui::menu_style;
use crate::tui::views::{
ActionHint, ModalKind, ModalView, ViewAction, ViewEvent, render_modal_footer,
render_underwater_surface,
};
use codewhale_core::ContextReferenceSource;
/// Marker used by per-turn working-set metadata. Replicated here so the
/// context inspector can distinguish stable prompt blocks from volatile
@@ -1018,10 +1018,8 @@ mod tests {
use crate::models::{ContentBlock, Message, Tool};
use crate::session_manager::SessionContextReference;
use crate::tui::app::TuiOptions;
use crate::tui::file_mention::{
ContextReference, ContextReferenceKind, ContextReferenceSource,
};
use crate::tui::history::HistoryCell;
use codewhale_core::{ContextReference, ContextReferenceKind, ContextReferenceSource};
use std::path::PathBuf;
use crate::localization::Locale;
+4 -6
View File
@@ -29,6 +29,10 @@ use crate::tui::app::{App, MentionCompletionCache};
use crate::tui::git_mention::{self, GitMentionCache, GitMentionKind};
use crate::tui::mention_completion::{MentionDiscoveryBehavior, MentionDiscoveryKey};
use crate::working_set::Workspace;
use codewhale_core::{
ContextReference, ContextReferenceKind, ContextReferenceSource, MediaAttachmentReference,
media_attachment_references,
};
/// Maximum number of `@`-mentions whose contents are inlined into one user
/// message. Beyond this we stop appending blocks but the raw `@token` text
@@ -54,12 +58,6 @@ pub struct FileMentionPreview {
pub removable: bool,
}
/// Durable, compact metadata for a user-visible context reference.
pub use codewhale_core::{
ContextReference, ContextReferenceKind, ContextReferenceSource, MediaAttachmentReference,
media_attachment_references,
};
// ---------------------------------------------------------------------------
// Tab-completion
// ---------------------------------------------------------------------------
+1 -1
View File
@@ -20,9 +20,9 @@ use std::path::{Path, PathBuf};
use crate::tui::app::App;
use crate::tui::app::ToolDetailRecord;
use crate::tui::file_mention::{ContextReferenceKind, ContextReferenceSource};
use crate::tui::file_picker::FilePickerRelevance;
use crate::tui::file_picker::FilePickerView;
use codewhale_core::{ContextReferenceKind, ContextReferenceSource};
/// Push the `/files` picker onto the view stack, pre-populated with
/// per-session relevance ranks (modified, @-mentioned, tool-touched).
+2 -1
View File
@@ -3,12 +3,13 @@ use std::collections::{BTreeMap, BTreeSet, HashMap};
use std::sync::Arc;
use anyhow::Result;
use codewhale_config::AppMode;
use crate::commands::{self, CommandInfo, CommandResult};
use crate::config::{ApiProvider, Config};
use crate::localization::{Locale, MessageId, tr};
use crate::provider_lake::all_catalog_models_for_provider;
use crate::tui::app::{App, AppAction, AppMode};
use crate::tui::app::{App, AppAction};
use crate::tui::command_palette::{
CommandPaletteView, build_entries as build_command_palette_entries,
};
+1 -1
View File
@@ -115,7 +115,7 @@ use std::collections::VecDeque;
use std::sync::{Arc, Mutex, MutexGuard};
use crate::settings::Settings;
use crate::tui::app::AppMode;
use codewhale_config::AppMode;
/// One user selection's worth of startup-default writes.
///
+2 -2
View File
@@ -9,8 +9,7 @@ use crate::tools::subagent::{
};
use crate::tui::app::{
AgentCurrentActivity, AgentCurrentActivityStatus, AgentProgressMeta, AgentRecentAction, App,
AppMode, MAX_AGENT_RECENT_ACTIONS, TaskPanelEntry, TaskPanelEntryKind,
bound_agent_activity_text,
MAX_AGENT_RECENT_ACTIONS, TaskPanelEntry, TaskPanelEntryKind, bound_agent_activity_text,
};
use crate::tui::history::{HistoryCell, SubAgentCell, summarize_tool_output};
use crate::tui::pager::PagerView;
@@ -19,6 +18,7 @@ use crate::tui::widgets::agent_card::{
AgentLifecycle, DelegateCard, FanoutCard, apply_to_delegate, apply_to_fanout,
};
use crate::tui::workspace_context;
use codewhale_config::AppMode;
/// Keep settled cards visible briefly, then archive them from the compact
/// live projection. Their transcript card and persisted agent record remain
+5 -3
View File
@@ -15,6 +15,9 @@ use std::time::{Duration, Instant};
use crate::error_taxonomy::{ErrorCategory, ErrorEnvelope, ErrorSeverity};
use crate::resource_telemetry::estimate_output_tokens_from_text;
use anyhow::{Context, Result};
use codewhale_config::AppMode;
use codewhale_core::ContextReference;
use codewhale_execpolicy::ApprovalMode;
use codewhale_release::InstallMethod;
// On Windows the push/pop helpers write the escapes directly; crossterm's
// PushKeyboardEnhancementFlags / PopKeyboardEnhancementFlags commands are
@@ -88,7 +91,6 @@ use crate::tui::command_palette::{
use crate::tui::composer_ui::*;
use crate::tui::context_inspector::ContextInspectorView;
use crate::tui::event_broker::EventBroker;
use crate::tui::file_mention::ContextReference;
use crate::tui::file_picker_relevance;
use crate::tui::footer_ui::{friendly_subagent_progress, is_noisy_subagent_progress};
use crate::tui::format_helpers;
@@ -136,7 +138,7 @@ use crate::reasoning_preference::{EffectiveReasoningEffort, ReasoningEffort};
use super::key_actions;
use super::app::{
ActiveCompaction, ActiveTurnMetadata, AgentCurrentActivity, App, AppAction, AppMode,
ActiveCompaction, ActiveTurnMetadata, AgentCurrentActivity, App, AppAction,
ComposerSubmitAction, ComposerSubmitChord, GoalControlIntent, OnboardingState,
PendingGoalControl, PendingProviderSwitch, QueuedMessage, RedactionGateNotice, ScreenMode,
StatusToast, StatusToastLevel, SubmitDisposition, TaskPanelEntry, TaskPanelEntryKind,
@@ -144,7 +146,7 @@ use super::app::{
looks_like_slash_command_input, shell_command_from_bang_input,
};
use super::approval::{
ApprovalMode, ApprovalRequest, ApprovalView, ElevationRequest, ElevationView, ReviewDecision,
ApprovalRequest, ApprovalView, ElevationRequest, ElevationView, ReviewDecision,
};
use super::history::{
ExecCell, HistoryCell, ReasoningAction, ToolCell, ToolStatus, history_cells_from_message,
+1 -1
View File
@@ -622,7 +622,7 @@ pub(crate) fn apply_goal_snapshot_to_app(app: &mut App, snapshot: &GoalSnapshot)
// Operate set it from the prompt (or the model did while operating);
// the objective is the prompt the user just typed, so the receipt
// says what Operate will do with it instead of echoing it.
let content = if app.mode == crate::tui::app::AppMode::Operate {
let content = if app.mode == AppMode::Operate {
app.tr(crate::localization::MessageId::GoalReceiptSetOperate)
.into_owned()
} else {
+1 -1
View File
@@ -4,8 +4,8 @@ use crate::audit::log_sensitive_event;
use crate::core::engine::EngineHandle;
use crate::localization::MessageId;
use crate::tui::app::{App, StatusToastLevel};
use crate::tui::approval::ApprovalMode;
use crate::tui::history::HistoryCell;
use codewhale_execpolicy::ApprovalMode;
pub(super) fn is_session_approved_for_tool(
app: &App,
+13 -25
View File
@@ -8548,7 +8548,7 @@ async fn model_change_update_syncs_engine_model_before_compaction() {
#[tokio::test]
async fn mode_change_update_notifies_engine() {
let mut app = create_test_app();
let _ = app.set_mode(crate::tui::app::AppMode::Plan);
let _ = app.set_mode(AppMode::Plan);
let mut engine = crate::core::engine::mock_engine_handle();
assert!(
@@ -8566,11 +8566,11 @@ async fn mode_change_update_notifies_engine() {
} => {
// The deprecated YOLO alias lands in Agent mode with full-access
// compat policies (M6 shim); the engine sees the remapped mode.
assert_eq!(mode, crate::tui::app::AppMode::Agent);
assert_eq!(mode, AppMode::Agent);
assert!(allow_shell);
assert!(trust_mode);
assert!(auto_approve);
assert_eq!(approval_mode, crate::tui::approval::ApprovalMode::Bypass);
assert_eq!(approval_mode, ApprovalMode::Bypass);
assert_eq!(configured_sandbox_mode, app.configured_sandbox_mode);
}
other => panic!("expected ChangeMode, got {other:?}"),
@@ -8582,8 +8582,8 @@ async fn mode_change_update_sends_restored_agent_policy() {
let mut app = create_test_app();
app.allow_shell = true;
app.trust_mode = false;
app.approval_mode = crate::tui::approval::ApprovalMode::Never;
let _ = app.set_mode(crate::tui::app::AppMode::Plan);
app.approval_mode = ApprovalMode::Never;
let _ = app.set_mode(AppMode::Plan);
let mut engine = crate::core::engine::mock_engine_handle();
assert!(
@@ -8591,7 +8591,7 @@ async fn mode_change_update_sends_restored_agent_policy() {
&mut app,
&engine.handle,
&crate::config::Config::default(),
crate::tui::app::AppMode::Agent
AppMode::Agent
)
.await
);
@@ -8605,11 +8605,11 @@ async fn mode_change_update_sends_restored_agent_policy() {
approval_mode,
configured_sandbox_mode,
} => {
assert_eq!(mode, crate::tui::app::AppMode::Agent);
assert_eq!(mode, AppMode::Agent);
assert!(allow_shell);
assert!(!trust_mode);
assert!(!auto_approve);
assert_eq!(approval_mode, crate::tui::approval::ApprovalMode::Never);
assert_eq!(approval_mode, ApprovalMode::Never);
assert_eq!(configured_sandbox_mode, app.configured_sandbox_mode);
}
other => panic!("expected ChangeMode, got {other:?}"),
@@ -8667,13 +8667,7 @@ async fn operate_entry_preserves_live_custom_identity_and_auto_over_startup_rout
app.set_model_selection(selected.to_string());
app.last_effective_model = Some("previous-turn-model".to_string());
let engine = mock_engine_handle();
apply_mode_update(
&mut app,
&engine.handle,
&config,
crate::tui::app::AppMode::Operate,
)
.await;
apply_mode_update(&mut app, &engine.handle, &config, AppMode::Operate).await;
let record = automations
.lock()
.await
@@ -8743,13 +8737,7 @@ async fn operate_rejected_attach_does_not_reactivate_saved_keepalive() {
);
app.set_model_selection("startup-model".into());
let engine = mock_engine_handle();
apply_mode_update(
&mut app,
&engine.handle,
&config,
crate::tui::app::AppMode::Operate,
)
.await;
apply_mode_update(&mut app, &engine.handle, &config, AppMode::Operate).await;
let record = automations
.lock()
.await
@@ -8800,7 +8788,7 @@ async fn operate_mode_entry_attaches_to_recorded_operation() {
&mut app,
&engine.handle,
&Config::default(),
crate::tui::app::AppMode::Operate
AppMode::Operate
)
.await
);
@@ -20124,7 +20112,7 @@ fn auto_route_receipt_survives_session_snapshot_and_restore() {
#[test]
fn apply_loaded_session_restores_saved_mode() {
let mut app = create_test_app();
app.set_mode(crate::tui::app::AppMode::Agent);
app.set_mode(AppMode::Agent);
let mut session = saved_session_with_messages(vec![
text_message("user", "draft a plan"),
text_message("assistant", "plan response"),
@@ -20132,7 +20120,7 @@ fn apply_loaded_session_restores_saved_mode() {
session.metadata.mode = Some("plan".to_string());
apply_loaded_session(&mut app, &mut Config::default(), &session).expect("restore session");
assert_eq!(app.mode, crate::tui::app::AppMode::Plan);
assert_eq!(app.mode, AppMode::Plan);
assert!(!app.allow_shell);
assert!(!app.trust_mode);
}
+6 -4
View File
@@ -24,12 +24,13 @@ use crate::localization::{Locale, MessageId, tr};
use crate::palette::{ChromeInk, chrome_style};
use crate::tui::ui_text::{semantic_truncate, text_display_width};
use crate::tui::{
app::{App, AppMode, HeaderActionTarget, HeaderHitbox, OnboardingState},
approval::ApprovalMode,
app::{App, HeaderActionTarget, HeaderHitbox, OnboardingState},
footer_ui::format_token_count_compact,
ocean::COMPLETION_BREATH_MS,
views::ModalKind,
};
use codewhale_config::AppMode;
use codewhale_execpolicy::ApprovalMode;
/// Responsive density tier. It changes how much truth is shown, never the
/// underlying state grammar.
@@ -2380,9 +2381,10 @@ mod header_tests {
render_header_with_git_status,
};
use crate::palette::ChromeInk;
use crate::tui::app::{App, AppMode};
use crate::tui::approval::ApprovalMode;
use crate::tui::app::App;
use crate::tui::widgets::workflow_panel::{WorkflowPanel, WorkflowPanelLifecycle};
use codewhale_config::AppMode;
use codewhale_execpolicy::ApprovalMode;
use ratatui::{buffer::Buffer, layout::Rect};
fn app() -> App {
+1 -1
View File
@@ -885,7 +885,7 @@ pub enum ViewEvent {
},
/// Emitted by the `/mode` picker when the user chooses a mode.
ModeSelected {
mode: crate::tui::app::AppMode,
mode: codewhale_config::AppMode,
},
/// Emitted by the `/statusline` picker every time the user toggles an
/// item (live preview) and once more on Enter (final). The handler
+1 -1
View File
@@ -14,13 +14,13 @@ use unicode_width::UnicodeWidthStr;
use crate::localization::Locale;
use crate::palette;
use crate::tui::app::AppMode;
use crate::tui::app::AppModeUi;
use crate::tui::menu_style;
use crate::tui::views::{
ActionHint, ModalKind, ModalView, ViewAction, ViewEvent, centered_modal_area,
render_modal_footer, render_modal_surface,
};
use codewhale_config::AppMode;
// Operate is visible because the engine now enforces a coordinator/worker
// boundary while allowing ordinary conversation and asynchronous dispatch.
+7 -5
View File
@@ -20,16 +20,17 @@ use crate::localization::{Locale, MessageId, tr};
use crate::palette;
#[cfg(test)]
use crate::provider_lake::all_catalog_models_for_provider;
use crate::tui::app::{App, AppMode, ComposerDensity, ViewportState};
use crate::tui::app::{App, ComposerDensity, ViewportState};
use crate::tui::approval::{
ApprovalMode, ApprovalRequest, ApprovalView, ElevationOption, ElevationRequest, RiskLevel,
ToolCategory,
ApprovalRequest, ApprovalView, ElevationOption, ElevationRequest, RiskLevel, ToolCategory,
};
use crate::tui::history::{GenericToolCell, HistoryCell, ToolCell, ToolRun, ToolStatus};
use crate::tui::menu_style;
use crate::tui::scrolling::TranscriptLineMeta;
use crate::tui::ui_text::{grapheme_display_width, text_display_width};
use crate::tui::underwater::ShellPhase;
use codewhale_config::AppMode;
use codewhale_execpolicy::ApprovalMode;
use ratatui::{
buffer::Buffer,
layout::Rect,
@@ -4601,13 +4602,14 @@ mod tests {
use crate::palette;
use crate::tui::active_cell::ActiveCell;
use crate::tui::app::{
App, AppMode, ComposerDensity, QueuedMessage, TaskPanelEntry, TaskPanelEntryKind,
ToolCollapseMode, TranscriptSpacing, TuiOptions,
App, ComposerDensity, QueuedMessage, TaskPanelEntry, TaskPanelEntryKind, ToolCollapseMode,
TranscriptSpacing, TuiOptions,
};
use crate::tui::history::{
ExecCell, ExecSource, GenericToolCell, HistoryCell, ToolCell, ToolRun, ToolStatus,
};
use crate::tui::scrolling::{TranscriptLineMeta, TranscriptScroll};
use codewhale_config::AppMode;
use crossterm::event::{KeyModifiers, MouseButton, MouseEvent, MouseEventKind};
use ratatui::{
buffer::Buffer,
+7 -7
View File
@@ -717,7 +717,7 @@ fn agents_view_rows(app: &mut App) -> Vec<WorkRow> {
retained.push(agents.remove(index));
continue;
}
let parked = receipt.state == crate::tui::agent_roster::RosterState::Parked;
let parked = receipt.state == crate::agent_roster::RosterState::Parked;
let status = if parked {
current_activity_status_label(AgentCurrentActivityStatus::Parked, app.ui_locale)
} else {
@@ -2924,9 +2924,9 @@ mod tests {
fn retained_agent_receipt(
id: &str,
status: AgentWorkerStatus,
state: crate::tui::agent_roster::RosterState,
) -> crate::tui::agent_roster::AgentRosterRow {
crate::tui::agent_roster::AgentRosterRow {
state: crate::agent_roster::RosterState,
) -> crate::agent_roster::AgentRosterRow {
crate::agent_roster::AgentRosterRow {
worker_id: id.to_string(),
display_name: format!("retained {id}"),
model: "test-model".to_string(),
@@ -2945,7 +2945,7 @@ mod tests {
#[test]
fn agents_register_retains_expired_receipts_alongside_fresh_live_rows() {
use crate::tui::agent_roster::RosterState;
use crate::agent_roster::RosterState;
use crate::tui::subagent_routing::reconcile_subagent_activity_state_at;
use std::time::Duration;
@@ -3002,7 +3002,7 @@ mod tests {
#[test]
fn retained_agents_register_is_bound_to_the_exact_parent_session() {
use crate::tui::agent_roster::RosterState;
use crate::agent_roster::RosterState;
let mut app = test_app();
app.agent_roster_session_id = Some("original-session".to_string());
@@ -3039,7 +3039,7 @@ mod tests {
#[test]
fn retained_agents_register_distinguishes_parked_from_waiting_for_input() {
use crate::tui::agent_roster::RosterState;
use crate::agent_roster::RosterState;
let mut app = test_app();
app.current_session_id = Some("roster-owner".to_string());
+1 -1
View File
@@ -4,7 +4,7 @@
//! like a to-do or a sub-agent — a view is a subset of one row grammar, not
//! a second widget system.
use crate::tui::agent_roster::format_tokens;
use crate::agent_roster::format_tokens;
use crate::tui::app::{App, SidebarRowAction};
use super::model::{RailPanel, WorkRow, WorkRowId, WorkTone};
+1 -1
View File
@@ -23,7 +23,7 @@ use crate::reasoning_preference::ReasoningEffort;
use crate::route_runtime::{
ResolvedRuntimeRoute, resolve_runtime_route, resolve_runtime_route_for_identity,
};
use crate::tui::app::AppMode;
use codewhale_config::AppMode;
/// Everything the shared turn-route planner needs.
///
+4 -2
View File
@@ -29,8 +29,10 @@ The current dependencies explain the blockage:
- CLI imports TUI for runtime dispatch and route preferences.
- `core/engine.rs` imports approval policy, context thresholds, attachment
parsing, and roster construction from `tui/`.
- `core/events.rs` carries `tui::agent_roster::AgentRosterRow`.
- `session_manager.rs` persists `tui::file_mention::ContextReference`.
- `core/events.rs` carries the roster row and `session_manager.rs` persists
the durable context reference; both now name their owning crate
(`crate::agent_roster::AgentRosterRow`, `codewhale_core::ContextReference`)
rather than a `tui::` re-export.
- `tools/subagent` imports engine policy/catalog functions, and implements
its own repeated model-request/tool-result cycle in `run_subagent`.
- `crates/core` owns request construction and some runtime/session services;