mirror of
https://github.com/openai/codex.git
synced 2026-05-28 06:55:01 +00:00
TUI config cleanup: plugin marketplace (#24257)
## Why Plugin and marketplace mutations are applied by the app server, but several TUI follow-up paths still refreshed state from the TUI host config. In remote workspace mode, that can leave plugin UI state tied to stale client-local `config.toml` after the server has already applied the mutation. ## What - Stop reloading the TUI host config after app-server-owned plugin, marketplace, skill, and app mutations. - Use the same app-server-owned refresh path for local and remote sessions: ask the app server to reload user config where the running session needs it, then refetch plugin list/detail state from the app server. - Build plugin mention candidates from existing app-server `plugin/list` and `plugin/read` data in both local and remote sessions instead of TUI-host plugin config. - Avoid the duplicate local config reload after `ReloadUserConfig` asks the app server to reload config. ## Verification Manually launched a local WebSocket app-server with a temp server `CODEX_HOME`, launched the TUI with a separate temp host `CODEX_HOME` and `--remote`, installed a sample plugin from a temp local marketplace through `/plugins`, and confirmed the TUI refreshed to installed state while only the server config gained `[plugins."sample@debug"]`. Trace logs showed the TUI using app-server `plugin/list` and `plugin/read` for the refresh path.
This commit is contained in:
@@ -400,16 +400,16 @@ impl App {
|
||||
}
|
||||
|
||||
pub(super) fn refresh_plugin_mentions(&mut self, app_server: &AppServerSession) {
|
||||
let config = self.config.clone();
|
||||
let cwd = self.config.cwd.to_path_buf();
|
||||
let request_handle = app_server.request_handle();
|
||||
let app_event_tx = self.app_event_tx.clone();
|
||||
if !config.features.enabled(Feature::Plugins) {
|
||||
if !self.config.features.enabled(Feature::Plugins) {
|
||||
app_event_tx.send(AppEvent::PluginMentionsLoaded { plugins: None });
|
||||
return;
|
||||
}
|
||||
|
||||
tokio::spawn(async move {
|
||||
match fetch_plugin_mentions(request_handle, config.cwd.to_path_buf()).await {
|
||||
match fetch_plugin_mentions(request_handle, cwd).await {
|
||||
Ok(plugins) => {
|
||||
app_event_tx.send(AppEvent::PluginMentionsLoaded {
|
||||
plugins: Some(plugins),
|
||||
|
||||
@@ -506,9 +506,6 @@ impl App {
|
||||
self.chat_widget
|
||||
.on_marketplace_add_loaded(cwd.clone(), source, result);
|
||||
if add_succeeded && self.chat_widget.config_ref().cwd.as_path() == cwd.as_path() {
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
tracing::warn!(error = %err, "failed to refresh config after marketplace add");
|
||||
}
|
||||
self.fetch_plugins_list(app_server, cwd);
|
||||
}
|
||||
}
|
||||
@@ -516,14 +513,7 @@ impl App {
|
||||
let marketplace_contents_changed =
|
||||
matches!(&result, Ok(response) if !response.upgraded_roots.is_empty());
|
||||
if marketplace_contents_changed {
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
"failed to refresh config after marketplace upgrade"
|
||||
);
|
||||
}
|
||||
self.chat_widget.refresh_plugin_mentions();
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
self.refresh_plugin_mentions_after_config_write();
|
||||
}
|
||||
self.chat_widget
|
||||
.on_marketplace_upgrade_loaded(cwd.clone(), result);
|
||||
@@ -558,11 +548,7 @@ impl App {
|
||||
);
|
||||
if remove_succeeded && self.chat_widget.config_ref().cwd.as_path() == cwd.as_path()
|
||||
{
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
tracing::warn!(error = %err, "failed to refresh config after marketplace remove");
|
||||
}
|
||||
self.chat_widget.refresh_plugin_mentions();
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
self.refresh_plugin_mentions_after_config_write();
|
||||
self.fetch_plugins_list(app_server, cwd);
|
||||
}
|
||||
}
|
||||
@@ -609,11 +595,7 @@ impl App {
|
||||
} => {
|
||||
let install_succeeded = result.is_ok();
|
||||
if install_succeeded {
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
tracing::warn!(error = %err, "failed to refresh config after plugin install");
|
||||
}
|
||||
self.chat_widget.refresh_plugin_mentions();
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
self.refresh_plugin_mentions_after_config_write();
|
||||
}
|
||||
let should_refresh_plugin_detail = self.chat_widget.on_plugin_install_loaded(
|
||||
cwd.clone(),
|
||||
@@ -665,14 +647,7 @@ impl App {
|
||||
self.pending_plugin_enabled_writes.remove(&plugin_id);
|
||||
let update_succeeded = result.is_ok();
|
||||
if update_succeeded {
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
"failed to refresh config after plugin toggle"
|
||||
);
|
||||
}
|
||||
self.chat_widget.refresh_plugin_mentions();
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
self.refresh_plugin_mentions_after_config_write();
|
||||
}
|
||||
self.chat_widget
|
||||
.on_plugin_enabled_set(cwd, plugin_id, enabled, result);
|
||||
@@ -1303,14 +1278,7 @@ impl App {
|
||||
} => {
|
||||
let uninstall_succeeded = result.is_ok();
|
||||
if uninstall_succeeded {
|
||||
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
"failed to refresh config after plugin uninstall"
|
||||
);
|
||||
}
|
||||
self.chat_widget.refresh_plugin_mentions();
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
self.refresh_plugin_mentions_after_config_write();
|
||||
}
|
||||
self.chat_widget.on_plugin_uninstall_loaded(
|
||||
cwd.clone(),
|
||||
@@ -1705,14 +1673,6 @@ impl App {
|
||||
{
|
||||
Ok(()) => {
|
||||
self.chat_widget.update_skill_enabled(path, enabled);
|
||||
if !app_server.uses_remote_workspace()
|
||||
&& let Err(err) = self.refresh_in_memory_config_from_disk().await
|
||||
{
|
||||
tracing::warn!(
|
||||
error = %err,
|
||||
"failed to refresh config after skill toggle"
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let path_display = path.display();
|
||||
@@ -1749,11 +1709,6 @@ impl App {
|
||||
{
|
||||
Ok(_) => {
|
||||
self.chat_widget.update_connector_enabled(&id, enabled);
|
||||
if !app_server.uses_remote_workspace()
|
||||
&& let Err(err) = self.refresh_in_memory_config_from_disk().await
|
||||
{
|
||||
tracing::warn!(error = %err, "failed to refresh config after app toggle");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
self.chat_widget.add_error_message(format!(
|
||||
@@ -2098,6 +2053,11 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_plugin_mentions_after_config_write(&mut self) {
|
||||
self.chat_widget.refresh_plugin_mentions();
|
||||
self.chat_widget.submit_op(AppCommand::reload_user_config());
|
||||
}
|
||||
|
||||
async fn apply_keymap_clear(&mut self, context: String, action: String) {
|
||||
let keymap_config = match crate::keymap_setup::keymap_without_custom_binding(
|
||||
&self.config.tui_keymap,
|
||||
|
||||
@@ -684,7 +684,6 @@ impl App {
|
||||
}
|
||||
AppCommand::ReloadUserConfig => {
|
||||
app_server.reload_user_config().await?;
|
||||
self.refresh_in_memory_config_from_disk().await?;
|
||||
Ok(true)
|
||||
}
|
||||
AppCommand::OverrideTurnContext { .. } => {
|
||||
|
||||
Reference in New Issue
Block a user