[codex] add /import for external agents (#27071)

## Why

External-agent import should be discoverable and deliberate without
blocking startup or claiming the public `codex [PROMPT]` CLI namespace.
The slash command keeps the flow local to the interactive TUI and reuses
the existing app-server import API.

## What changed

- add the user-facing `/import` slash command
- detect external-agent importable items only when the command is
invoked
- run imports through the embedded local app-server
- show start and completion messages, refresh configuration, and block
duplicate imports while one is pending
- reject the flow for unsupported remote and local-daemon sessions

## Validation

- `just test -p codex-tui external_agent_config_migration` (10 passed)
- manually exercised an isolated TUI fixture with existing
external-agent setup and session data using a fresh `CODEX_HOME`
- verified picker customization, plugin and session detection, import
completion, repeated invocation, and imported-session resume context
- the broader `just test -p codex-tui` run passed 2,805 tests, with 2
unrelated guardian feature-flag failures and 4 skipped tests

## Draft follow-ups

- review whether completion messaging should remain attached to the
initiating chat if the user switches chats during an import
- review shutdown semantics for an in-progress background import

## Stack

1. [#27064](https://github.com/openai/codex/pull/27064): remove the
startup migration flow
2. [#27065](https://github.com/openai/codex/pull/27065): extract the
picker renderer
3. [#27070](https://github.com/openai/codex/pull/27070): add the
external-agent import picker UX
4. [#27071](https://github.com/openai/codex/pull/27071): expose the flow
through `/import`

**This PR is stack item 4.** Draft while the lower stack dependencies
are reviewed.
This commit is contained in:
stefanstokic-oai
2026-06-10 15:53:15 -04:00
committed by GitHub
Unverified
parent 1deae7bd4a
commit b4445f2758
22 changed files with 336 additions and 27 deletions
+8
View File
@@ -158,6 +158,7 @@ pub(crate) fn server_notification_requires_delivery(notification: &ServerNotific
ServerNotification::TurnCompleted(_)
| ServerNotification::ThreadSettingsUpdated(_)
| ServerNotification::ItemCompleted(_)
| ServerNotification::ExternalAgentConfigImportCompleted(_)
| ServerNotification::AgentMessageDelta(_)
| ServerNotification::PlanDelta(_)
| ServerNotification::ReasoningSummaryTextDelta(_)
@@ -2139,6 +2140,13 @@ mod tests {
)
)
));
assert!(event_requires_delivery(
&InProcessServerEvent::ServerNotification(
codex_app_server_protocol::ServerNotification::ExternalAgentConfigImportCompleted(
codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification {},
)
)
));
assert!(!event_requires_delivery(&InProcessServerEvent::Lagged {
skipped: 1
}));
+9 -1
View File
@@ -104,7 +104,9 @@ type PendingClientRequestResponse = std::result::Result<Result, JSONRPCErrorErro
fn server_notification_requires_delivery(notification: &ServerNotification) -> bool {
matches!(
notification,
ServerNotification::TurnCompleted(_) | ServerNotification::ThreadSettingsUpdated(_)
ServerNotification::TurnCompleted(_)
| ServerNotification::ThreadSettingsUpdated(_)
| ServerNotification::ExternalAgentConfigImportCompleted(_)
)
}
@@ -729,6 +731,7 @@ mod tests {
use super::*;
use codex_app_server_protocol::ClientInfo;
use codex_app_server_protocol::ConfigRequirementsReadResponse;
use codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification;
use codex_app_server_protocol::SessionSource as ApiSessionSource;
use codex_app_server_protocol::ThreadStartParams;
use codex_app_server_protocol::ThreadStartResponse;
@@ -893,5 +896,10 @@ mod tests {
},
})
));
assert!(server_notification_requires_delivery(
&ServerNotification::ExternalAgentConfigImportCompleted(
ExternalAgentConfigImportCompletedNotification {},
)
));
}
}
+8
View File
@@ -2600,6 +2600,14 @@ mod tests {
);
}
#[test]
fn import_remains_an_interactive_prompt() {
let cli = MultitoolCli::try_parse_from(["codex", "import"]).expect("parse");
assert!(cli.subcommand.is_none());
assert_eq!(cli.interactive.prompt.as_deref(), Some("import"));
}
#[test]
fn profile_v2_rejects_non_plain_names_at_parse_time() {
assert!(
+10 -1
View File
@@ -93,16 +93,25 @@ impl App {
return;
}
ServerNotification::ExternalAgentConfigImportCompleted(_) => {
let cwd = self.chat_widget.config_ref().cwd.to_path_buf();
let should_report_completion =
app_server_client.consume_external_agent_config_import_completion();
if let Err(err) = self.refresh_in_memory_config_from_disk().await {
tracing::warn!(
error = %err,
"failed to refresh config after external agent config import"
);
}
let cwd = self.chat_widget.config_ref().cwd.to_path_buf();
self.chat_widget.refresh_plugin_mentions();
self.chat_widget.submit_op(AppCommand::reload_user_config());
self.fetch_plugins_list(app_server_client, cwd);
if should_report_completion {
self.chat_widget.add_info_message(
crate::external_agent_config_migration_flow::EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE
.to_string(),
/*hint*/ None,
);
}
return;
}
ServerNotification::AppListUpdated(notification) => {
+26
View File
@@ -6,6 +6,7 @@
use super::resize_reflow::trailing_run_start;
use super::*;
use crate::config_update::format_config_error;
use crate::external_agent_config_migration_flow::ExternalAgentConfigMigrationFlowOutcome;
#[cfg(target_os = "windows")]
use codex_config::types::WindowsSandboxModeToml;
@@ -110,6 +111,31 @@ impl App {
// Leaving alt-screen may blank the inline viewport; force a redraw either way.
tui.frame_requester().schedule_frame();
}
AppEvent::OpenExternalAgentConfigMigration => {
match crate::external_agent_config_migration_flow::handle_external_agent_config_migration_prompt(
tui,
app_server,
&self.config,
)
.await
{
Ok(ExternalAgentConfigMigrationFlowOutcome::Started(message)) => {
self.chat_widget.add_info_message(message, /*hint*/ None);
}
Ok(ExternalAgentConfigMigrationFlowOutcome::NoItems) => {
self.chat_widget.add_info_message(
crate::external_agent_config_migration_flow::EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE
.to_string(),
/*hint*/ None,
);
}
Ok(ExternalAgentConfigMigrationFlowOutcome::Cancelled) => {}
Err(error_message) => {
self.chat_widget.add_error_message(error_message);
}
}
tui.frame_requester().schedule_frame();
}
AppEvent::ResumeSessionByIdOrName(id_or_name) => {
match crate::lookup_session_target_with_app_server(app_server, &id_or_name).await? {
Some(target_session) => {
+3
View File
@@ -221,6 +221,9 @@ pub(crate) enum AppEvent {
/// Open the resume picker inside the running TUI session.
OpenResumePicker,
/// Open the external agent migration picker inside the running TUI session.
OpenExternalAgentConfigMigration,
/// Resume a thread by UUID or thread name inside the running TUI session.
ResumeSessionByIdOrName(String),
+67
View File
@@ -22,6 +22,11 @@ use codex_app_server_protocol::AuthMode;
use codex_app_server_protocol::ClientRequest;
use codex_app_server_protocol::ConfigBatchWriteParams;
use codex_app_server_protocol::ConfigWriteResponse;
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
use codex_app_server_protocol::ExternalAgentConfigDetectResponse;
use codex_app_server_protocol::ExternalAgentConfigImportParams;
use codex_app_server_protocol::ExternalAgentConfigImportResponse;
use codex_app_server_protocol::ExternalAgentConfigMigrationItem;
use codex_app_server_protocol::GetAccountParams;
use codex_app_server_protocol::GetAccountRateLimitsResponse;
use codex_app_server_protocol::GetAccountResponse;
@@ -123,12 +128,16 @@ use color_eyre::eyre::Result;
use color_eyre::eyre::WrapErr;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::time::Duration;
use std::time::Instant;
use uuid::Uuid;
const JSONRPC_INVALID_REQUEST: i64 = -32600;
const JSONRPC_METHOD_NOT_FOUND: i64 = -32601;
pub(crate) const EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE: &str =
"A previous agent import is still running. Wait for it to finish before importing again.";
const THREAD_SETTINGS_UPDATE_METHOD: &str = "thread/settings/update";
fn bootstrap_request_error(context: &'static str, err: TypedRequestError) -> color_eyre::Report {
@@ -171,6 +180,7 @@ pub(crate) struct AppServerSession {
thread_settings_update_supported: bool,
default_model: Option<String>,
available_models: Vec<ModelPreset>,
external_agent_config_import_completion_pending: AtomicBool,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -214,6 +224,7 @@ impl AppServerSession {
thread_settings_update_supported: true,
default_model: None,
available_models: Vec::new(),
external_agent_config_import_completion_pending: AtomicBool::new(false),
}
}
@@ -230,6 +241,10 @@ impl AppServerSession {
matches!(self.thread_params_mode, ThreadParamsMode::Remote)
}
pub(crate) fn uses_embedded_app_server(&self) -> bool {
matches!(&self.client, AppServerClient::InProcess(_))
}
pub(crate) fn server_version(&self) -> Option<&str> {
let AppServerClient::Remote(client) = &self.client else {
return None;
@@ -344,6 +359,58 @@ impl AppServerSession {
.map_err(|err| bootstrap_request_error("account/read failed during TUI bootstrap", err))
}
pub(crate) async fn external_agent_config_detect(
&mut self,
params: ExternalAgentConfigDetectParams,
) -> Result<ExternalAgentConfigDetectResponse> {
let request_id = self.next_request_id();
self.client
.request_typed(ClientRequest::ExternalAgentConfigDetect { request_id, params })
.await
.wrap_err("externalAgentConfig/detect failed during agent import")
}
pub(crate) async fn external_agent_config_import(
&mut self,
migration_items: Vec<ExternalAgentConfigMigrationItem>,
) -> Result<()> {
// Mark the import active before sending the request so a fast completion notification
// cannot arrive before the TUI records it.
if self
.external_agent_config_import_completion_pending
.swap(true, Ordering::Relaxed)
{
color_eyre::eyre::bail!(EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE);
}
let request_id = self.next_request_id();
let response: Result<ExternalAgentConfigImportResponse> = self
.client
.request_typed(ClientRequest::ExternalAgentConfigImport {
request_id,
params: ExternalAgentConfigImportParams { migration_items },
})
.await
.wrap_err("externalAgentConfig/import failed during agent import");
match response {
Ok(_) => Ok(()),
Err(err) => {
self.external_agent_config_import_completion_pending
.store(false, Ordering::Relaxed);
Err(err)
}
}
}
pub(crate) fn external_agent_config_import_in_progress(&self) -> bool {
self.external_agent_config_import_completion_pending
.load(Ordering::Relaxed)
}
pub(crate) fn consume_external_agent_config_import_completion(&self) -> bool {
self.external_agent_config_import_completion_pending
.swap(false, Ordering::Relaxed)
}
pub(crate) async fn next_event(&mut self) -> Option<AppServerEvent> {
self.client.next_event().await
}
@@ -398,6 +398,10 @@ impl ChatWidget {
SlashCommand::Skills => {
self.open_skills_menu();
}
SlashCommand::Import => {
self.app_event_tx
.send(AppEvent::OpenExternalAgentConfigMigration);
}
SlashCommand::Hooks => {
self.add_hooks_output();
}
@@ -1010,6 +1014,7 @@ impl ChatWidget {
| SlashCommand::Logout
| SlashCommand::Mention
| SlashCommand::Skills
| SlashCommand::Import
| SlashCommand::Hooks
| SlashCommand::Title
| SlashCommand::Statusline
@@ -1861,6 +1861,18 @@ async fn slash_resume_opens_picker() {
assert_matches!(rx.try_recv(), Ok(AppEvent::OpenResumePicker));
}
#[tokio::test]
async fn slash_import_opens_claude_code_import_picker() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
chat.dispatch_command(SlashCommand::Import);
assert_matches!(
rx.try_recv(),
Ok(AppEvent::OpenExternalAgentConfigMigration)
);
}
#[tokio::test]
async fn slash_archive_confirmation_requests_current_thread_archive() {
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
@@ -758,7 +758,7 @@ mod tests {
},
ExternalAgentConfigMigrationItem {
item_type: ExternalAgentConfigMigrationItemType::Sessions,
description: "Migrate recent Claude Code sessions".to_string(),
description: "Migrate recent chat sessions".to_string(),
cwd: None,
details: Some(codex_app_server_protocol::MigrationDetails {
sessions: vec![SessionMigration {
@@ -80,13 +80,13 @@ impl WidgetRef for &ExternalAgentConfigMigrationScreen {
MigrationView::Summary => vec![
Line::from("Bring over your setup, current project, and recent chats."),
Line::from("Codex may add files to your current project folder."),
Line::from("Your existing Claude Code setup will not be changed."),
Line::from("Standard Claude Chat data cannot be imported."),
Line::from("Your existing agent setup will not be changed."),
Line::from("Cloud-hosted chat data cannot be imported."),
],
MigrationView::Customize => vec![
Line::from("Choose the Claude Code items to import."),
Line::from("Choose the items to import."),
Line::from("Codex may add files to your current project folder."),
Line::from("Your existing Claude Code setup will not be changed."),
Line::from("Your existing agent setup will not be changed."),
],
};
let intro_height = intro_lines.len() as u16;
@@ -119,7 +119,7 @@ impl WidgetRef for &ExternalAgentConfigMigrationScreen {
.areas(inner_area);
let title = match self.view {
MigrationView::Summary => "Import from Claude Code",
MigrationView::Summary => "Import from another coding agent",
MigrationView::Customize => "Choose what to import",
};
let heading = Line::from(vec!["> ".into(), title.bold()]);
@@ -0,0 +1,125 @@
use crate::app_server_session::AppServerSession;
use crate::app_server_session::EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE;
use crate::external_agent_config_migration::ExternalAgentConfigMigrationOutcome;
use crate::external_agent_config_migration::run_external_agent_config_migration_prompt;
use crate::legacy_core::config::Config;
use crate::tui;
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE: &str =
"Agent import finished. Run /import again to check for additional items.";
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE: &str =
"No supported agent setup was found to import.";
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE: &str =
"Agent import is unavailable in remote sessions. Start Codex locally and run /import.";
pub(crate) const EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE: &str = "Agent import is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.";
pub(crate) enum ExternalAgentConfigMigrationFlowOutcome {
Started(String),
NoItems,
Cancelled,
}
fn external_agent_config_migration_success_message(remaining_item_count: usize) -> String {
let message = "Agent import started. You can keep working while it finishes. Imported setup will apply to new chats.";
match remaining_items_handoff(remaining_item_count) {
Some(remaining_items_handoff) => format!("{message} {remaining_items_handoff}"),
None => message.to_string(),
}
}
fn remaining_items_handoff(remaining_item_count: usize) -> Option<String> {
match remaining_item_count {
0 => None,
1 => Some(
"1 additional item remains. After it finishes, run /import again to review it."
.to_string(),
),
_ => Some(format!(
"{remaining_item_count} additional items remain. After it finishes, run /import again to review them."
)),
}
}
pub(crate) async fn handle_external_agent_config_migration_prompt(
tui: &mut tui::Tui,
app_server: &mut AppServerSession,
config: &Config,
) -> Result<ExternalAgentConfigMigrationFlowOutcome, String> {
if app_server.uses_remote_workspace() {
return Err(EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE.to_string());
}
if !app_server.uses_embedded_app_server() {
return Err(EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE.to_string());
}
if app_server.external_agent_config_import_in_progress() {
return Err(EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE.to_string());
}
let cwd = config.cwd.to_path_buf();
let detected_items = match app_server
.external_agent_config_detect(ExternalAgentConfigDetectParams {
include_home: true,
cwds: Some(vec![cwd.clone()]),
})
.await
{
Ok(response) => response.items,
Err(err) => {
tracing::warn!(
error = %err,
cwd = %cwd.display(),
"failed to detect external agent config migrations"
);
return Err(format!("Could not check for agent setup: {err}"));
}
};
if detected_items.is_empty() {
return Ok(ExternalAgentConfigMigrationFlowOutcome::NoItems);
}
let mut selected_items = detected_items.clone();
let mut error: Option<String> = None;
loop {
match run_external_agent_config_migration_prompt(
tui,
&detected_items,
&selected_items,
error.as_deref(),
)
.await
{
ExternalAgentConfigMigrationOutcome::Proceed(items) => {
selected_items = items.clone();
match app_server.external_agent_config_import(items).await {
Ok(()) => {
let remaining_item_count =
detected_items.len().saturating_sub(selected_items.len());
let success_message =
external_agent_config_migration_success_message(remaining_item_count);
return Ok(ExternalAgentConfigMigrationFlowOutcome::Started(
success_message,
));
}
Err(err) => {
tracing::warn!(
error = %err,
cwd = %cwd.display(),
"failed to import external agent config migration items"
);
error = Some(format!("Import failed: {err}"));
}
}
}
ExternalAgentConfigMigrationOutcome::Skip => {
return Ok(ExternalAgentConfigMigrationFlowOutcome::Cancelled);
}
}
}
}
#[cfg(test)]
#[path = "external_agent_config_migration_flow_tests.rs"]
mod tests;
@@ -0,0 +1,21 @@
use super::*;
#[test]
fn external_agent_config_migration_messages_snapshot() {
let cases = [0, 1, 2];
let messages = cases
.map(external_agent_config_migration_success_message)
.into_iter()
.chain([
EXTERNAL_AGENT_CONFIG_MIGRATION_FINISHED_MESSAGE.to_string(),
EXTERNAL_AGENT_CONFIG_MIGRATION_NO_ITEMS_MESSAGE.to_string(),
EXTERNAL_AGENT_CONFIG_MIGRATION_REMOTE_UNAVAILABLE_MESSAGE.to_string(),
EXTERNAL_AGENT_CONFIG_MIGRATION_DAEMON_UNAVAILABLE_MESSAGE.to_string(),
EXTERNAL_AGENT_CONFIG_IMPORT_IN_PROGRESS_MESSAGE.to_string(),
])
.collect::<Vec<_>>()
.join("\n");
insta::assert_snapshot!("external_agent_config_migration_messages", messages);
}
+1 -1
View File
@@ -130,8 +130,8 @@ mod diff_model;
mod diff_render;
mod exec_cell;
mod exec_command;
#[allow(dead_code)]
mod external_agent_config_migration;
mod external_agent_config_migration_flow;
mod external_agent_config_migration_model;
mod external_editor;
mod file_search;
+5
View File
@@ -26,6 +26,7 @@ pub enum SlashCommand {
AutoReview,
Memories,
Skills,
Import,
Hooks,
Review,
Rename,
@@ -98,6 +99,9 @@ impl SlashCommand {
SlashCommand::Diff => "show git diff (including untracked files)",
SlashCommand::Mention => "mention a file",
SlashCommand::Skills => "use skills to improve how Codex performs specific tasks",
SlashCommand::Import => {
"import setup, this project, and recent chats from another coding agent"
}
SlashCommand::Hooks => "view and manage lifecycle hooks",
SlashCommand::Status => "show current session configuration and token usage",
SlashCommand::DebugConfig => "show config layers and requirement sources for debugging",
@@ -198,6 +202,7 @@ impl SlashCommand {
| SlashCommand::SandboxReadRoot
| SlashCommand::Experimental
| SlashCommand::Memories
| SlashCommand::Import
| SlashCommand::Review
| SlashCommand::Plan
| SlashCommand::Clear
@@ -4,14 +4,14 @@ expression: rendered
---
> Choose what to import
Choose the Claude Code items to import.
Choose the items to import.
Codex may add files to your current project folder.
Your existing Claude Code setup will not be changed.
Your existing agent setup will not be changed.
Home
[x] Settings (settings.json -> config.toml)
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
[x] Recent chat sessions
Import recent Claude Code sessions
Import recent chat sessions
1 chat session: Investigate migration UX
Current project: /workspace/project
@@ -4,14 +4,14 @@ expression: rendered
---
> Choose what to import
Choose the Claude Code items to import.
Choose the items to import.
Codex may add files to your current project folder.
Your existing Claude Code setup will not be changed.
Your existing agent setup will not be changed.
Home
[x] Settings (settings.json -> config.toml)
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
[x] Recent chat sessions
Import recent Claude Code sessions
Import recent chat sessions
1 chat session: Investigate migration UX
Current project: /workspace/project
@@ -4,14 +4,14 @@ expression: rendered
---
> Choose what to import
Choose the Claude Code items to import.
Choose the items to import.
Codex may add files to your current project folder.
Your existing Claude Code setup will not be changed.
Your existing agent setup will not be changed.
Home
[x] Settings (settings.json -> config.toml)
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
[x] Recent chat sessions
Import recent Claude Code sessions
Import recent chat sessions
1 chat session: Investigate migration UX
Current project: C:\workspace\project
@@ -4,14 +4,14 @@ expression: rendered
---
> Choose what to import
Choose the Claude Code items to import.
Choose the items to import.
Codex may add files to your current project folder.
Your existing Claude Code setup will not be changed.
Your existing agent setup will not be changed.
Home
[x] Settings (settings.json -> config.toml)
Import /Users/alex/.claude/settings.json into /Users/alex/.codex/conf…
[x] Recent chat sessions
Import recent Claude Code sessions
Import recent chat sessions
1 chat session: Investigate migration UX
Current project: C:\workspace\project
@@ -3,11 +3,11 @@ source: tui/src/external_agent_config_migration.rs
expression: rendered
---
> Import from Claude Code
> Import from another coding agent
Bring over your setup, current project, and recent chats.
Codex may add files to your current project folder.
Your existing Claude Code setup will not be changed.
Standard Claude Chat data cannot be imported.
Your existing agent setup will not be changed.
Cloud-hosted chat data cannot be imported.
[x] Tools & setup
Settings, instructions, integrations, agents, commands, and skills
[x] Current project
@@ -3,11 +3,11 @@ source: tui/src/external_agent_config_migration.rs
expression: rendered
---
> Import from Claude Code
> Import from another coding agent
Bring over your setup, current project, and recent chats.
Codex may add files to your current project folder.
Your existing Claude Code setup will not be changed.
Standard Claude Chat data cannot be imported.
Your existing agent setup will not be changed.
Cloud-hosted chat data cannot be imported.
[x] Tools & setup
Settings, instructions, integrations, agents, commands, and skills
[x] Current project
@@ -0,0 +1,12 @@
---
source: tui/src/external_agent_config_migration_flow_tests.rs
expression: messages
---
Agent import started. You can keep working while it finishes. Imported setup will apply to new chats.
Agent import started. You can keep working while it finishes. Imported setup will apply to new chats. 1 additional item remains. After it finishes, run /import again to review it.
Agent import started. You can keep working while it finishes. Imported setup will apply to new chats. 2 additional items remain. After it finishes, run /import again to review them.
Agent import finished. Run /import again to check for additional items.
No supported agent setup was found to import.
Agent import is unavailable in remote sessions. Start Codex locally and run /import.
Agent import is unavailable while Codex is connected to the local app-server daemon. Stop the daemon, restart Codex, and run /import.
A previous agent import is still running. Wait for it to finish before importing again.