mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
[2 of 4] tui: route app and skill enablement through app server (#22914)
## Why App and skill toggles are user config mutations too. When the TUI is attached to a remote app server, writing those toggles into the local `config.toml` makes the UI report success without updating the server that actually owns the session. This is **[2 of 4]** in a stacked series that moves TUI-owned config mutations onto app-server APIs. ## What changed - Routed app enable/disable persistence through app-server config batch writes. - Routed skill enable/disable persistence through `skills/config/write`. - Avoided refreshing local config from disk after these writes when the TUI is connected to a remote app server. ## Config keys affected - `apps.<app_id>.enabled` - `apps.<app_id>.disabled_reason` - `[[skills.config]]` entries keyed by `path`, with `enabled = false` used for persisted disables ## Suggested manual validation - Connect the TUI to a remote app server, disable an app, reconnect, and confirm the app remains disabled from remote config rather than local disk state. - Re-enable the same app and confirm both `apps.<app_id>.enabled` and `apps.<app_id>.disabled_reason` are cleared remotely. - Disable a skill in the manage-skills UI and confirm a remote `[[skills.config]]` disable entry appears. - Re-enable that skill and confirm the disable entry is removed and the effective enabled state updates without relying on local config reloads. ## Stack 1. [#22913](https://github.com/openai/codex/pull/22913) `[1 of 4]` primary settings writes 2. [#22914](https://github.com/openai/codex/pull/22914) `[2 of 4]` app and skill enablement 3. [#22915](https://github.com/openai/codex/pull/22915) `[3 of 4]` feature and memory toggles 4. [#22916](https://github.com/openai/codex/pull/22916) `[4 of 4]` startup and onboarding bookkeeping
This commit is contained in:
@@ -275,6 +275,7 @@ use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::NetworkProxyAuditMetadata;
|
||||
use codex_core::config::edit::ConfigEdit;
|
||||
use codex_core::config::edit::ConfigEditsBuilder;
|
||||
use codex_core::connectors::AccessibleConnectorsStatus;
|
||||
use codex_core::exec::ExecCapturePolicy;
|
||||
use codex_core::exec::ExecExpiration;
|
||||
use codex_core::exec::ExecParams;
|
||||
|
||||
@@ -41,11 +41,19 @@ impl AppsRequestProcessor {
|
||||
request_id: &ConnectionRequestId,
|
||||
params: AppsListParams,
|
||||
) -> Result<Option<AppsListResponse>, JSONRPCErrorError> {
|
||||
let mut config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
|
||||
if let Some(thread_id) = params.thread_id.as_deref() {
|
||||
let (_, thread) = self.load_thread(thread_id).await?;
|
||||
let thread = if let Some(thread_id) = params.thread_id.as_deref() {
|
||||
let (_, loaded_thread) = self.load_thread(thread_id).await?;
|
||||
Some(loaded_thread)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let fallback_cwd = match thread.as_ref() {
|
||||
Some(thread) => Some(thread.config_snapshot().await.cwd.to_path_buf()),
|
||||
None => None,
|
||||
};
|
||||
let mut config = self.load_latest_config(fallback_cwd).await?;
|
||||
|
||||
if let Some(thread) = thread {
|
||||
let _ = config
|
||||
.features
|
||||
.set_enabled(Feature::Apps, thread.enabled(Feature::Apps));
|
||||
@@ -88,8 +96,31 @@ impl AppsRequestProcessor {
|
||||
config: Config,
|
||||
environment_manager: Arc<EnvironmentManager>,
|
||||
) {
|
||||
let retry_params = params.clone();
|
||||
let retry_config = config.clone();
|
||||
let retry_environment_manager = Arc::clone(&environment_manager);
|
||||
let result = Self::apps_list_response(&outgoing, params, config, environment_manager).await;
|
||||
outgoing.send_result(request_id, result).await;
|
||||
let should_retry = result
|
||||
.as_ref()
|
||||
.is_ok_and(|(_, codex_apps_ready)| !codex_apps_ready);
|
||||
outgoing
|
||||
.send_result(request_id, result.map(|(response, _)| response))
|
||||
.await;
|
||||
|
||||
if should_retry && !retry_params.force_refetch {
|
||||
let mut retry_params = retry_params;
|
||||
retry_params.force_refetch = true;
|
||||
if let Err(err) = Self::apps_list_response(
|
||||
&outgoing,
|
||||
retry_params,
|
||||
retry_config,
|
||||
retry_environment_manager,
|
||||
)
|
||||
.await
|
||||
{
|
||||
warn!("failed to refresh app list after codex-apps readiness retry: {err:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn apps_list_response(
|
||||
@@ -97,7 +128,7 @@ impl AppsRequestProcessor {
|
||||
params: AppsListParams,
|
||||
config: Config,
|
||||
environment_manager: Arc<EnvironmentManager>,
|
||||
) -> Result<AppsListResponse, JSONRPCErrorError> {
|
||||
) -> Result<(AppsListResponse, bool), JSONRPCErrorError> {
|
||||
let AppsListParams {
|
||||
cursor,
|
||||
limit,
|
||||
@@ -130,7 +161,6 @@ impl AppsRequestProcessor {
|
||||
&environment_manager,
|
||||
)
|
||||
.await
|
||||
.map(|status| status.connectors)
|
||||
.map_err(|err| format!("failed to load accessible apps: {err}"));
|
||||
let _ = accessible_tx.send(AppListLoadResult::Accessible(result));
|
||||
});
|
||||
@@ -146,6 +176,7 @@ impl AppsRequestProcessor {
|
||||
let app_list_deadline = tokio::time::Instant::now() + APP_LIST_LOAD_TIMEOUT;
|
||||
let mut accessible_loaded = false;
|
||||
let mut all_loaded = false;
|
||||
let mut codex_apps_ready = true;
|
||||
let mut last_notified_apps = None;
|
||||
|
||||
if accessible_connectors.is_some() || all_connectors.is_some() {
|
||||
@@ -178,9 +209,10 @@ impl AppsRequestProcessor {
|
||||
};
|
||||
|
||||
match result {
|
||||
AppListLoadResult::Accessible(Ok(connectors)) => {
|
||||
accessible_connectors = Some(connectors);
|
||||
AppListLoadResult::Accessible(Ok(status)) => {
|
||||
accessible_connectors = Some(status.connectors);
|
||||
accessible_loaded = true;
|
||||
codex_apps_ready = status.codex_apps_ready;
|
||||
}
|
||||
AppListLoadResult::Accessible(Err(err)) => {
|
||||
return Err(internal_error(err));
|
||||
@@ -222,7 +254,8 @@ impl AppsRequestProcessor {
|
||||
}
|
||||
|
||||
if accessible_loaded && all_loaded {
|
||||
return paginate_apps(merged.as_slice(), start, limit);
|
||||
let response = paginate_apps(merged.as_slice(), start, limit)?;
|
||||
return Ok((response, codex_apps_ready));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -279,7 +312,7 @@ impl AppsRequestProcessor {
|
||||
const APP_LIST_LOAD_TIMEOUT: Duration = Duration::from_secs(90);
|
||||
|
||||
enum AppListLoadResult {
|
||||
Accessible(Result<Vec<AppInfo>, String>),
|
||||
Accessible(Result<AccessibleConnectorsStatus, String>),
|
||||
Directory(Result<Vec<AppInfo>, String>),
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user