mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
## Why The app-server no longer needs to expose a TCP websocket listener. Keeping that transport also kept around a separate listener/auth surface that is unnecessary now that local clients can use stdio or the Unix-domain control socket, while remote connectivity is handled by `remote_control`. ## What Changed - Removed `ws://IP:PORT` parsing and the `AppServerTransport::WebSocket` startup path. - Deleted the app-server websocket listener auth module and removed related CLI flags/dependencies. - Kept websocket framing only where it is still needed: over the Unix-domain control socket and in the outbound `remote_control` connection. - Updated app-server CLI/help text and `app-server/README.md` to document only `stdio://`, `unix://`, `unix://PATH`, and `off` for local transports. - Converted affected app-server integration coverage from TCP websocket listeners to UDS-backed websocket connections, and added a parse test that rejects `ws://` listen URLs. - Removed the now-unused workspace `constant_time_eq` dependency and refreshed `Cargo.lock` after `cargo shear` caught the drift. - Moved test app-server UDS socket paths to short Unix temp paths so macOS Bazel test sandboxes do not exceed Unix socket path limits. ## Verification - Added/updated tests around UDS websocket transport behavior and `ws://` listen URL rejection. - `cargo shear` - `cargo metadata --no-deps --format-version 1` - `cargo test -p codex-app-server unix_socket_transport` - `cargo test -p codex-app-server unix_socket_disconnect` - `just fix -p codex-app-server` - `git diff --check` Local full Rust test execution was blocked before compilation by an external fetch failure for the pinned `nornagon/crossterm` git dependency. `just bazel-lock-update` and `just bazel-lock-check` were retried after the manifest cleanup but remain blocked by external BuildBuddy/V8 fetch timeouts.
197 lines
7.3 KiB
Rust
197 lines
7.3 KiB
Rust
use super::connection_handling_websocket::DEFAULT_READ_TIMEOUT;
|
|
use super::connection_handling_websocket::WsClient;
|
|
use super::connection_handling_websocket::assert_no_message;
|
|
use super::connection_handling_websocket::connect_websocket;
|
|
use super::connection_handling_websocket::create_config_toml;
|
|
use super::connection_handling_websocket::read_notification_for_method;
|
|
use super::connection_handling_websocket::read_response_and_notification_for_method;
|
|
use super::connection_handling_websocket::read_response_for_id;
|
|
use super::connection_handling_websocket::send_initialize_request;
|
|
use super::connection_handling_websocket::send_request;
|
|
use super::connection_handling_websocket::spawn_websocket_server;
|
|
use anyhow::Context;
|
|
use anyhow::Result;
|
|
use app_test_support::create_fake_rollout_with_text_elements;
|
|
use app_test_support::create_mock_responses_server_repeating_assistant;
|
|
use app_test_support::to_response;
|
|
use codex_app_server_protocol::JSONRPCNotification;
|
|
use codex_app_server_protocol::JSONRPCResponse;
|
|
use codex_app_server_protocol::ThreadNameUpdatedNotification;
|
|
use codex_app_server_protocol::ThreadResumeParams;
|
|
use codex_app_server_protocol::ThreadResumeResponse;
|
|
use codex_app_server_protocol::ThreadSetNameParams;
|
|
use codex_app_server_protocol::ThreadSetNameResponse;
|
|
use codex_core::find_thread_name_by_id;
|
|
use codex_protocol::ThreadId;
|
|
use pretty_assertions::assert_eq;
|
|
use std::path::Path;
|
|
use tempfile::TempDir;
|
|
use tokio::time::Duration;
|
|
use tokio::time::timeout;
|
|
|
|
#[tokio::test]
|
|
async fn thread_name_updated_broadcasts_for_loaded_threads() -> Result<()> {
|
|
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
|
let codex_home = TempDir::new()?;
|
|
create_config_toml(codex_home.path(), &server.uri(), "never")?;
|
|
let conversation_id = create_rollout(codex_home.path(), "2025-01-05T12-00-00")?;
|
|
|
|
let (mut process, socket_path, _socket_dir) = spawn_websocket_server(codex_home.path()).await?;
|
|
|
|
let result = async {
|
|
let mut ws1 = connect_websocket(&socket_path).await?;
|
|
let mut ws2 = connect_websocket(&socket_path).await?;
|
|
initialize_both_clients(&mut ws1, &mut ws2).await?;
|
|
|
|
send_request(
|
|
&mut ws1,
|
|
"thread/resume",
|
|
/*id*/ 10,
|
|
Some(serde_json::to_value(ThreadResumeParams {
|
|
thread_id: conversation_id.clone(),
|
|
..Default::default()
|
|
})?),
|
|
)
|
|
.await?;
|
|
let resume_resp: JSONRPCResponse = read_response_for_id(&mut ws1, /*id*/ 10).await?;
|
|
let resume: ThreadResumeResponse = to_response::<ThreadResumeResponse>(resume_resp)?;
|
|
assert_eq!(resume.thread.id, conversation_id);
|
|
|
|
let renamed = "Loaded rename";
|
|
send_request(
|
|
&mut ws1,
|
|
"thread/name/set",
|
|
/*id*/ 11,
|
|
Some(serde_json::to_value(ThreadSetNameParams {
|
|
thread_id: conversation_id.clone(),
|
|
name: renamed.to_string(),
|
|
})?),
|
|
)
|
|
.await?;
|
|
let (rename_resp, ws1_notification) = read_response_and_notification_for_method(
|
|
&mut ws1,
|
|
/*id*/ 11,
|
|
"thread/name/updated",
|
|
)
|
|
.await?;
|
|
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
|
|
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
|
|
|
|
let ws2_notification =
|
|
read_notification_for_method(&mut ws2, "thread/name/updated").await?;
|
|
assert_thread_name_updated(ws2_notification, &conversation_id, renamed)?;
|
|
assert_legacy_thread_name(codex_home.path(), &conversation_id, renamed).await?;
|
|
|
|
assert_no_message(&mut ws1, Duration::from_millis(250)).await?;
|
|
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
|
|
Ok(())
|
|
}
|
|
.await;
|
|
|
|
process
|
|
.kill()
|
|
.await
|
|
.context("failed to stop app-server process")?;
|
|
result
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn thread_name_updated_broadcasts_for_not_loaded_threads() -> Result<()> {
|
|
let server = create_mock_responses_server_repeating_assistant("Done").await;
|
|
let codex_home = TempDir::new()?;
|
|
create_config_toml(codex_home.path(), &server.uri(), "never")?;
|
|
let conversation_id = create_rollout(codex_home.path(), "2025-01-05T12-05-00")?;
|
|
|
|
let (mut process, socket_path, _socket_dir) = spawn_websocket_server(codex_home.path()).await?;
|
|
|
|
let result = async {
|
|
let mut ws1 = connect_websocket(&socket_path).await?;
|
|
let mut ws2 = connect_websocket(&socket_path).await?;
|
|
initialize_both_clients(&mut ws1, &mut ws2).await?;
|
|
|
|
let renamed = "Stored rename";
|
|
send_request(
|
|
&mut ws1,
|
|
"thread/name/set",
|
|
/*id*/ 20,
|
|
Some(serde_json::to_value(ThreadSetNameParams {
|
|
thread_id: conversation_id.clone(),
|
|
name: renamed.to_string(),
|
|
})?),
|
|
)
|
|
.await?;
|
|
let (rename_resp, ws1_notification) = read_response_and_notification_for_method(
|
|
&mut ws1,
|
|
/*id*/ 20,
|
|
"thread/name/updated",
|
|
)
|
|
.await?;
|
|
let _: ThreadSetNameResponse = to_response::<ThreadSetNameResponse>(rename_resp)?;
|
|
assert_thread_name_updated(ws1_notification, &conversation_id, renamed)?;
|
|
|
|
let ws2_notification =
|
|
read_notification_for_method(&mut ws2, "thread/name/updated").await?;
|
|
assert_thread_name_updated(ws2_notification, &conversation_id, renamed)?;
|
|
assert_legacy_thread_name(codex_home.path(), &conversation_id, renamed).await?;
|
|
|
|
assert_no_message(&mut ws1, Duration::from_millis(250)).await?;
|
|
assert_no_message(&mut ws2, Duration::from_millis(250)).await?;
|
|
Ok(())
|
|
}
|
|
.await;
|
|
|
|
process
|
|
.kill()
|
|
.await
|
|
.context("failed to stop app-server process")?;
|
|
result
|
|
}
|
|
|
|
async fn initialize_both_clients(ws1: &mut WsClient, ws2: &mut WsClient) -> Result<()> {
|
|
send_initialize_request(ws1, /*id*/ 1, "ws_client_one").await?;
|
|
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws1, /*id*/ 1)).await??;
|
|
|
|
send_initialize_request(ws2, /*id*/ 2, "ws_client_two").await?;
|
|
timeout(DEFAULT_READ_TIMEOUT, read_response_for_id(ws2, /*id*/ 2)).await??;
|
|
Ok(())
|
|
}
|
|
|
|
fn create_rollout(codex_home: &std::path::Path, filename_ts: &str) -> Result<String> {
|
|
create_fake_rollout_with_text_elements(
|
|
codex_home,
|
|
filename_ts,
|
|
"2025-01-05T12:00:00Z",
|
|
"Saved user message",
|
|
Vec::new(),
|
|
Some("mock_provider"),
|
|
/*git_info*/ None,
|
|
)
|
|
}
|
|
|
|
fn assert_thread_name_updated(
|
|
notification: JSONRPCNotification,
|
|
thread_id: &str,
|
|
thread_name: &str,
|
|
) -> Result<()> {
|
|
let notification: ThreadNameUpdatedNotification =
|
|
serde_json::from_value(notification.params.context("thread/name/updated params")?)?;
|
|
assert_eq!(notification.thread_id, thread_id);
|
|
assert_eq!(notification.thread_name.as_deref(), Some(thread_name));
|
|
Ok(())
|
|
}
|
|
|
|
async fn assert_legacy_thread_name(
|
|
codex_home: &Path,
|
|
conversation_id: &str,
|
|
expected_name: &str,
|
|
) -> Result<()> {
|
|
let thread_id = ThreadId::from_string(conversation_id)?;
|
|
assert_eq!(
|
|
find_thread_name_by_id(codex_home, &thread_id)
|
|
.await?
|
|
.as_deref(),
|
|
Some(expected_name)
|
|
);
|
|
Ok(())
|
|
}
|