mirror of
https://github.com/hanzoai/dev.git
synced 2026-10-25 03:32:32 +00:00
Merge upstream/main with -X ours; adapt to protocol and TUI changes
- core: replace StreamError with Error events; wrap EnvironmentContext args; fix let chains - exec: drop StreamError handling (use Error) - mcp-server: stub unsupported auth methods; remove StreamError match - tui: wire transcript overlay types; add missing imports; initialize new fields - composer: add Command variant to InputResult - chatwidget: remove legacy dispatch_command; handle Command result - lib: expose transcript_app module - transcript_app: update to current TUI API (draw, size)
This commit is contained in:
@@ -310,7 +310,6 @@ use crate::protocol::PatchApplyEndEvent;
|
||||
use crate::protocol::ReviewDecision;
|
||||
use crate::protocol::SandboxPolicy;
|
||||
use crate::protocol::SessionConfiguredEvent;
|
||||
use crate::protocol::StreamErrorEvent;
|
||||
use crate::protocol::Submission;
|
||||
use crate::protocol::TaskCompleteEvent;
|
||||
use crate::protocol::TurnDiffEvent;
|
||||
@@ -910,7 +909,7 @@ impl Session {
|
||||
async fn notify_stream_error(&self, sub_id: &str, message: impl Into<String>) {
|
||||
let event = Event {
|
||||
id: sub_id.to_string(),
|
||||
msg: EventMsg::StreamError(StreamErrorEvent {
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: message.into(),
|
||||
}),
|
||||
};
|
||||
@@ -1856,10 +1855,10 @@ async fn run_turn(
|
||||
tools: tools.clone(),
|
||||
base_instructions_override: sess.base_instructions.clone(),
|
||||
environment_context: Some(EnvironmentContext::new(
|
||||
sess.cwd.clone(),
|
||||
sess.approval_policy,
|
||||
sess.sandbox_policy.clone(),
|
||||
sess.user_shell.clone(),
|
||||
Some(sess.cwd.clone()),
|
||||
Some(sess.approval_policy),
|
||||
Some(sess.sandbox_policy.clone()),
|
||||
Some(sess.user_shell.clone()),
|
||||
)),
|
||||
status_items, // Include status items with this request
|
||||
};
|
||||
|
||||
@@ -98,10 +98,10 @@ impl EnvironmentContext {
|
||||
network_access
|
||||
));
|
||||
}
|
||||
if let Some(shell) = self.shell
|
||||
&& let Some(shell_name) = shell.name()
|
||||
{
|
||||
lines.push(format!(" <shell>{}</shell>", shell_name));
|
||||
if let Some(shell) = self.shell {
|
||||
if let Some(shell_name) = shell.name() {
|
||||
lines.push(format!(" <shell>{}</shell>", shell_name));
|
||||
}
|
||||
}
|
||||
lines.push(ENVIRONMENT_CONTEXT_END.to_string());
|
||||
lines.join("\n")
|
||||
|
||||
@@ -20,7 +20,6 @@ use codex_core::protocol::McpToolCallEndEvent;
|
||||
use codex_core::protocol::PatchApplyBeginEvent;
|
||||
use codex_core::protocol::PatchApplyEndEvent;
|
||||
use codex_core::protocol::SessionConfiguredEvent;
|
||||
use codex_core::protocol::StreamErrorEvent;
|
||||
use codex_core::protocol::TaskCompleteEvent;
|
||||
use codex_protocol::protocol::TurnAbortReason;
|
||||
use codex_core::protocol::TurnDiffEvent;
|
||||
@@ -175,9 +174,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
||||
EventMsg::BackgroundEvent(BackgroundEventEvent { message }) => {
|
||||
ts_println!(self, "{}", message.style(self.dimmed));
|
||||
}
|
||||
EventMsg::StreamError(StreamErrorEvent { message }) => {
|
||||
ts_println!(self, "{}", message.style(self.dimmed));
|
||||
}
|
||||
// Stream errors are surfaced as Error/Background events in core
|
||||
EventMsg::TaskStarted => {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
@@ -148,10 +148,22 @@ impl CodexMessageProcessor {
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
ClientRequest::LogoutChatGpt { request_id } => {
|
||||
self.logout_chatgpt(request_id).await;
|
||||
// Not supported by this server implementation
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: "logout is not supported by this server".to_string(),
|
||||
data: None,
|
||||
};
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
ClientRequest::GetAuthStatus { request_id } => {
|
||||
self.get_auth_status(request_id).await;
|
||||
// Not supported by this server implementation
|
||||
let error = JSONRPCErrorError {
|
||||
code: INVALID_REQUEST_ERROR_CODE,
|
||||
message: "auth status is not supported by this server".to_string(),
|
||||
data: None,
|
||||
};
|
||||
self.outgoing.send_error(request_id, error).await;
|
||||
}
|
||||
ClientRequest::GitDiffToRemote { request_id, params } => {
|
||||
self.git_diff_to_origin(request_id, params.cwd).await;
|
||||
|
||||
@@ -268,7 +268,6 @@ async fn run_codex_tool_session_inner(
|
||||
| EventMsg::ExecCommandOutputDelta(_)
|
||||
| EventMsg::ExecCommandEnd(_)
|
||||
| EventMsg::BackgroundEvent(_)
|
||||
| EventMsg::StreamError(_)
|
||||
| EventMsg::PatchApplyBegin(_)
|
||||
| EventMsg::PatchApplyEnd(_)
|
||||
| EventMsg::TurnDiff(_)
|
||||
|
||||
@@ -8,6 +8,7 @@ use crate::onboarding::onboarding_screen::KeyboardHandler;
|
||||
use crate::onboarding::onboarding_screen::OnboardingScreen;
|
||||
use crate::onboarding::onboarding_screen::OnboardingScreenArgs;
|
||||
use crate::slash_command::SlashCommand;
|
||||
use crate::transcript_app::TranscriptApp;
|
||||
use crate::tui;
|
||||
use crate::tui::TerminalInfo;
|
||||
use codex_core::ConversationManager;
|
||||
@@ -24,6 +25,8 @@ use crossterm::terminal::EnterAlternateScreen;
|
||||
use crossterm::terminal::LeaveAlternateScreen;
|
||||
use crossterm::terminal::supports_keyboard_enhancement;
|
||||
use std::path::PathBuf;
|
||||
use ratatui::prelude::Rect;
|
||||
use ratatui::text::Line;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
@@ -209,6 +212,9 @@ impl App<'_> {
|
||||
config,
|
||||
file_search,
|
||||
pending_redraw,
|
||||
transcript_overlay: None,
|
||||
deferred_history_lines: Vec::new(),
|
||||
transcript_saved_viewport: None,
|
||||
enhanced_keys_supported,
|
||||
_debug: debug,
|
||||
commit_anim_running: Arc::new(AtomicBool::new(false)),
|
||||
|
||||
@@ -42,6 +42,7 @@ const LARGE_PASTE_CHAR_THRESHOLD: usize = 1000;
|
||||
/// Result returned when the user interacts with the text area.
|
||||
pub enum InputResult {
|
||||
Submitted(String),
|
||||
Command(SlashCommand),
|
||||
ScrollUp,
|
||||
ScrollDown,
|
||||
None,
|
||||
|
||||
@@ -58,6 +58,8 @@ use std::cell::RefCell;
|
||||
use tokio::sync::mpsc::UnboundedSender;
|
||||
use tokio::sync::mpsc::unbounded_channel;
|
||||
use tracing::info;
|
||||
use crate::slash_command::SlashCommand;
|
||||
use crate::get_git_diff::get_git_diff;
|
||||
// use image::GenericImageView;
|
||||
|
||||
use crate::app_event::AppEvent;
|
||||
@@ -1453,6 +1455,10 @@ impl ChatWidget<'_> {
|
||||
let user_message = self.parse_message_with_images(text);
|
||||
self.submit_user_message(user_message);
|
||||
}
|
||||
InputResult::Command(_cmd) => {
|
||||
// Command was dispatched at the App layer; request redraw.
|
||||
self.app_event_tx.send(AppEvent::RequestRedraw);
|
||||
}
|
||||
InputResult::ScrollUp => {
|
||||
// If already at the very top, try navigating command history instead
|
||||
if self.scroll_offset >= self.last_max_scroll.get() {
|
||||
@@ -1523,101 +1529,7 @@ impl ChatWidget<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
fn dispatch_command(&mut self, cmd: SlashCommand) {
|
||||
match cmd {
|
||||
SlashCommand::New => {
|
||||
self.app_event_tx.send(AppEvent::NewSession);
|
||||
}
|
||||
SlashCommand::Init => {
|
||||
// Guard: do not run if a task is active.
|
||||
const INIT_PROMPT: &str = include_str!("../prompt_for_init_command.md");
|
||||
self.submit_text_message(INIT_PROMPT.to_string());
|
||||
}
|
||||
SlashCommand::Compact => {
|
||||
self.clear_token_usage();
|
||||
self.app_event_tx.send(AppEvent::CodexOp(Op::Compact));
|
||||
}
|
||||
SlashCommand::Model => {
|
||||
self.open_model_popup();
|
||||
}
|
||||
SlashCommand::Approvals => {
|
||||
self.open_approvals_popup();
|
||||
}
|
||||
SlashCommand::Quit => {
|
||||
self.app_event_tx.send(AppEvent::ExitRequest);
|
||||
}
|
||||
SlashCommand::Logout => {
|
||||
if let Err(e) = codex_login::logout(&self.config.codex_home) {
|
||||
tracing::error!("failed to logout: {e}");
|
||||
}
|
||||
self.app_event_tx.send(AppEvent::ExitRequest);
|
||||
}
|
||||
SlashCommand::Diff => {
|
||||
self.add_diff_in_progress();
|
||||
let tx = self.app_event_tx.clone();
|
||||
tokio::spawn(async move {
|
||||
let text = match get_git_diff().await {
|
||||
Ok((is_git_repo, diff_text)) => {
|
||||
if is_git_repo {
|
||||
diff_text
|
||||
} else {
|
||||
"`/diff` — _not inside a git repository_".to_string()
|
||||
}
|
||||
}
|
||||
Err(e) => format!("Failed to compute diff: {e}"),
|
||||
};
|
||||
tx.send(AppEvent::DiffResult(text));
|
||||
});
|
||||
}
|
||||
SlashCommand::Mention => {
|
||||
self.insert_str("@");
|
||||
}
|
||||
SlashCommand::Status => {
|
||||
self.add_status_output();
|
||||
}
|
||||
SlashCommand::Mcp => {
|
||||
self.add_mcp_output();
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
SlashCommand::TestApproval => {
|
||||
use codex_core::protocol::EventMsg;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use codex_core::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_core::protocol::FileChange;
|
||||
|
||||
self.app_event_tx.send(AppEvent::CodexEvent(Event {
|
||||
id: "1".to_string(),
|
||||
// msg: EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
|
||||
// call_id: "1".to_string(),
|
||||
// command: vec!["git".into(), "apply".into()],
|
||||
// cwd: self.config.cwd.clone(),
|
||||
// reason: Some("test".to_string()),
|
||||
// }),
|
||||
msg: EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent {
|
||||
call_id: "1".to_string(),
|
||||
changes: HashMap::from([
|
||||
(
|
||||
PathBuf::from("/tmp/test.txt"),
|
||||
FileChange::Add {
|
||||
content: "test".to_string(),
|
||||
},
|
||||
),
|
||||
(
|
||||
PathBuf::from("/tmp/test2.txt"),
|
||||
FileChange::Update {
|
||||
unified_diff: "+test\n-test2".to_string(),
|
||||
move_path: None,
|
||||
},
|
||||
),
|
||||
]),
|
||||
reason: None,
|
||||
grant_root: Some(PathBuf::from("/tmp")),
|
||||
}),
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
// dispatch_command() removed — command routing is handled at the App layer via AppEvent::DispatchCommand
|
||||
|
||||
pub(crate) fn handle_paste(&mut self, text: String) {
|
||||
// Check if the pasted text is a file path to an image
|
||||
|
||||
@@ -58,6 +58,7 @@ mod theme;
|
||||
mod tui;
|
||||
mod user_approval_widget;
|
||||
mod height_manager;
|
||||
mod transcript_app;
|
||||
|
||||
// Internal vt100-based replay tests live as a separate source file to keep them
|
||||
// close to the widget code. Include them in unit tests.
|
||||
|
||||
@@ -2,7 +2,6 @@ use std::io::Result;
|
||||
|
||||
use crate::insert_history;
|
||||
use crate::tui;
|
||||
use crate::tui::TuiEvent;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
@@ -36,8 +35,10 @@ impl TranscriptApp {
|
||||
match event {
|
||||
TuiEvent::Key(key_event) => self.handle_key_event(tui, key_event),
|
||||
TuiEvent::Draw => {
|
||||
tui.draw(u16::MAX, |frame| {
|
||||
self.render(frame.area(), frame.buffer);
|
||||
tui.draw(|frame| {
|
||||
let area = frame.area();
|
||||
let buf = frame.buffer_mut();
|
||||
self.render(area, buf);
|
||||
})?;
|
||||
}
|
||||
_ => {}
|
||||
@@ -89,7 +90,8 @@ impl TranscriptApp {
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
} => {
|
||||
let area = self.scroll_area(tui.terminal.viewport_area);
|
||||
let (w, h) = crossterm::terminal::size().unwrap_or((80, 24));
|
||||
let area = self.scroll_area(Rect::new(0, 0, w, h));
|
||||
self.scroll_offset = self.scroll_offset.saturating_sub(area.height as usize);
|
||||
}
|
||||
KeyEvent {
|
||||
@@ -97,7 +99,8 @@ impl TranscriptApp {
|
||||
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
||||
..
|
||||
} => {
|
||||
let area = self.scroll_area(tui.terminal.viewport_area);
|
||||
let (w, h) = crossterm::terminal::size().unwrap_or((80, 24));
|
||||
let area = self.scroll_area(Rect::new(0, 0, w, h));
|
||||
self.scroll_offset = self.scroll_offset.saturating_add(area.height as usize);
|
||||
}
|
||||
KeyEvent {
|
||||
@@ -207,3 +210,10 @@ impl TranscriptApp {
|
||||
.render_ref(hints_rect, buf);
|
||||
}
|
||||
}
|
||||
|
||||
/// Minimal local event enum for transcript overlay rendering
|
||||
pub(crate) enum TuiEvent {
|
||||
Key(KeyEvent),
|
||||
Draw,
|
||||
Other,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user