Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
59c1ebe
feat(tui): add ambient pets
fcoury-oai May 3, 2026
18d8786
fix(tui): disable ambient pet images in tmux
fcoury-oai May 3, 2026
85cdf56
fix(tui): gate pets on terminal image support
fcoury-oai May 3, 2026
94d3498
fix(tui): allow disabling pets
fcoury-oai May 3, 2026
75c2bcd
feat(tui): port Codex App pets
fcoury-oai May 3, 2026
b5ca385
feat(tui): match app pet animations
fcoury-oai May 4, 2026
725cdcb
feat(tui): add pet anchor config
fcoury-oai May 4, 2026
82d5563
test(tui): cover pet anchor layout
fcoury-oai May 4, 2026
95a2737
feat(tui): preview pets while selecting
fcoury-oai May 4, 2026
f81aa78
fix(tui): wrap transcript before terminal pet
fcoury-oai May 4, 2026
ed69375
fix(tui): keep pet clear of composer text
fcoury-oai May 5, 2026
ea8affe
fix(tui): use kitty graphics for wezterm pets
fcoury-oai May 5, 2026
db17f13
fix(tui): use kitty graphics for iterm pets
fcoury-oai May 5, 2026
1c006dd
fix(tui): use local-file kitty graphics for iterm pets
fcoury-oai May 5, 2026
1efaa99
fix(tui): enable sixel pets in windows terminal
fcoury-oai May 5, 2026
02fd7d9
fix(tui): clear sixel pet area before redraw
fcoury-oai May 5, 2026
2b156b4
fix(tui): clear sixel pet column artifacts
fcoury-oai May 5, 2026
1703b40
fix(tui): clear stale sixel pet image
fcoury-oai May 5, 2026
2081eb3
fix(tui): keep pet visible with long previews
fcoury-oai May 5, 2026
60d93af
fix(tui): resolve pet rebase drift
fcoury-oai May 5, 2026
de1a8ef
fix(tui): make pet tests bazel-safe
fcoury-oai May 5, 2026
1b96228
fix(tui): keep pet-enabled test defaults
fcoury-oai May 5, 2026
d0c7075
fix(tui): download built-in pets from CDN
fcoury-oai May 10, 2026
dfcc2e7
fix(tui): hide ambient pet text overlay
fcoury-oai May 10, 2026
78574cb
docs(tui): clarify pet subsystem contracts
fcoury-oai May 10, 2026
8f0a7da
codex: fix CI failure on PR #21206
fcoury-oai May 10, 2026
0fe7f46
codex: update pet overlay snapshots (#21206)
fcoury-oai May 10, 2026
1ee0b2e
codex: fix ambient pet test setup (#21206)
fcoury-oai May 10, 2026
62ed9b0
refactor(tui): replace sixel dependency
fcoury-oai May 10, 2026
f707b9a
style(tui): trim pet descriptions
fcoury-oai May 10, 2026
7a8f5f9
test(tui): update pet picker snapshot
fcoury-oai May 10, 2026
998d17e
fix(tui): address pet review feedback
fcoury-oai May 11, 2026
0e49991
test(tui): trim snapshot noise
fcoury-oai May 11, 2026
0f4fe8b
test(tui): restore snapshot assertion metadata
fcoury-oai May 11, 2026
284787a
refactor(tui): trim unused pet rendering glue
fcoury-oai May 11, 2026
e40b9e6
Simplify pet animation fallback
fcoury-oai May 12, 2026
bd21aa0
Harden pet manifests and image render errors
fcoury-oai May 12, 2026
3fff83d
Fix pet cache and deferred loading
fcoury-oai May 12, 2026
a78d0b8
Trim redundant pet tests
fcoury-oai May 12, 2026
6f880e6
refactor(tui): simplify pet helpers
fcoury-oai May 12, 2026
dc6159c
refactor(tui): simplify pet helpers
fcoury-oai May 12, 2026
d5586ab
fix(tui): use transparent sixel pet backgrounds
fcoury-oai May 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions codex-rs/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 22 additions & 0 deletions codex-rs/config/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,16 @@ impl fmt::Display for NotificationCondition {
}
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, Default)]
#[serde(rename_all = "kebab-case")]
pub enum TuiPetAnchor {
/// Anchor the pet to the bottom of the current TUI composer viewport.
#[default]
Composer,
/// Anchor the pet to the physical bottom of the terminal screen.
ScreenBottom,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default, JsonSchema)]
#[schemars(deny_unknown_fields)]
pub struct TuiNotificationSettings {
Expand Down Expand Up @@ -695,6 +705,18 @@ pub struct Tui {
#[serde(default)]
pub theme: Option<String>,

/// Pet id to preselect in the terminal pet picker.
///
/// Custom pet ids resolve against CODEX_HOME/pets/<pet-id>/pet.json.
#[serde(default)]
pub pet: Option<String>,

/// Where the terminal pet should anchor vertically.
///
/// Defaults to `composer`, which follows the current TUI composer viewport.
#[serde(default)]
pub pet_anchor: TuiPetAnchor,

/// Preferred layout for resume/fork session picker results.
#[serde(default)]
pub session_picker_view: Option<SessionPickerViewMode>,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/core-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub use codex_config::types::SessionPickerViewMode;
pub use codex_config::types::ToolSuggestConfig;
pub use codex_config::types::TuiKeymap;
pub use codex_config::types::TuiNotificationSettings;
pub use codex_config::types::TuiPetAnchor;
pub use codex_config::types::UriBasedFileOpener;
pub use codex_core::CodexThread;
pub use codex_core::ForkSnapshot;
Expand Down
32 changes: 32 additions & 0 deletions codex-rs/core/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -2692,6 +2692,20 @@
"default": true,
"description": "Enable desktop notifications from the TUI. Defaults to `true`."
},
"pet": {
"default": null,
"description": "Pet id to preselect in the terminal pet picker.\n\nCustom pet ids resolve against CODEX_HOME/pets/<pet-id>/pet.json.",
"type": "string"
},
"pet_anchor": {
"allOf": [
{
"$ref": "#/definitions/TuiPetAnchor"
}
],
"default": "composer",
"description": "Where the terminal pet should anchor vertically.\n\nDefaults to `composer`, which follows the current TUI composer viewport."
},
"raw_output_mode": {
"default": false,
"description": "Start the TUI in raw scrollback mode for copy-friendly transcript output. Defaults to `false`.",
Expand Down Expand Up @@ -3436,6 +3450,24 @@
},
"type": "object"
},
"TuiPetAnchor": {
"oneOf": [
{
"description": "Anchor the pet to the bottom of the current TUI composer viewport.",
"enum": [
"composer"
],
"type": "string"
},
{
"description": "Anchor the pet to the physical bottom of the terminal screen.",
"enum": [
"screen-bottom"
],
"type": "string"
}
]
},
"TuiVimNormalKeymap": {
"additionalProperties": false,
"description": "Vim normal-mode keybindings for modal editing inside text areas.\n\nActions that use uppercase letters (like `A` for append-line-end) should be specified as `shift-a` in config; the runtime matcher handles cross-terminal shift-reporting differences automatically.",
Expand Down
76 changes: 76 additions & 0 deletions codex-rs/core/src/config/config_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ use codex_config::types::ToolSuggestDiscoverableType;
use codex_config::types::Tui;
use codex_config::types::TuiKeymap;
use codex_config::types::TuiNotificationSettings;
use codex_config::types::TuiPetAnchor;
use codex_config::types::WindowsSandboxModeToml;
use codex_config::types::WindowsToml;
use codex_core_plugins::PluginsManager;
Expand Down Expand Up @@ -566,6 +567,8 @@ fn config_toml_deserializes_model_availability_nux() {
status_line_use_colors: true,
terminal_title: None,
theme: None,
pet: None,
pet_anchor: TuiPetAnchor::Composer,
session_picker_view: None,
keymap: TuiKeymap::default(),
model_availability_nux: ModelAvailabilityNuxConfig {
Expand Down Expand Up @@ -2530,6 +2533,19 @@ session_picker_view = "dense"
);
}

#[test]
fn tui_pet_deserializes_from_toml() {
let cfg = r#"
[tui]
pet = "chefito"
"#;
let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");
assert_eq!(
parsed.tui.as_ref().and_then(|t| t.pet.as_deref()),
Some("chefito"),
);
}

#[test]
fn tui_session_picker_view_defaults_to_none() {
let cfg = r#"
Expand All @@ -2542,6 +2558,56 @@ fn tui_session_picker_view_defaults_to_none() {
);
}

#[test]
fn tui_pet_defaults_to_none() {
let cfg = r#"
[tui]
"#;
let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");
assert_eq!(parsed.tui.as_ref().and_then(|t| t.pet.as_deref()), None);
}

#[test]
fn tui_pet_anchor_deserializes_from_toml() {
let cfg = r#"
[tui]
pet_anchor = "screen-bottom"
"#;
let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");
assert_eq!(
parsed.tui.as_ref().map(|t| t.pet_anchor),
Some(TuiPetAnchor::ScreenBottom),
);
}

#[test]
fn tui_pet_anchor_defaults_to_composer() {
let cfg = r#"
[tui]
"#;
let parsed = toml::from_str::<ConfigToml>(cfg).expect("TOML deserialization should succeed");
assert_eq!(
parsed.tui.as_ref().map(|t| t.pet_anchor),
Some(TuiPetAnchor::Composer),
);
}

#[test]
fn tui_pet_anchor_rejects_unknown_value() {
let cfg = r#"
[tui]
pet_anchor = "bottom"
"#;
let err = toml::from_str::<ConfigToml>(cfg).expect_err("reject unknown pet anchor");
let err = err.to_string();
assert!(
err.contains("unknown variant `bottom`")
&& err.contains("composer")
&& err.contains("screen-bottom"),
"unexpected error: {err}"
);
}

#[test]
fn tui_config_missing_notifications_field_defaults_to_enabled() {
let cfg = r#"
Expand All @@ -2565,6 +2631,8 @@ fn tui_config_missing_notifications_field_defaults_to_enabled() {
status_line_use_colors: true,
terminal_title: None,
theme: None,
pet: None,
pet_anchor: TuiPetAnchor::Composer,
session_picker_view: None,
keymap: TuiKeymap::default(),
model_availability_nux: ModelAvailabilityNuxConfig::default(),
Expand Down Expand Up @@ -7322,6 +7390,8 @@ async fn test_precedence_fixture_with_o3_profile() -> std::io::Result<()> {
tui_status_line_use_colors: true,
tui_terminal_title: None,
tui_theme: None,
tui_pet: None,
tui_pet_anchor: TuiPetAnchor::Composer,
tui_session_picker_view: SessionPickerViewMode::Dense,
otel: OtelConfig::default(),
},
Expand Down Expand Up @@ -7766,6 +7836,8 @@ async fn test_precedence_fixture_with_gpt3_profile() -> std::io::Result<()> {
tui_status_line_use_colors: true,
tui_terminal_title: None,
tui_theme: None,
tui_pet: None,
tui_pet_anchor: TuiPetAnchor::Composer,
tui_session_picker_view: SessionPickerViewMode::Dense,
otel: OtelConfig::default(),
};
Expand Down Expand Up @@ -7924,6 +7996,8 @@ async fn test_precedence_fixture_with_zdr_profile() -> std::io::Result<()> {
tui_status_line_use_colors: true,
tui_terminal_title: None,
tui_theme: None,
tui_pet: None,
tui_pet_anchor: TuiPetAnchor::Composer,
tui_session_picker_view: SessionPickerViewMode::Dense,
otel: OtelConfig::default(),
};
Expand Down Expand Up @@ -8067,6 +8141,8 @@ async fn test_precedence_fixture_with_gpt5_profile() -> std::io::Result<()> {
tui_status_line_use_colors: true,
tui_terminal_title: None,
tui_theme: None,
tui_pet: None,
tui_pet_anchor: TuiPetAnchor::Composer,
tui_session_picker_view: SessionPickerViewMode::Dense,
otel: OtelConfig::default(),
};
Expand Down
8 changes: 8 additions & 0 deletions codex-rs/core/src/config/edit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ pub fn syntax_theme_edit(name: &str) -> ConfigEdit {
}
}

/// Produces a config edit that sets [tui].pet = "<name>".
pub fn tui_pet_edit(name: &str) -> ConfigEdit {
ConfigEdit::SetPath {
segments: vec!["tui".to_string(), "pet".to_string()],
value: value(name.to_string()),
}
}

/// Produces a config edit that sets `[tui].session_picker_view = "<mode>"`.
pub fn session_picker_view_edit(mode: SessionPickerViewMode) -> ConfigEdit {
ConfigEdit::SetPath {
Expand Down
13 changes: 13 additions & 0 deletions codex-rs/core/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ use codex_config::types::ToolSuggestDisabledTool;
use codex_config::types::ToolSuggestDiscoverable;
use codex_config::types::TuiKeymap;
use codex_config::types::TuiNotificationSettings;
use codex_config::types::TuiPetAnchor;
use codex_config::types::UriBasedFileOpener;
use codex_config::types::WindowsSandboxModeToml;
use codex_core_plugins::PluginsConfigInput;
Expand Down Expand Up @@ -549,6 +550,12 @@ pub struct Config {
/// Syntax highlighting theme override (kebab-case name).
pub tui_theme: Option<String>,

/// Pet id preselected by the terminal pet picker.
pub tui_pet: Option<String>,

/// Vertical anchor used by terminal pet rendering.
pub tui_pet_anchor: TuiPetAnchor,

/// Preferred layout for resume/fork session picker results.
pub tui_session_picker_view: SessionPickerViewMode,

Expand Down Expand Up @@ -3211,6 +3218,12 @@ impl Config {
.unwrap_or(true),
tui_terminal_title: cfg.tui.as_ref().and_then(|t| t.terminal_title.clone()),
tui_theme: cfg.tui.as_ref().and_then(|t| t.theme.clone()),
tui_pet: cfg.tui.as_ref().and_then(|t| t.pet.clone()),
tui_pet_anchor: cfg
.tui
.as_ref()
.map(|t| t.pet_anchor)
.unwrap_or_default(),
tui_session_picker_view: config_profile
.tui
.as_ref()
Expand Down
3 changes: 3 additions & 0 deletions codex-rs/thread-manager-sample/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use codex_core_api::ThreadStoreConfig;
use codex_core_api::ToolSuggestConfig;
use codex_core_api::TuiKeymap;
use codex_core_api::TuiNotificationSettings;
use codex_core_api::TuiPetAnchor;
use codex_core_api::UriBasedFileOpener;
use codex_core_api::UserInput;
use codex_core_api::WebSearchMode;
Expand Down Expand Up @@ -205,6 +206,8 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
tui_terminal_title: None,
tui_theme: None,
tui_raw_output_mode: false,
tui_pet: None,
tui_pet_anchor: TuiPetAnchor::Composer,
terminal_resize_reflow: TerminalResizeReflowConfig::default(),
tui_keymap: TuiKeymap::default(),
tui_session_picker_view: SessionPickerViewMode::Dense,
Expand Down
3 changes: 2 additions & 1 deletion codex-rs/tui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,11 @@ ratatui = { workspace = true, features = [
] }
ratatui-macros = { workspace = true }
regex-lite = { workspace = true }
reqwest = { workspace = true, features = ["json"] }
reqwest = { workspace = true, features = ["blocking", "json"] }
rmcp = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["preserve_order"] }
sha2 = { workspace = true }
shlex = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
Expand Down
Loading
Loading