Skip to content
Merged
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions codex-rs/app-server-protocol/schema/typescript/v2/index.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 17 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,6 +756,23 @@ pub struct ExternalAgentConfigImportHistory {
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigImportHistoriesReadResponse {
pub data: Vec<ExternalAgentConfigImportHistory>,
pub connectors: Vec<ExternalAgentImportedConnectorCandidate>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub enum ExternalAgentImportedConnectorSource {
RemoteMcpServersConfig,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentImportedConnectorCandidate {
pub name: String,
pub session_count: u32,
pub source: ExternalAgentImportedConnectorSource,
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
Expand Down
1 change: 1 addition & 0 deletions codex-rs/app-server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ Example with notification opt-out:
- `config/read` — fetch the effective config on disk after resolving config layering, including opaque `desktop` values stored in `config.toml`.
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome`, optional `cwds`, and an optional `source` identifier reserved for adapter selection; omitted, `null`, or unrecognized values retain the default behavior. Each detected item includes `cwd` (`null` for home), and multi-item migrations may additionally include structured `details` with plugin ids, skill names, session metadata, or other artifact names.
- `externalAgentConfig/import` — apply selected external-agent migration items by passing explicit `migrationItems` with `cwd` (`null` for home) and any `details` returned by detect. Callers may pass `source` to identify the product that initiated the import; omitted or `null` means unspecified. The response acknowledges the synchronous import phase with an `importId`. Expected migration failures are reported as per-item failures rather than JSON-RPC errors, so the server still returns that `importId` and emits `externalAgentConfig/import/completed` with the same ID once all synchronous and background work finishes. The completion notification contains type-level `itemTypeResults` with successes and failures, including raw failure messages for the client to report separately.
- `externalAgentConfig/import/readHistories` — read completed import histories and connector candidates detected from successfully imported session histories. Connector candidates include a normalized display `name`, the number of imported sessions that used the connector, and the source metadata field used for detection.
- `config/value/write` — write a single config key/value to the user's config.toml on disk; dotted paths such as `desktop.someKey` use the same generic write surface.
- `config/batchWrite` — apply multiple config edits atomically to the user's config.toml on disk, with optional `reloadUserConfig: true` to hot-reload loaded threads, including multiple `desktop.*` edits.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including allow-lists (`allowedApprovalPolicies`, `allowedSandboxModes`, `allowedWebSearchModes`), the layered permission-profile allow map (`allowedPermissionProfiles`), the managed permission-profile default (`defaultPermissions`), lifecycle hook lockdown (`allowManagedHooksOnly`), remote-control policy (`allowRemoteControl`; `false` force-disables remote control while `true` or `null` preserves existing behavior), computer use policy (`computerUse`), pinned feature values (`featureRequirements`), managed lifecycle hooks (`hooks`), `enforceResidency`, managed new-thread defaults (`models.newThread.model`, `models.newThread.modelReasoningEffort`, and `models.newThread.serviceTier`), and `network` constraints such as canonical domain/socket permissions plus `managedAllowedDomainsOnly` and `dangerFullAccessDenylistOnly`.
Expand Down
25 changes: 21 additions & 4 deletions codex-rs/app-server/src/external_agent_migration/processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ use codex_app_server_protocol::ExternalAgentConfigImportResponse;
use codex_app_server_protocol::ExternalAgentConfigImportTypeResult as ProtocolImportTypeResult;
use codex_app_server_protocol::ExternalAgentConfigMigrationItem;
use codex_app_server_protocol::ExternalAgentConfigMigrationItemType;
use codex_app_server_protocol::ExternalAgentImportedConnectorCandidate;
use codex_app_server_protocol::ExternalAgentImportedConnectorSource;
use codex_app_server_protocol::HookMigration;
use codex_app_server_protocol::JSONRPCErrorError;
use codex_app_server_protocol::McpServerMigration;
Expand All @@ -32,6 +34,7 @@ use codex_app_server_protocol::SkillMigration;
use codex_arg0::Arg0DispatchPaths;
use codex_core::ThreadManager;
use codex_external_agent_migration::sessions::ExternalAgentSessionMigration as CoreSessionMigration;
use codex_external_agent_migration::sessions::read_imported_connector_candidates;
use codex_rollout::StateDbHandle;
use codex_state::ExternalAgentConfigImportFailureRecord;
use codex_state::ExternalAgentConfigImportSuccessRecord;
Expand Down Expand Up @@ -89,15 +92,16 @@ impl ExternalAgentConfigRequestProcessor {
arg0_paths,
codex_home,
} = args;
let migration_service =
ExternalAgentConfigService::new(codex_home.clone(), analytics_events_client.clone());
let session_importer = ExternalAgentSessionImporter::new(
codex_home.clone(),
codex_home,
migration_service.connector_metadata_roots.clone(),
Arc::clone(&thread_manager),
thread_store,
config_manager,
arg0_paths,
);
let migration_service =
ExternalAgentConfigService::new(codex_home, analytics_events_client.clone());
Self {
outgoing,
migration_service,
Expand Down Expand Up @@ -369,8 +373,21 @@ impl ExternalAgentConfigRequestProcessor {
.into_iter()
.map(protocol_import_history)
.collect::<Result<Vec<_>, _>>()?;
let connectors = read_imported_connector_candidates(&self.migration_service.codex_home)
.map_err(|err| {
internal_error(format!(
"failed to read imported connector candidates: {err}"
))
})?
.into_iter()
.map(|candidate| ExternalAgentImportedConnectorCandidate {
name: candidate.name,
session_count: candidate.session_count,
source: ExternalAgentImportedConnectorSource::RemoteMcpServersConfig,
})
.collect();

Ok(ExternalAgentConfigImportHistoriesReadResponse { data })
Ok(ExternalAgentConfigImportHistoriesReadResponse { data, connectors })
}

fn validate_pending_session_imports(
Expand Down
10 changes: 8 additions & 2 deletions codex-rs/app-server/src/external_agent_migration/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,8 @@ pub(crate) struct ExternalAgentConfigMigrationItem {

#[derive(Clone)]
pub(crate) struct ExternalAgentConfigService {
codex_home: PathBuf,
pub(super) codex_home: PathBuf,
pub(super) connector_metadata_roots: Vec<PathBuf>,
external_agent_home: PathBuf,
analytics_events_client: Option<AnalyticsEventsClient>,
source: ExternalAgentSource,
Expand All @@ -197,8 +198,10 @@ impl ExternalAgentConfigService {
pub(crate) fn new(codex_home: PathBuf, analytics_events_client: AnalyticsEventsClient) -> Self {
let source = ExternalAgentSource::default();
let external_agent_home = default_external_agent_home(source);
let connector_metadata_roots = source.connector_metadata_roots(&external_agent_home);
Self {
codex_home,
connector_metadata_roots,
external_agent_home,
analytics_events_client: Some(analytics_events_client),
source,
Expand All @@ -207,11 +210,14 @@ impl ExternalAgentConfigService {

#[cfg(test)]
fn new_for_test(codex_home: PathBuf, external_agent_home: PathBuf) -> Self {
let source = ExternalAgentSource::default();
let connector_metadata_roots = source.connector_metadata_roots(&external_agent_home);
Self {
codex_home,
connector_metadata_roots,
external_agent_home,
analytics_events_client: None,
source: ExternalAgentSource::default(),
source,
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ impl ExternalAgentSource {
}
}

pub(super) fn connector_metadata_roots(self, external_agent_home: &Path) -> Vec<PathBuf> {
match self {
Self::Cla => source_cla::connector_metadata_roots(external_agent_home),
}
}

pub(super) fn marketplace_import_sources(
self,
external_agent_home: &Path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,48 @@ const COMMAND_MIGRATION_PROFILE: CommandMigrationProfile = CommandMigrationProfi
CommandDescriptionMode::RequireFrontmatter,
);

pub(super) fn connector_metadata_roots(external_agent_home: &Path) -> Vec<PathBuf> {
let Some(home) = external_agent_home
.parent()
.filter(|path| !path.as_os_str().is_empty())
else {
return Vec::new();
};

#[cfg(target_os = "macos")]
{
vec![home.join("Library/Application Support/Claude")]
}

#[cfg(target_os = "windows")]
{
let default_roaming = home.join("AppData/Roaming");
let default_local = home.join("AppData/Local");
let roaming = std::env::var_os("APPDATA")
.map(PathBuf::from)
.filter(|path| path.is_absolute())
.unwrap_or_else(|| default_roaming.clone());
let local = std::env::var_os("LOCALAPPDATA")
.map(PathBuf::from)
.filter(|path| path.is_absolute())
.unwrap_or_else(|| default_local.clone());
let mut roots = vec![
local.join("Packages/Claude_pzs8sxrjxfjjc/LocalCache/Roaming/Claude"),
roaming.join("Claude"),
default_local.join("Packages/Claude_pzs8sxrjxfjjc/LocalCache/Roaming/Claude"),
default_roaming.join("Claude"),
];
roots.sort();
roots.dedup();
roots
}

#[cfg(not(any(target_os = "macos", target_os = "windows")))]
{
vec![home.join(".config/Claude")]
}
}

pub(super) fn effective_settings(project_settings: &Path) -> io::Result<Option<JsonValue>> {
let mut effective = super::read_external_settings(project_settings)?;
let Some(settings_dir) = project_settings.parent() else {
Expand Down
Loading
Loading