Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 18 additions & 0 deletions codex-rs/tui/src/app/event_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,24 @@ impl App {
self.manage_thread_workflow(app_server, thread_id, action)
.await;
}
AppEvent::OpenThreadWorkflowManager { thread_id } => {
self.open_thread_workflow_manager(app_server, thread_id)
.await;
}
AppEvent::OpenThreadWorkflowDraftPrompt => {
self.chat_widget.show_thread_workflow_draft_prompt();
}
AppEvent::OpenThreadWorkflowActions {
thread_id,
workflow,
} => {
self.chat_widget
.show_thread_workflow_actions(thread_id, workflow);
}
AppEvent::OpenThreadWorkflowRunActions { thread_id, run } => {
self.chat_widget
.show_thread_workflow_run_actions(thread_id, run);
}
AppEvent::OpenThreadGoalPlanDetail { thread_id, plan } => {
self.chat_widget.show_goal_plan_detail(thread_id, plan);
}
Expand Down
34 changes: 34 additions & 0 deletions codex-rs/tui/src/app/thread_workflow_actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,40 @@ use crate::app_server_session::AppServerSession;
use codex_protocol::ThreadId;

impl App {
pub(super) async fn open_thread_workflow_manager(
&mut self,
app_server: &mut AppServerSession,
thread_id: ThreadId,
) {
if self.current_displayed_thread_id() != Some(thread_id) {
return;
}
self.chat_widget
.show_thread_workflow_manager_loading(thread_id);

let workflow_response = app_server.thread_workflow_list(thread_id).await;
let run_response = app_server.thread_workflow_run_list(thread_id).await;
if self.current_displayed_thread_id() != Some(thread_id) {
return;
}

match (workflow_response, run_response) {
(Ok(workflows), Ok(runs)) => self
.chat_widget
.show_thread_workflow_manager(thread_id, workflows, runs),
(Err(err), _) => self.chat_widget.show_thread_workflow_manager_error(
thread_id,
"read workflow specs",
&err,
),
(_, Err(err)) => self.chat_widget.show_thread_workflow_manager_error(
thread_id,
"list workflow runs",
&err,
),
}
}

pub(super) async fn manage_thread_workflow(
&mut self,
app_server: &mut AppServerSession,
Expand Down
22 changes: 22 additions & 0 deletions codex-rs/tui/src/app_event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ use codex_app_server_protocol::ThreadPendingInteractionTerminalStatus;
use codex_app_server_protocol::ThreadQueuedMessageMoveDirection;
use codex_app_server_protocol::ThreadSchedulePromptSource;
use codex_app_server_protocol::ThreadScheduleSpec;
use codex_app_server_protocol::ThreadWorkflow;
use codex_app_server_protocol::ThreadWorkflowRun;
use codex_app_server_protocol::WebhookEventStatus;
use codex_file_search::FileMatch;
use codex_protocol::ThreadId;
Expand Down Expand Up @@ -403,6 +405,26 @@ pub(crate) enum AppEvent {
action: ThreadWorkflowAction,
},

/// Open the interactive thread workflow manager.
OpenThreadWorkflowManager {
thread_id: ThreadId,
},

/// Open a prompt that prepares a workflow YAML draft request.
OpenThreadWorkflowDraftPrompt,

/// Open actions for a saved workflow spec.
OpenThreadWorkflowActions {
thread_id: ThreadId,
workflow: ThreadWorkflow,
},

/// Open actions for a workflow run.
OpenThreadWorkflowRunActions {
thread_id: ThreadId,
run: ThreadWorkflowRun,
},

/// Open details for one durable thread goal plan.
OpenThreadGoalPlanDetail {
thread_id: ThreadId,
Expand Down
1 change: 1 addition & 0 deletions codex-rs/tui/src/chatwidget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,7 @@ mod mission_control_menu;
mod monitor_display;
mod webhook_display;
mod workflow_display;
mod workflow_manager;
mod workflow_slash;
mod worktree_display;
use self::ide_context::IdeContextState;
Expand Down
8 changes: 3 additions & 5 deletions codex-rs/tui/src/chatwidget/slash_dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1154,11 +1154,9 @@ impl ChatWidget {
return;
}
if let Some(thread_id) = self.thread_id {
self.app_event_tx.send(AppEvent::ManageThreadWorkflow {
thread_id,
action: ThreadWorkflowAction::List,
});
self.append_message_history_entry("/workflow".to_string());
self.app_event_tx
.send(AppEvent::OpenThreadWorkflowManager { thread_id });
self.append_message_history_entry(format!("/{}", cmd.command()));
} else {
self.add_info_message(
WORKFLOW_USAGE.to_string(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
---
source: tui/src/chatwidget/tests/slash_commands.rs
assertion_line: 3505
expression: preview
---
• # Codewith Changelog

## [Unreleased]

### Fixed
## [0.1.57] - 2026-07-05

- Recovered loop-driven and long-running turns from context-window overflow
errors by compacting mid-turn and retrying once before surfacing the
failure.

## [0.1.54] - 2026-07-02
Tag: rust-v0.1.57
npm: https://www.npmjs.com/package/@hasna/codewith/v/0.1.57
(https://www.npmjs.com/package/@hasna/codewith/v/0.1.57)
Compare: https://github.com/hasna/codewith/compare/rust-v0.1.56...rust-v0.1.57
(https://github.com/hasna/codewith/compare/rust-v0.1.56...rust-v0.1.57)
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: tui/src/chatwidget/tests/workflow_manager.rs
expression: "render_bottom_popup(&chat, 120)"
---
Specs (1) [Runs (1)] Approvals

Search workflows
› spec_workflow-alpha · waiting ·… 2 agent steps active, 1 steps waiting for verifiers, 9 monitor/activity events · 4
verifiers · 9 events · workflow workflow-alp

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
source: tui/src/chatwidget/tests/workflow_manager.rs
expression: "render_bottom_popup(&chat, 110)"
---
Run run-complete
9 monitor/activity events · 4 verifiers · 9 events · workflow workflow-alp

› 1. Inspect run Show step, verifier, and event progress
Stop (disabled) Request cancellation for this workflow run (disabled: Completed workflow
runs cannot be stopped)
Review approva… (disabled) Workflow-specific approval review is not available yet (disabled: Approval
review uses the existing global approval popups)
2. Back to workflows Return to specs and runs

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: tui/src/chatwidget/tests/workflow_manager.rs
expression: "render_bottom_popup(&chat, 110)"
---
[Specs (0)] Runs (0) Approvals

Search workflows
› Create from prompt Draft workflow YAML from a natural-language request
No saved workflow specs Create one from a prompt or use /workflow draft <request>

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
source: tui/src/chatwidget/tests/workflow_manager.rs
expression: "render_bottom_popup(&chat, 105)"
---
Workflows
Failed to read workflow specs

› 1. Retry Refresh workflow specs and runs
Workflow manager unavai… ephemeral thread does not support workflows: thread-1

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
source: tui/src/chatwidget/tests/workflow_manager.rs
expression: "render_bottom_popup(&chat, 100)"
---
Workflows
Loading workflow specs and runs

› 1. Loading workflows Fetching saved specs and recent runs for this thread

Press enter to confirm or esc to go back
1 change: 1 addition & 0 deletions codex-rs/tui/src/chatwidget/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ mod status_command_tests;
mod status_surface_previews;
mod terminal_title;
mod webhook;
mod workflow_manager;
mod worktree_display;

pub(crate) use helpers::make_chatwidget_manual_with_sender;
Expand Down
21 changes: 21 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/slash_commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2347,6 +2347,27 @@ async fn workflow_list_slash_command_emits_metadata_event() {
assert_eq!(recall_latest_after_clearing(&mut chat), command);
}

#[tokio::test]
async fn workflows_alias_opens_interactive_manager() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.set_feature_enabled(Feature::Workflows, /*enabled*/ true);
let thread_id = ThreadId::new();
chat.thread_id = Some(thread_id);

submit_composer_text(&mut chat, "/workflows");

let event = rx.try_recv().expect("expected workflow manager event");
let AppEvent::OpenThreadWorkflowManager {
thread_id: actual_thread_id,
} = event
else {
panic!("expected OpenThreadWorkflowManager, got {event:?}");
};
assert_eq!(actual_thread_id, thread_id);
assert_no_submit_op(&mut op_rx);
assert_eq!(recall_latest_after_clearing(&mut chat), "/workflows");
}

#[tokio::test]
async fn workflow_run_slash_commands_emit_management_events() {
let cases = [
Expand Down
13 changes: 13 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/status_surface_previews.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,13 @@ fn cache_project_root(chat: &mut ChatWidget, root_name: &str) {
});
}

fn cache_missing_project_root(chat: &mut ChatWidget) {
chat.status_line_project_root_name_cache = Some(CachedProjectRootName {
cwd: chat.config.cwd.to_path_buf(),
root_name: None,
});
}

fn cache_rate_limit_snapshot(chat: &mut ChatWidget) {
chat.on_rate_limit_snapshot(Some(RateLimitSnapshot {
limit_id: None,
Expand Down Expand Up @@ -129,6 +136,7 @@ async fn status_line_setup_popup_live_only_snapshot() {
#[tokio::test]
async fn status_surface_preview_lines_hardcoded_only_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);

let snapshot = combined_preview_snapshot(
&mut chat,
Expand Down Expand Up @@ -168,6 +176,7 @@ async fn thread_title_falls_back_to_thread_id_when_unnamed() {
#[tokio::test]
async fn status_line_setup_popup_hardcoded_only_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.config.tui_status_line = Some(vec![
"project-name".to_string(),
"git-branch".to_string(),
Expand All @@ -183,6 +192,7 @@ async fn status_line_setup_popup_hardcoded_only_snapshot() {
#[tokio::test]
async fn status_surface_preview_lines_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.status_line_branch = Some("feature/mixed-preview".to_string());
chat.thread_name = Some("Mixed preview thread".to_string());

Expand Down Expand Up @@ -280,6 +290,7 @@ async fn status_line_setup_popup_rate_limits_snapshot() {
#[tokio::test]
async fn status_line_setup_popup_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.status_line_branch = Some("feature/mixed-preview".to_string());
chat.thread_name = Some("Mixed preview thread".to_string());
chat.config.tui_status_line = Some(vec![
Expand Down Expand Up @@ -332,6 +343,7 @@ async fn terminal_title_setup_popup_hardcoded_only_snapshot() {
#[tokio::test]
async fn terminal_title_setup_popup_mixed_snapshot() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.thread_name = Some("Mixed preview thread".to_string());
chat.config.tui_terminal_title = Some(vec![
"project-name".to_string(),
Expand Down Expand Up @@ -363,6 +375,7 @@ async fn terminal_title_setup_popup_rate_limits_snapshot() {
#[tokio::test]
async fn missing_project_root_uses_different_status_and_title_preview_sources() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);

let status_preview = status_preview_line(&mut chat, &[StatusLineItem::ProjectRoot]);
let title_preview = title_preview_line(&mut chat, &[TerminalTitleItem::Project]);
Expand Down
11 changes: 11 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/terminal_title.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,17 @@
use super::*;
use pretty_assertions::assert_eq;

fn cache_missing_project_root(chat: &mut ChatWidget) {
chat.status_line_project_root_name_cache = Some(CachedProjectRootName {
cwd: chat.config.cwd.to_path_buf(),
root_name: None,
});
}

#[tokio::test]
async fn terminal_title_shows_action_required_while_exec_approval_is_pending() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.bottom_pane.set_task_running(/*running*/ true);
chat.refresh_terminal_title();

Expand Down Expand Up @@ -47,6 +55,7 @@ async fn terminal_title_shows_action_required_while_exec_approval_is_pending() {
#[tokio::test]
async fn terminal_title_action_required_respects_spinner_setting() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.config.tui_terminal_title = Some(vec!["project".to_string()]);
chat.bottom_pane.set_task_running(/*running*/ true);
chat.refresh_terminal_title();
Expand Down Expand Up @@ -75,6 +84,7 @@ async fn terminal_title_action_required_respects_spinner_setting() {
#[tokio::test]
async fn terminal_title_action_required_blinks_when_animations_are_enabled() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.bottom_pane.set_task_running(/*running*/ true);
chat.terminal_title_animation_origin = Instant::now() - std::time::Duration::from_millis(1500);
chat.refresh_terminal_title();
Expand Down Expand Up @@ -106,6 +116,7 @@ async fn terminal_title_action_required_blinks_when_animations_are_enabled() {
#[tokio::test]
async fn terminal_title_activity_indicators_do_not_animate_when_animations_are_disabled() {
let (mut chat, _rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
cache_missing_project_root(&mut chat);
chat.config.animations = false;
chat.bottom_pane.set_task_running(/*running*/ true);
chat.terminal_title_animation_origin = Instant::now() - std::time::Duration::from_millis(1500);
Expand Down
Loading
Loading