Include session titles in external agent import history (#35870)

## What changed

- Preserve the original title when importing an external-agent session and return it with successful session entries from `externalAgentConfig/import/readHistories`.
- Add dedicated history-record success types so externally completed imports can optionally supply a session title without changing the live import result type.

## Testing

- Verify imported session titles are returned by the app server and persisted when session import ledger entries are created or refreshed.

GitOrigin-RevId: e896688a5763c238f1e1dc8b4672a7aa7bc1ed4c
This commit is contained in:
charlesgong-openai
2026-07-29 02:45:55 +00:00
committed by copyberry
parent 28f3f1f9ef
commit 9f23e97797
28 changed files with 465 additions and 130 deletions
+63 -55
View File
@@ -916,7 +916,7 @@
"itemTypeResults": {
"description": "Completed results grouped by imported item type.",
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
"$ref": "#/definitions/ExternalAgentConfigImportHistoryRecordTypeResultParams"
},
"type": "array"
},
@@ -931,6 +931,68 @@
],
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordSuccessParams": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session, when available.",
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordTypeResultParams": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportHistoryRecordSuccessParams"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
@@ -974,35 +1036,6 @@
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeSuccess": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportParams": {
"properties": {
"migrationItems": {
@@ -1038,31 +1071,6 @@
],
"type": "object"
},
"ExternalAgentConfigImportTypeResult": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigMigrationItem": {
"properties": {
"cwd": {
@@ -1275,6 +1275,14 @@
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session; null for other item types.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -9837,7 +9837,7 @@
"itemTypeResults": {
"description": "Completed results grouped by imported item type.",
"items": {
"$ref": "#/definitions/v2/ExternalAgentConfigImportTypeResult"
"$ref": "#/definitions/v2/ExternalAgentConfigImportHistoryRecordTypeResultParams"
},
"type": "array"
},
@@ -9866,6 +9866,68 @@
"title": "ExternalAgentConfigImportHistoryRecordResponse",
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordSuccessParams": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/v2/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session, when available.",
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordTypeResultParams": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/v2/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/v2/ExternalAgentConfigImportHistoryRecordSuccessParams"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
@@ -9931,6 +9993,14 @@
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session; null for other item types.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -6015,7 +6015,7 @@
"itemTypeResults": {
"description": "Completed results grouped by imported item type.",
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
"$ref": "#/definitions/ExternalAgentConfigImportHistoryRecordTypeResultParams"
},
"type": "array"
},
@@ -6044,6 +6044,68 @@
"title": "ExternalAgentConfigImportHistoryRecordResponse",
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordSuccessParams": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session, when available.",
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordTypeResultParams": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportHistoryRecordSuccessParams"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
@@ -6109,6 +6171,14 @@
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session; null for other item types.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -66,6 +66,14 @@
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session; null for other item types.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -102,6 +102,14 @@
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session; null for other item types.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -1,6 +1,68 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"definitions": {
"ExternalAgentConfigImportHistoryRecordSuccessParams": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session, when available.",
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportHistoryRecordTypeResultParams": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportHistoryRecordSuccessParams"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeFailure": {
"properties": {
"cwd": {
@@ -44,60 +106,6 @@
],
"type": "object"
},
"ExternalAgentConfigImportItemTypeSuccess": {
"properties": {
"cwd": {
"type": [
"string",
"null"
]
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"source": {
"type": [
"string",
"null"
]
},
"target": {
"type": [
"string",
"null"
]
}
},
"required": [
"itemType"
],
"type": "object"
},
"ExternalAgentConfigImportTypeResult": {
"properties": {
"failures": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure"
},
"type": "array"
},
"itemType": {
"$ref": "#/definitions/ExternalAgentConfigMigrationItemType"
},
"successes": {
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess"
},
"type": "array"
}
},
"required": [
"failures",
"itemType",
"successes"
],
"type": "object"
},
"ExternalAgentConfigMigrationItemType": {
"enum": [
"AGENTS_MD",
@@ -118,7 +126,7 @@
"itemTypeResults": {
"description": "Completed results grouped by imported item type.",
"items": {
"$ref": "#/definitions/ExternalAgentConfigImportTypeResult"
"$ref": "#/definitions/ExternalAgentConfigImportHistoryRecordTypeResultParams"
},
"type": "array"
},
@@ -66,6 +66,14 @@
"string",
"null"
]
},
"title": {
"default": null,
"description": "Original title for an imported session; null for other item types.",
"type": [
"string",
"null"
]
}
},
"required": [
@@ -1,7 +1,7 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExternalAgentConfigImportTypeResult } from "./ExternalAgentConfigImportTypeResult";
import type { ExternalAgentConfigImportHistoryRecordTypeResultParams } from "./ExternalAgentConfigImportHistoryRecordTypeResultParams";
export type ExternalAgentConfigImportHistoryRecordParams = {
/**
@@ -11,4 +11,4 @@ providerId: string,
/**
* Completed results grouped by imported item type.
*/
itemTypeResults: Array<ExternalAgentConfigImportTypeResult>, };
itemTypeResults: Array<ExternalAgentConfigImportHistoryRecordTypeResultParams>, };
@@ -0,0 +1,10 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExternalAgentConfigMigrationItemType } from "./ExternalAgentConfigMigrationItemType";
export type ExternalAgentConfigImportHistoryRecordSuccessParams = { itemType: ExternalAgentConfigMigrationItemType, cwd: string | null, source: string | null, target: string | null,
/**
* Original title for an imported session, when available.
*/
title?: string | null, };
@@ -0,0 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExternalAgentConfigImportHistoryRecordSuccessParams } from "./ExternalAgentConfigImportHistoryRecordSuccessParams";
import type { ExternalAgentConfigImportItemTypeFailure } from "./ExternalAgentConfigImportItemTypeFailure";
import type { ExternalAgentConfigMigrationItemType } from "./ExternalAgentConfigMigrationItemType";
export type ExternalAgentConfigImportHistoryRecordTypeResultParams = { itemType: ExternalAgentConfigMigrationItemType, successes: Array<ExternalAgentConfigImportHistoryRecordSuccessParams>, failures: Array<ExternalAgentConfigImportItemTypeFailure>, };
@@ -3,4 +3,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ExternalAgentConfigMigrationItemType } from "./ExternalAgentConfigMigrationItemType";
export type ExternalAgentConfigImportItemTypeSuccess = { itemType: ExternalAgentConfigMigrationItemType, cwd: string | null, source: string | null, target: string | null, };
export type ExternalAgentConfigImportItemTypeSuccess = { itemType: ExternalAgentConfigMigrationItemType, cwd: string | null, source: string | null, target: string | null,
/**
* Original title for an imported session; null for other item types.
*/
title: string | null, };
@@ -122,6 +122,8 @@ export type { ExternalAgentConfigImportHistoriesReadResponse } from "./ExternalA
export type { ExternalAgentConfigImportHistory } from "./ExternalAgentConfigImportHistory";
export type { ExternalAgentConfigImportHistoryRecordParams } from "./ExternalAgentConfigImportHistoryRecordParams";
export type { ExternalAgentConfigImportHistoryRecordResponse } from "./ExternalAgentConfigImportHistoryRecordResponse";
export type { ExternalAgentConfigImportHistoryRecordSuccessParams } from "./ExternalAgentConfigImportHistoryRecordSuccessParams";
export type { ExternalAgentConfigImportHistoryRecordTypeResultParams } from "./ExternalAgentConfigImportHistoryRecordTypeResultParams";
export type { ExternalAgentConfigImportItemTypeFailure } from "./ExternalAgentConfigImportItemTypeFailure";
export type { ExternalAgentConfigImportItemTypeSuccess } from "./ExternalAgentConfigImportItemTypeSuccess";
export type { ExternalAgentConfigImportParams } from "./ExternalAgentConfigImportParams";
@@ -787,6 +787,9 @@ pub struct ExternalAgentConfigImportItemTypeSuccess {
pub cwd: Option<PathBuf>,
pub source: Option<String>,
pub target: Option<String>,
/// Original title for an imported session; null for other item types.
#[serde(default)]
pub title: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@@ -798,6 +801,29 @@ pub struct ExternalAgentConfigImportTypeResult {
pub failures: Vec<ExternalAgentConfigImportItemTypeFailure>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigImportHistoryRecordSuccessParams {
pub item_type: ExternalAgentConfigMigrationItemType,
pub cwd: Option<PathBuf>,
pub source: Option<String>,
pub target: Option<String>,
/// Original title for an imported session, when available.
#[serde(default)]
#[ts(optional = nullable)]
pub title: Option<String>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ExternalAgentConfigImportHistoryRecordTypeResultParams {
pub item_type: ExternalAgentConfigMigrationItemType,
pub successes: Vec<ExternalAgentConfigImportHistoryRecordSuccessParams>,
pub failures: Vec<ExternalAgentConfigImportItemTypeFailure>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@@ -805,7 +831,7 @@ pub struct ExternalAgentConfigImportHistoryRecordParams {
/// Opaque provider identifier for the externally completed import.
pub provider_id: String,
/// Completed results grouped by imported item type.
pub item_type_results: Vec<ExternalAgentConfigImportTypeResult>,
pub item_type_results: Vec<ExternalAgentConfigImportHistoryRecordTypeResultParams>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
+1 -1
View File
@@ -248,7 +248,7 @@ Example with notification opt-out:
- `config/read` — fetch the runtime-effective config after resolving config layering and managed requirements, including opaque `desktop` values stored in `config.toml`.
- `externalAgentConfig/detect` — detect migratable external-agent artifacts with `includeHome`, optional `cwds`, and an optional `migrationSource` selector. Omitted, `null`, or unrecognized migration-source values retain the default behavior. The deprecated optional `source` field remains accepted for compatibility but does not select the migration source. Each detected item includes `cwd` (`null` for home), and multi-item migrations may additionally include structured `details` with plugin ids, skill names, memory, 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. Pass the same optional `migrationSource` used for detection so the server reads from the matching source; omitted, `null`, or unrecognized values retain the default behavior. The optional `source` identifies the product that initiated the import, while the optional opaque `providerId` attributes analytics to the provider selected by that product without affecting migration-source selection. 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.
- `externalAgentConfig/import/readHistories` — read completed import histories and connector candidates detected from successfully imported session histories. Successful session entries include the original imported title when one was available. 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. Writes that overlap a managed requirement are rejected with `configRequirementReadonly`.
- `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. Session-static model, reasoning-effort, Plan-mode reasoning-effort, service-tier, and personality defaults do not reload existing threads.
- `configRequirements/read` — fetch loaded requirements constraints from `requirements.toml` and/or MDM (or `null` if none are configured), including exact managed values (`sqliteHome`, `logDir`, `modelCatalogJson`, `checkForUpdateOnStartup`, `allowLoginShell`, `feedback.enabled`, and `windowsSandboxPrivateDesktop`), 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`), Browser Use policy (`browserUse.disableAutoReview`), pinned feature values (`featureRequirements`, including the default-allowed `in_app_updates` policy that administrators can set to `false`), managed lifecycle hooks (`hooks`, including each command handler's optional `additionalContextLimit`), `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`.
@@ -17,6 +17,7 @@ use codex_app_server_protocol::ExternalAgentConfigImportHistoriesReadResponse;
use codex_app_server_protocol::ExternalAgentConfigImportHistoryRecordParams;
use codex_app_server_protocol::ExternalAgentConfigImportHistoryRecordResponse;
use codex_app_server_protocol::ExternalAgentConfigImportItemTypeFailure as ProtocolImportFailure;
use codex_app_server_protocol::ExternalAgentConfigImportItemTypeSuccess as ProtocolImportSuccess;
use codex_app_server_protocol::ExternalAgentConfigImportParams;
use codex_app_server_protocol::ExternalAgentConfigImportProgressNotification;
use codex_app_server_protocol::ExternalAgentConfigImportResponse;
@@ -390,11 +391,30 @@ impl ExternalAgentConfigRequestProcessor {
.as_ref()
.ok_or_else(|| internal_error("state database is unavailable"))?;
let import_id = Uuid::new_v4().to_string();
let item_type_results = params
.item_type_results
.into_iter()
.map(|type_result| ProtocolImportTypeResult {
item_type: type_result.item_type,
successes: type_result
.successes
.into_iter()
.map(|success| ProtocolImportSuccess {
item_type: success.item_type,
cwd: success.cwd,
source: success.source,
target: success.target,
title: success.title,
})
.collect(),
failures: type_result.failures,
})
.collect::<Vec<_>>();
record_import_history(
state_db,
import_id.as_str(),
Some(params.provider_id.as_str()),
&params.item_type_results,
&item_type_results,
)
.await
.map_err(|err| internal_error(format!("failed to record import history: {err}")))?;
@@ -639,6 +659,7 @@ async fn record_import_history(
cwd: success.cwd.clone(),
source: success.source.clone(),
target: success.target.clone(),
title: success.title.clone(),
})
})
.collect::<anyhow::Result<Vec<_>>>()?;
@@ -672,7 +693,11 @@ fn apply_plugin_outcome_to_item_result(
plugin_outcome: PluginImportOutcome,
) {
for plugin_id in plugin_outcome.succeeded_plugin_ids {
item_result.record_success(Some(plugin_id.clone()), Some(plugin_id));
item_result.record_success(
Some(plugin_id.clone()),
Some(plugin_id),
/*title*/ None,
);
}
for raw_error in plugin_outcome.raw_errors {
item_result.record_error(raw_error);
@@ -225,6 +225,7 @@ fn protocol_import_success_record(
cwd: record.cwd,
source: record.source,
target: record.target,
title: record.title,
})
}
@@ -326,6 +327,7 @@ fn protocol_import_success(success: &ExternalAgentConfigImportSuccess) -> Protoc
cwd: success.cwd.clone(),
source: success.source.clone(),
target: success.target.clone(),
title: success.title.clone(),
}
}
@@ -113,6 +113,7 @@ impl ExternalAgentSessionImporter {
item_result.record_success(
Some(completed_import.import.source_path.display().to_string()),
Some(completed_import.import.imported_thread_id.to_string()),
completed_import.import.title.clone(),
);
completed_imports.push(completed_import);
}
@@ -211,6 +212,7 @@ impl ExternalAgentSessionImporter {
session_id: session_id.to_string(),
server_ids: pending_import.attributed_mcp_server_ids,
});
let title = pending_import.session.title.clone();
let imported_thread_id =
self.persist_session(pending_import.session)
.await
@@ -226,6 +228,7 @@ impl ExternalAgentSessionImporter {
source_content_sha256: pending_import.source_content_sha256,
imported_thread_id,
connector_names: Vec::new(),
title,
},
connector_attribution,
}))
@@ -654,6 +654,7 @@ async fn external_agent_config_records_externally_completed_import_history() ->
"cwd": "/repo",
"source": "/source/session.jsonl",
"target": "thread-1",
"title": null,
}])
);
assert_eq!(entry.failures, Vec::new());
@@ -1655,6 +1656,13 @@ async fn external_agent_config_import_creates_session_rollouts() -> Result<()> {
source: ExternalAgentImportedConnectorSource::RemoteMcpServersConfig,
}]
);
let imported_session = response
.data
.iter()
.flat_map(|history| history.successes.iter())
.find(|success| success.item_type == ExternalAgentConfigMigrationItemType::Sessions)
.expect("imported session history should be available");
assert_eq!(imported_session.title.as_deref(), Some("Fix auth flow"));
let request_id = mcp
.send_thread_list_request(ThreadListParams {
@@ -122,13 +122,19 @@ impl ExternalAgentConfigImportItemResult {
self.raw_errors.push(raw_error);
}
pub fn record_success(&mut self, source: Option<String>, target: Option<String>) {
pub fn record_success(
&mut self,
source: Option<String>,
target: Option<String>,
title: Option<String>,
) {
self.success_count = self.success_count.saturating_add(1);
self.successes.push(ExternalAgentConfigImportSuccess {
item_type: self.item_type,
cwd: self.cwd.clone(),
source,
target,
title,
});
}
}
@@ -139,6 +145,7 @@ pub struct ExternalAgentConfigImportSuccess {
pub cwd: Option<PathBuf>,
pub source: Option<String>,
pub target: Option<String>,
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -173,7 +173,7 @@ impl ExternalAgentConfigService {
if let Some((source, target)) =
self.import_config(migration_item.cwd.as_deref())?
{
item_result.record_success(Some(source), Some(target));
item_result.record_success(Some(source), Some(target), /*title*/ None);
}
emit_migration_metric(
EXTERNAL_AGENT_CONFIG_IMPORT_METRIC,
@@ -190,7 +190,11 @@ impl ExternalAgentConfigService {
Some(imported_skills.len()),
);
for skill_name in imported_skills {
item_result.record_success(Some(skill_name.clone()), Some(skill_name));
item_result.record_success(
Some(skill_name.clone()),
Some(skill_name),
/*title*/ None,
);
}
Ok(())
})(),
@@ -198,7 +202,7 @@ impl ExternalAgentConfigService {
if let Some((source, target)) =
self.import_agents_md(migration_item.cwd.as_deref())?
{
item_result.record_success(Some(source), Some(target));
item_result.record_success(Some(source), Some(target), /*title*/ None);
}
emit_migration_metric(
EXTERNAL_AGENT_CONFIG_IMPORT_METRIC,
@@ -260,8 +264,11 @@ impl ExternalAgentConfigService {
}
};
for plugin_id in plugin_outcome.succeeded_plugin_ids {
item_result
.record_success(Some(plugin_id.clone()), Some(plugin_id));
item_result.record_success(
Some(plugin_id.clone()),
Some(plugin_id),
/*title*/ None,
);
}
for raw_error in plugin_outcome.raw_errors {
item_result.record_error(raw_error);
@@ -292,7 +299,11 @@ impl ExternalAgentConfigService {
/*skills_count*/ None,
);
for server_name in migrated_server_names {
item_result.record_success(Some(server_name.clone()), Some(server_name));
item_result.record_success(
Some(server_name.clone()),
Some(server_name),
/*title*/ None,
);
}
Ok(())
})(),
@@ -305,8 +316,11 @@ impl ExternalAgentConfigService {
Some(imported_subagents.len()),
);
for subagent_name in imported_subagents {
item_result
.record_success(Some(subagent_name.clone()), Some(subagent_name));
item_result.record_success(
Some(subagent_name.clone()),
Some(subagent_name),
/*title*/ None,
);
}
Ok(())
})(),
@@ -318,7 +332,11 @@ impl ExternalAgentConfigService {
/*skills_count*/ None,
);
for hook_name in migrated_hook_names {
item_result.record_success(Some(hook_name.clone()), Some(hook_name));
item_result.record_success(
Some(hook_name.clone()),
Some(hook_name),
/*title*/ None,
);
}
Ok(())
})(),
@@ -330,7 +348,11 @@ impl ExternalAgentConfigService {
Some(imported_commands.len()),
);
for command_name in imported_commands {
item_result.record_success(Some(command_name.clone()), Some(command_name));
item_result.record_success(
Some(command_name.clone()),
Some(command_name),
/*title*/ None,
);
}
Ok(())
})(),
@@ -358,6 +380,7 @@ impl ExternalAgentConfigService {
item_result.record_success(
Some(project_key),
Some(target_path.display().to_string()),
/*title*/ None,
);
}
for failure in memory_outcome.failures {
@@ -66,6 +66,7 @@ fn import_success(
cwd,
source: Some(source.into()),
target: Some(target.into()),
title: None,
}
}
@@ -390,6 +390,7 @@ async fn import_local_plugins_returns_completed_status() {
cwd: None,
source: Some("cloudflare@my-plugins".to_string()),
target: Some("cloudflare@my-plugins".to_string()),
title: None,
}],
raw_errors: Vec::new(),
}]
@@ -31,6 +31,8 @@ struct ImportedExternalAgentSessionRecord {
source_modified_at: Option<i64>,
#[serde(default)]
connector_names: Vec<String>,
#[serde(default)]
title: Option<String>,
}
#[derive(Debug, PartialEq, Eq)]
@@ -39,6 +41,7 @@ pub struct CompletedExternalAgentSessionImport {
pub source_content_sha256: String,
pub imported_thread_id: ThreadId,
pub connector_names: Vec<String>,
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
@@ -74,6 +77,7 @@ pub(crate) fn record_imported_session(
source_path,
imported_thread_id,
connector_names: Vec::new(),
title: None,
}],
)
}
@@ -98,6 +102,7 @@ pub fn record_completed_session_imports(
record.imported_at = imported_at;
record.source_modified_at = source_modified_at.or(record.source_modified_at);
record.connector_names = import.connector_names;
record.title = import.title;
ledger.records.push(record);
continue;
}
@@ -108,6 +113,7 @@ pub fn record_completed_session_imports(
imported_at,
source_modified_at,
connector_names: import.connector_names,
title: import.title,
});
}
save_import_ledger(codex_home, &ledger)
@@ -38,6 +38,7 @@ fn completed_imports_do_not_read_source_files() {
source_content_sha256: format!("{:x}", Sha256::digest(contents)),
imported_thread_id,
connector_names: Vec::new(),
title: Some("Imported session".to_string()),
}],
)
.expect("record completed imports");
@@ -47,6 +48,7 @@ fn completed_imports_do_not_read_source_files() {
assert_eq!(ledger.records[0].source_path, source_path);
assert_eq!(ledger.records[0].imported_thread_id, imported_thread_id);
assert_eq!(ledger.records[0].source_modified_at, None);
assert_eq!(ledger.records[0].title.as_deref(), Some("Imported session"));
}
#[test]
@@ -68,6 +70,7 @@ fn completed_import_refreshes_existing_record_metadata() {
source_content_sha256: content_sha256.clone(),
imported_thread_id: first_thread_id,
connector_names: vec!["Gmail".to_string()],
title: Some("First title".to_string()),
}],
)
.expect("record first import");
@@ -78,6 +81,7 @@ fn completed_import_refreshes_existing_record_metadata() {
source_content_sha256: content_sha256,
imported_thread_id: second_thread_id,
connector_names: vec!["Slack".to_string()],
title: Some("Second title".to_string()),
}],
)
.expect("record replacement import");
@@ -88,6 +92,7 @@ fn completed_import_refreshes_existing_record_metadata() {
assert_eq!(ledger.records[0].imported_thread_id, second_thread_id);
assert!(ledger.records[0].source_modified_at.is_some());
assert_eq!(ledger.records[0].connector_names, vec!["Slack"]);
assert_eq!(ledger.records[0].title.as_deref(), Some("Second title"));
}
#[test]
@@ -105,18 +110,21 @@ fn connector_candidates_use_latest_import_for_each_source() {
source_content_sha256: "first-version".to_string(),
imported_thread_id: ThreadId::new(),
connector_names: vec!["Gmail".to_string()],
title: None,
},
CompletedExternalAgentSessionImport {
source_path: first_source,
source_content_sha256: "second-version".to_string(),
imported_thread_id: ThreadId::new(),
connector_names: vec!["Slack".to_string()],
title: None,
},
CompletedExternalAgentSessionImport {
source_path: second_source,
source_content_sha256: "only-version".to_string(),
imported_thread_id: ThreadId::new(),
connector_names: vec!["Gmail".to_string(), "Slack".to_string()],
title: None,
},
],
)
@@ -12,6 +12,8 @@ pub struct ExternalAgentConfigImportSuccessRecord {
pub cwd: Option<PathBuf>,
pub source: Option<String>,
pub target: Option<String>,
#[serde(default)]
pub title: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
@@ -20,6 +20,7 @@ async fn records_completion_by_import_id() -> anyhow::Result<()> {
cwd: None,
source: Some("settings.json".to_string()),
target: Some("config.toml".to_string()),
title: None,
}],
&[],
)
@@ -34,12 +35,14 @@ async fn records_completion_by_import_id() -> anyhow::Result<()> {
cwd: None,
source: Some("settings.json".to_string()),
target: Some("config.toml".to_string()),
title: None,
},
ExternalAgentConfigImportSuccessRecord {
item_type: "MCP_SERVER_CONFIG".to_string(),
cwd: None,
source: Some("github".to_string()),
target: Some("github".to_string()),
title: None,
},
],
&[ExternalAgentConfigImportFailureRecord {
@@ -65,12 +68,14 @@ async fn records_completion_by_import_id() -> anyhow::Result<()> {
cwd: None,
source: Some("settings.json".to_string()),
target: Some("config.toml".to_string()),
title: None,
},
ExternalAgentConfigImportSuccessRecord {
item_type: "MCP_SERVER_CONFIG".to_string(),
cwd: None,
source: Some("github".to_string()),
target: Some("github".to_string()),
title: None,
}
],
failures: vec![ExternalAgentConfigImportFailureRecord {
@@ -106,12 +111,14 @@ async fn records_completion_by_import_id() -> anyhow::Result<()> {
cwd: None,
source: Some("settings.json".to_string()),
target: Some("config.toml".to_string()),
title: None,
},
ExternalAgentConfigImportSuccessRecord {
item_type: "MCP_SERVER_CONFIG".to_string(),
cwd: None,
source: Some("github".to_string()),
target: Some("github".to_string()),
title: None,
}
],
vec![ExternalAgentConfigImportFailureRecord {
@@ -127,6 +127,7 @@ fn completed_notification() -> ExternalAgentConfigImportCompletedNotification {
cwd: None,
source: Some("settings.json".to_string()),
target: Some("config.toml".to_string()),
title: None,
}],
failures: Vec::new(),
},
@@ -138,12 +139,14 @@ fn completed_notification() -> ExternalAgentConfigImportCompletedNotification {
cwd: None,
source: Some("project-a".to_string()),
target: Some("memory resources".to_string()),
title: None,
},
ExternalAgentConfigImportItemTypeSuccess {
item_type: ExternalAgentConfigMigrationItemType::Memory,
cwd: None,
source: Some("project-b".to_string()),
target: Some("memory resources".to_string()),
title: None,
},
],
failures: Vec::new(),
@@ -155,6 +158,7 @@ fn completed_notification() -> ExternalAgentConfigImportCompletedNotification {
cwd: None,
source: Some("formatter@example".to_string()),
target: Some("formatter@example".to_string()),
title: None,
}],
failures: vec![ExternalAgentConfigImportItemTypeFailure {
item_type: ExternalAgentConfigMigrationItemType::Plugins,