diff --git a/codex-rs/app-server-protocol/schema/json/ClientRequest.json b/codex-rs/app-server-protocol/schema/json/ClientRequest.json index 169ed7e13..3ed2791c8 100644 --- a/codex-rs/app-server-protocol/schema/json/ClientRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ClientRequest.json @@ -6620,6 +6620,29 @@ "title": "ExternalAgentConfig/importRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "externalAgentConfig/import/readHistories" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequest", + "type": "object" + }, { "properties": { "id": { diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 21ff4a6de..1cf3d96c8 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -1167,6 +1167,12 @@ "null" ] }, + "errorType": { + "type": [ + "string", + "null" + ] + }, "failureStage": { "type": "string" }, @@ -1219,6 +1225,24 @@ ], "type": "object" }, + "ExternalAgentConfigImportProgressNotification": { + "properties": { + "importId": { + "type": "string" + }, + "itemTypeResults": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportTypeResult" + }, + "type": "array" + } + }, + "required": [ + "importId", + "itemTypeResults" + ], + "type": "object" + }, "ExternalAgentConfigImportTypeResult": { "properties": { "failures": { @@ -6333,6 +6357,26 @@ "title": "RemoteControl/status/changedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "externalAgentConfig/import/progress" + ], + "title": "ExternalAgentConfig/import/progressNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExternalAgentConfigImportProgressNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ExternalAgentConfig/import/progressNotification", + "type": "object" + }, { "properties": { "method": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index 7e431cea8..34046d224 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -2124,6 +2124,29 @@ "title": "ExternalAgentConfig/importRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/v2/RequestId" + }, + "method": { + "enum": [ + "externalAgentConfig/import/readHistories" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequest", + "type": "object" + }, { "properties": { "id": { @@ -4834,6 +4857,26 @@ "title": "RemoteControl/status/changedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "externalAgentConfig/import/progress" + ], + "title": "ExternalAgentConfig/import/progressNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportProgressNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ExternalAgentConfig/import/progressNotification", + "type": "object" + }, { "properties": { "method": { @@ -9089,6 +9132,52 @@ "title": "ExternalAgentConfigImportCompletedNotification", "type": "object" }, + "ExternalAgentConfigImportHistoriesReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportHistory" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "ExternalAgentConfigImportHistoriesReadResponse", + "type": "object" + }, + "ExternalAgentConfigImportHistory": { + "properties": { + "completedAtMs": { + "format": "int64", + "type": "integer" + }, + "failures": { + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeFailure" + }, + "type": "array" + }, + "importId": { + "type": "string" + }, + "successes": { + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportItemTypeSuccess" + }, + "type": "array" + } + }, + "required": [ + "completedAtMs", + "failures", + "importId", + "successes" + ], + "type": "object" + }, "ExternalAgentConfigImportItemTypeFailure": { "properties": { "cwd": { @@ -9097,6 +9186,12 @@ "null" ] }, + "errorType": { + "type": [ + "string", + "null" + ] + }, "failureStage": { "type": "string" }, @@ -9165,6 +9260,26 @@ "title": "ExternalAgentConfigImportParams", "type": "object" }, + "ExternalAgentConfigImportProgressNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "importId": { + "type": "string" + }, + "itemTypeResults": { + "items": { + "$ref": "#/definitions/v2/ExternalAgentConfigImportTypeResult" + }, + "type": "array" + } + }, + "required": [ + "importId", + "itemTypeResults" + ], + "title": "ExternalAgentConfigImportProgressNotification", + "type": "object" + }, "ExternalAgentConfigImportResponse": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 2733bc271..f3f6fb323 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -3111,6 +3111,29 @@ "title": "ExternalAgentConfig/importRequest", "type": "object" }, + { + "properties": { + "id": { + "$ref": "#/definitions/RequestId" + }, + "method": { + "enum": [ + "externalAgentConfig/import/readHistories" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequestMethod", + "type": "string" + }, + "params": { + "type": "null" + } + }, + "required": [ + "id", + "method" + ], + "title": "ExternalAgentConfig/import/readHistoriesRequest", + "type": "object" + }, { "properties": { "id": { @@ -5402,6 +5425,52 @@ "title": "ExternalAgentConfigImportCompletedNotification", "type": "object" }, + "ExternalAgentConfigImportHistoriesReadResponse": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "data": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportHistory" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "ExternalAgentConfigImportHistoriesReadResponse", + "type": "object" + }, + "ExternalAgentConfigImportHistory": { + "properties": { + "completedAtMs": { + "format": "int64", + "type": "integer" + }, + "failures": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure" + }, + "type": "array" + }, + "importId": { + "type": "string" + }, + "successes": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess" + }, + "type": "array" + } + }, + "required": [ + "completedAtMs", + "failures", + "importId", + "successes" + ], + "type": "object" + }, "ExternalAgentConfigImportItemTypeFailure": { "properties": { "cwd": { @@ -5410,6 +5479,12 @@ "null" ] }, + "errorType": { + "type": [ + "string", + "null" + ] + }, "failureStage": { "type": "string" }, @@ -5478,6 +5553,26 @@ "title": "ExternalAgentConfigImportParams", "type": "object" }, + "ExternalAgentConfigImportProgressNotification": { + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "importId": { + "type": "string" + }, + "itemTypeResults": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportTypeResult" + }, + "type": "array" + } + }, + "required": [ + "importId", + "itemTypeResults" + ], + "title": "ExternalAgentConfigImportProgressNotification", + "type": "object" + }, "ExternalAgentConfigImportResponse": { "$schema": "http://json-schema.org/draft-07/schema#", "properties": { @@ -13011,6 +13106,26 @@ "title": "RemoteControl/status/changedNotification", "type": "object" }, + { + "properties": { + "method": { + "enum": [ + "externalAgentConfig/import/progress" + ], + "title": "ExternalAgentConfig/import/progressNotificationMethod", + "type": "string" + }, + "params": { + "$ref": "#/definitions/ExternalAgentConfigImportProgressNotification" + } + }, + "required": [ + "method", + "params" + ], + "title": "ExternalAgentConfig/import/progressNotification", + "type": "object" + }, { "properties": { "method": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportCompletedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportCompletedNotification.json index 2b65371c6..51d0e9593 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportCompletedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportCompletedNotification.json @@ -9,6 +9,12 @@ "null" ] }, + "errorType": { + "type": [ + "string", + "null" + ] + }, "failureStage": { "type": "string" }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportHistoriesReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportHistoriesReadResponse.json new file mode 100644 index 000000000..68921f6b2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportHistoriesReadResponse.json @@ -0,0 +1,128 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ExternalAgentConfigImportHistory": { + "properties": { + "completedAtMs": { + "format": "int64", + "type": "integer" + }, + "failures": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportItemTypeFailure" + }, + "type": "array" + }, + "importId": { + "type": "string" + }, + "successes": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportItemTypeSuccess" + }, + "type": "array" + } + }, + "required": [ + "completedAtMs", + "failures", + "importId", + "successes" + ], + "type": "object" + }, + "ExternalAgentConfigImportItemTypeFailure": { + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "errorType": { + "type": [ + "string", + "null" + ] + }, + "failureStage": { + "type": "string" + }, + "itemType": { + "$ref": "#/definitions/ExternalAgentConfigMigrationItemType" + }, + "message": { + "type": "string" + }, + "source": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "failureStage", + "itemType", + "message" + ], + "type": "object" + }, + "ExternalAgentConfigImportItemTypeSuccess": { + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "itemType": { + "$ref": "#/definitions/ExternalAgentConfigMigrationItemType" + }, + "source": { + "type": [ + "string", + "null" + ] + }, + "target": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "itemType" + ], + "type": "object" + }, + "ExternalAgentConfigMigrationItemType": { + "enum": [ + "AGENTS_MD", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "SESSIONS" + ], + "type": "string" + } + }, + "properties": { + "data": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportHistory" + }, + "type": "array" + } + }, + "required": [ + "data" + ], + "title": "ExternalAgentConfigImportHistoriesReadResponse", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportProgressNotification.json b/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportProgressNotification.json new file mode 100644 index 000000000..32975b2c2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/json/v2/ExternalAgentConfigImportProgressNotification.json @@ -0,0 +1,127 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "definitions": { + "ExternalAgentConfigImportItemTypeFailure": { + "properties": { + "cwd": { + "type": [ + "string", + "null" + ] + }, + "errorType": { + "type": [ + "string", + "null" + ] + }, + "failureStage": { + "type": "string" + }, + "itemType": { + "$ref": "#/definitions/ExternalAgentConfigMigrationItemType" + }, + "message": { + "type": "string" + }, + "source": { + "type": [ + "string", + "null" + ] + } + }, + "required": [ + "failureStage", + "itemType", + "message" + ], + "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", + "CONFIG", + "SKILLS", + "PLUGINS", + "MCP_SERVER_CONFIG", + "SUBAGENTS", + "HOOKS", + "COMMANDS", + "SESSIONS" + ], + "type": "string" + } + }, + "properties": { + "importId": { + "type": "string" + }, + "itemTypeResults": { + "items": { + "$ref": "#/definitions/ExternalAgentConfigImportTypeResult" + }, + "type": "array" + } + }, + "required": [ + "importId", + "itemTypeResults" + ], + "title": "ExternalAgentConfigImportProgressNotification", + "type": "object" +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts index ebc96f65d..e014cc98b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ClientRequest.ts @@ -88,4 +88,4 @@ import type { WindowsSandboxSetupStartParams } from "./v2/WindowsSandboxSetupSta /** * Request from the client to the server. */ -export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/delete", id: RequestId, params: ThreadDeleteParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/goal/set", id: RequestId, params: ThreadGoalSetParams, } | { "method": "thread/goal/get", id: RequestId, params: ThreadGoalGetParams, } | { "method": "thread/goal/clear", id: RequestId, params: ThreadGoalClearParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/extraRoots/set", id: RequestId, params: SkillsExtraRootsSetParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/installed", id: RequestId, params: PluginInstalledParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/updateTargets", id: RequestId, params: PluginShareUpdateTargetsParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/checkout", id: RequestId, params: PluginShareCheckoutParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "permissionProfile/list", id: RequestId, params: PermissionProfileListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/rateLimitResetCredit/consume", id: RequestId, params: ConsumeAccountRateLimitResetCreditParams, } | { "method": "account/usage/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; +export type ClientRequest ={ "method": "initialize", id: RequestId, params: InitializeParams, } | { "method": "thread/start", id: RequestId, params: ThreadStartParams, } | { "method": "thread/resume", id: RequestId, params: ThreadResumeParams, } | { "method": "thread/fork", id: RequestId, params: ThreadForkParams, } | { "method": "thread/archive", id: RequestId, params: ThreadArchiveParams, } | { "method": "thread/delete", id: RequestId, params: ThreadDeleteParams, } | { "method": "thread/unsubscribe", id: RequestId, params: ThreadUnsubscribeParams, } | { "method": "thread/name/set", id: RequestId, params: ThreadSetNameParams, } | { "method": "thread/goal/set", id: RequestId, params: ThreadGoalSetParams, } | { "method": "thread/goal/get", id: RequestId, params: ThreadGoalGetParams, } | { "method": "thread/goal/clear", id: RequestId, params: ThreadGoalClearParams, } | { "method": "thread/metadata/update", id: RequestId, params: ThreadMetadataUpdateParams, } | { "method": "thread/unarchive", id: RequestId, params: ThreadUnarchiveParams, } | { "method": "thread/compact/start", id: RequestId, params: ThreadCompactStartParams, } | { "method": "thread/shellCommand", id: RequestId, params: ThreadShellCommandParams, } | { "method": "thread/approveGuardianDeniedAction", id: RequestId, params: ThreadApproveGuardianDeniedActionParams, } | { "method": "thread/rollback", id: RequestId, params: ThreadRollbackParams, } | { "method": "thread/list", id: RequestId, params: ThreadListParams, } | { "method": "thread/loaded/list", id: RequestId, params: ThreadLoadedListParams, } | { "method": "thread/read", id: RequestId, params: ThreadReadParams, } | { "method": "thread/inject_items", id: RequestId, params: ThreadInjectItemsParams, } | { "method": "skills/list", id: RequestId, params: SkillsListParams, } | { "method": "skills/extraRoots/set", id: RequestId, params: SkillsExtraRootsSetParams, } | { "method": "hooks/list", id: RequestId, params: HooksListParams, } | { "method": "marketplace/add", id: RequestId, params: MarketplaceAddParams, } | { "method": "marketplace/remove", id: RequestId, params: MarketplaceRemoveParams, } | { "method": "marketplace/upgrade", id: RequestId, params: MarketplaceUpgradeParams, } | { "method": "plugin/list", id: RequestId, params: PluginListParams, } | { "method": "plugin/installed", id: RequestId, params: PluginInstalledParams, } | { "method": "plugin/read", id: RequestId, params: PluginReadParams, } | { "method": "plugin/skill/read", id: RequestId, params: PluginSkillReadParams, } | { "method": "plugin/share/save", id: RequestId, params: PluginShareSaveParams, } | { "method": "plugin/share/updateTargets", id: RequestId, params: PluginShareUpdateTargetsParams, } | { "method": "plugin/share/list", id: RequestId, params: PluginShareListParams, } | { "method": "plugin/share/checkout", id: RequestId, params: PluginShareCheckoutParams, } | { "method": "plugin/share/delete", id: RequestId, params: PluginShareDeleteParams, } | { "method": "app/list", id: RequestId, params: AppsListParams, } | { "method": "fs/readFile", id: RequestId, params: FsReadFileParams, } | { "method": "fs/writeFile", id: RequestId, params: FsWriteFileParams, } | { "method": "fs/createDirectory", id: RequestId, params: FsCreateDirectoryParams, } | { "method": "fs/getMetadata", id: RequestId, params: FsGetMetadataParams, } | { "method": "fs/readDirectory", id: RequestId, params: FsReadDirectoryParams, } | { "method": "fs/remove", id: RequestId, params: FsRemoveParams, } | { "method": "fs/copy", id: RequestId, params: FsCopyParams, } | { "method": "fs/watch", id: RequestId, params: FsWatchParams, } | { "method": "fs/unwatch", id: RequestId, params: FsUnwatchParams, } | { "method": "skills/config/write", id: RequestId, params: SkillsConfigWriteParams, } | { "method": "plugin/install", id: RequestId, params: PluginInstallParams, } | { "method": "plugin/uninstall", id: RequestId, params: PluginUninstallParams, } | { "method": "turn/start", id: RequestId, params: TurnStartParams, } | { "method": "turn/steer", id: RequestId, params: TurnSteerParams, } | { "method": "turn/interrupt", id: RequestId, params: TurnInterruptParams, } | { "method": "review/start", id: RequestId, params: ReviewStartParams, } | { "method": "model/list", id: RequestId, params: ModelListParams, } | { "method": "modelProvider/capabilities/read", id: RequestId, params: ModelProviderCapabilitiesReadParams, } | { "method": "experimentalFeature/list", id: RequestId, params: ExperimentalFeatureListParams, } | { "method": "permissionProfile/list", id: RequestId, params: PermissionProfileListParams, } | { "method": "experimentalFeature/enablement/set", id: RequestId, params: ExperimentalFeatureEnablementSetParams, } | { "method": "mcpServer/oauth/login", id: RequestId, params: McpServerOauthLoginParams, } | { "method": "config/mcpServer/reload", id: RequestId, params: undefined, } | { "method": "mcpServerStatus/list", id: RequestId, params: ListMcpServerStatusParams, } | { "method": "mcpServer/resource/read", id: RequestId, params: McpResourceReadParams, } | { "method": "mcpServer/tool/call", id: RequestId, params: McpServerToolCallParams, } | { "method": "windowsSandbox/setupStart", id: RequestId, params: WindowsSandboxSetupStartParams, } | { "method": "windowsSandbox/readiness", id: RequestId, params: undefined, } | { "method": "account/login/start", id: RequestId, params: LoginAccountParams, } | { "method": "account/login/cancel", id: RequestId, params: CancelLoginAccountParams, } | { "method": "account/logout", id: RequestId, params: undefined, } | { "method": "account/rateLimits/read", id: RequestId, params: undefined, } | { "method": "account/rateLimitResetCredit/consume", id: RequestId, params: ConsumeAccountRateLimitResetCreditParams, } | { "method": "account/usage/read", id: RequestId, params: undefined, } | { "method": "account/sendAddCreditsNudgeEmail", id: RequestId, params: SendAddCreditsNudgeEmailParams, } | { "method": "feedback/upload", id: RequestId, params: FeedbackUploadParams, } | { "method": "command/exec", id: RequestId, params: CommandExecParams, } | { "method": "command/exec/write", id: RequestId, params: CommandExecWriteParams, } | { "method": "command/exec/terminate", id: RequestId, params: CommandExecTerminateParams, } | { "method": "command/exec/resize", id: RequestId, params: CommandExecResizeParams, } | { "method": "config/read", id: RequestId, params: ConfigReadParams, } | { "method": "externalAgentConfig/detect", id: RequestId, params: ExternalAgentConfigDetectParams, } | { "method": "externalAgentConfig/import", id: RequestId, params: ExternalAgentConfigImportParams, } | { "method": "externalAgentConfig/import/readHistories", id: RequestId, params: undefined, } | { "method": "config/value/write", id: RequestId, params: ConfigValueWriteParams, } | { "method": "config/batchWrite", id: RequestId, params: ConfigBatchWriteParams, } | { "method": "configRequirements/read", id: RequestId, params: undefined, } | { "method": "account/read", id: RequestId, params: GetAccountParams, } | { "method": "getConversationSummary", id: RequestId, params: GetConversationSummaryParams, } | { "method": "gitDiffToRemote", id: RequestId, params: GitDiffToRemoteParams, } | { "method": "getAuthStatus", id: RequestId, params: GetAuthStatusParams, } | { "method": "fuzzyFileSearch", id: RequestId, params: FuzzyFileSearchParams, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts index 7cae63a55..82814111d 100644 --- a/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts +++ b/codex-rs/app-server-protocol/schema/typescript/ServerNotification.ts @@ -15,6 +15,7 @@ import type { ContextCompactedNotification } from "./v2/ContextCompactedNotifica import type { DeprecationNoticeNotification } from "./v2/DeprecationNoticeNotification"; import type { ErrorNotification } from "./v2/ErrorNotification"; import type { ExternalAgentConfigImportCompletedNotification } from "./v2/ExternalAgentConfigImportCompletedNotification"; +import type { ExternalAgentConfigImportProgressNotification } from "./v2/ExternalAgentConfigImportProgressNotification"; import type { FileChangeOutputDeltaNotification } from "./v2/FileChangeOutputDeltaNotification"; import type { FileChangePatchUpdatedNotification } from "./v2/FileChangePatchUpdatedNotification"; import type { FsChangedNotification } from "./v2/FsChangedNotification"; @@ -72,4 +73,4 @@ import type { WindowsWorldWritableWarningNotification } from "./v2/WindowsWorldW /** * Notification sent from the server to the client. */ -export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/deleted", "params": ThreadDeletedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/goal/updated", "params": ThreadGoalUpdatedNotification } | { "method": "thread/goal/cleared", "params": ThreadGoalClearedNotification } | { "method": "thread/settings/updated", "params": ThreadSettingsUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "process/outputDelta", "params": ProcessOutputDeltaNotification } | { "method": "process/exited", "params": ProcessExitedNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/fileChange/patchUpdated", "params": FileChangePatchUpdatedNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "remoteControl/status/changed", "params": RemoteControlStatusChangedNotification } | { "method": "externalAgentConfig/import/completed", "params": ExternalAgentConfigImportCompletedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "model/verification", "params": ModelVerificationNotification } | { "method": "turn/moderationMetadata", "params": TurnModerationMetadataNotification } | { "method": "warning", "params": WarningNotification } | { "method": "guardianWarning", "params": GuardianWarningNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcript/delta", "params": ThreadRealtimeTranscriptDeltaNotification } | { "method": "thread/realtime/transcript/done", "params": ThreadRealtimeTranscriptDoneNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; +export type ServerNotification = { "method": "error", "params": ErrorNotification } | { "method": "thread/started", "params": ThreadStartedNotification } | { "method": "thread/status/changed", "params": ThreadStatusChangedNotification } | { "method": "thread/archived", "params": ThreadArchivedNotification } | { "method": "thread/deleted", "params": ThreadDeletedNotification } | { "method": "thread/unarchived", "params": ThreadUnarchivedNotification } | { "method": "thread/closed", "params": ThreadClosedNotification } | { "method": "skills/changed", "params": SkillsChangedNotification } | { "method": "thread/name/updated", "params": ThreadNameUpdatedNotification } | { "method": "thread/goal/updated", "params": ThreadGoalUpdatedNotification } | { "method": "thread/goal/cleared", "params": ThreadGoalClearedNotification } | { "method": "thread/settings/updated", "params": ThreadSettingsUpdatedNotification } | { "method": "thread/tokenUsage/updated", "params": ThreadTokenUsageUpdatedNotification } | { "method": "turn/started", "params": TurnStartedNotification } | { "method": "hook/started", "params": HookStartedNotification } | { "method": "turn/completed", "params": TurnCompletedNotification } | { "method": "hook/completed", "params": HookCompletedNotification } | { "method": "turn/diff/updated", "params": TurnDiffUpdatedNotification } | { "method": "turn/plan/updated", "params": TurnPlanUpdatedNotification } | { "method": "item/started", "params": ItemStartedNotification } | { "method": "item/autoApprovalReview/started", "params": ItemGuardianApprovalReviewStartedNotification } | { "method": "item/autoApprovalReview/completed", "params": ItemGuardianApprovalReviewCompletedNotification } | { "method": "item/completed", "params": ItemCompletedNotification } | { "method": "rawResponseItem/completed", "params": RawResponseItemCompletedNotification } | { "method": "item/agentMessage/delta", "params": AgentMessageDeltaNotification } | { "method": "item/plan/delta", "params": PlanDeltaNotification } | { "method": "command/exec/outputDelta", "params": CommandExecOutputDeltaNotification } | { "method": "process/outputDelta", "params": ProcessOutputDeltaNotification } | { "method": "process/exited", "params": ProcessExitedNotification } | { "method": "item/commandExecution/outputDelta", "params": CommandExecutionOutputDeltaNotification } | { "method": "item/commandExecution/terminalInteraction", "params": TerminalInteractionNotification } | { "method": "item/fileChange/outputDelta", "params": FileChangeOutputDeltaNotification } | { "method": "item/fileChange/patchUpdated", "params": FileChangePatchUpdatedNotification } | { "method": "serverRequest/resolved", "params": ServerRequestResolvedNotification } | { "method": "item/mcpToolCall/progress", "params": McpToolCallProgressNotification } | { "method": "mcpServer/oauthLogin/completed", "params": McpServerOauthLoginCompletedNotification } | { "method": "mcpServer/startupStatus/updated", "params": McpServerStatusUpdatedNotification } | { "method": "account/updated", "params": AccountUpdatedNotification } | { "method": "account/rateLimits/updated", "params": AccountRateLimitsUpdatedNotification } | { "method": "app/list/updated", "params": AppListUpdatedNotification } | { "method": "remoteControl/status/changed", "params": RemoteControlStatusChangedNotification } | { "method": "externalAgentConfig/import/progress", "params": ExternalAgentConfigImportProgressNotification } | { "method": "externalAgentConfig/import/completed", "params": ExternalAgentConfigImportCompletedNotification } | { "method": "fs/changed", "params": FsChangedNotification } | { "method": "item/reasoning/summaryTextDelta", "params": ReasoningSummaryTextDeltaNotification } | { "method": "item/reasoning/summaryPartAdded", "params": ReasoningSummaryPartAddedNotification } | { "method": "item/reasoning/textDelta", "params": ReasoningTextDeltaNotification } | { "method": "thread/compacted", "params": ContextCompactedNotification } | { "method": "model/rerouted", "params": ModelReroutedNotification } | { "method": "model/verification", "params": ModelVerificationNotification } | { "method": "turn/moderationMetadata", "params": TurnModerationMetadataNotification } | { "method": "warning", "params": WarningNotification } | { "method": "guardianWarning", "params": GuardianWarningNotification } | { "method": "deprecationNotice", "params": DeprecationNoticeNotification } | { "method": "configWarning", "params": ConfigWarningNotification } | { "method": "fuzzyFileSearch/sessionUpdated", "params": FuzzyFileSearchSessionUpdatedNotification } | { "method": "fuzzyFileSearch/sessionCompleted", "params": FuzzyFileSearchSessionCompletedNotification } | { "method": "thread/realtime/started", "params": ThreadRealtimeStartedNotification } | { "method": "thread/realtime/itemAdded", "params": ThreadRealtimeItemAddedNotification } | { "method": "thread/realtime/transcript/delta", "params": ThreadRealtimeTranscriptDeltaNotification } | { "method": "thread/realtime/transcript/done", "params": ThreadRealtimeTranscriptDoneNotification } | { "method": "thread/realtime/outputAudio/delta", "params": ThreadRealtimeOutputAudioDeltaNotification } | { "method": "thread/realtime/sdp", "params": ThreadRealtimeSdpNotification } | { "method": "thread/realtime/error", "params": ThreadRealtimeErrorNotification } | { "method": "thread/realtime/closed", "params": ThreadRealtimeClosedNotification } | { "method": "windows/worldWritableWarning", "params": WindowsWorldWritableWarningNotification } | { "method": "windowsSandbox/setupCompleted", "params": WindowsSandboxSetupCompletedNotification } | { "method": "account/login/completed", "params": AccountLoginCompletedNotification }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportHistoriesReadResponse.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportHistoriesReadResponse.ts new file mode 100644 index 000000000..9df61f556 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportHistoriesReadResponse.ts @@ -0,0 +1,6 @@ +// 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 { ExternalAgentConfigImportHistory } from "./ExternalAgentConfigImportHistory"; + +export type ExternalAgentConfigImportHistoriesReadResponse = { data: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportHistory.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportHistory.ts new file mode 100644 index 000000000..37273fc96 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportHistory.ts @@ -0,0 +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 { ExternalAgentConfigImportItemTypeFailure } from "./ExternalAgentConfigImportItemTypeFailure"; +import type { ExternalAgentConfigImportItemTypeSuccess } from "./ExternalAgentConfigImportItemTypeSuccess"; + +export type ExternalAgentConfigImportHistory = { importId: string, completedAtMs: bigint, successes: Array, failures: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportItemTypeFailure.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportItemTypeFailure.ts index 97386cba7..13e02bf17 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportItemTypeFailure.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportItemTypeFailure.ts @@ -3,4 +3,4 @@ // 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 ExternalAgentConfigImportItemTypeFailure = { itemType: ExternalAgentConfigMigrationItemType, failureStage: string, message: string, cwd: string | null, source: string | null, }; +export type ExternalAgentConfigImportItemTypeFailure = { itemType: ExternalAgentConfigMigrationItemType, errorType: string | null, failureStage: string, message: string, cwd: string | null, source: string | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportProgressNotification.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportProgressNotification.ts new file mode 100644 index 000000000..2115d633d --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ExternalAgentConfigImportProgressNotification.ts @@ -0,0 +1,6 @@ +// 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"; + +export type ExternalAgentConfigImportProgressNotification = { importId: string, itemTypeResults: Array, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 5aa66a56b..2e59b0fe1 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -110,9 +110,12 @@ export type { ExperimentalFeatureStage } from "./ExperimentalFeatureStage"; export type { ExternalAgentConfigDetectParams } from "./ExternalAgentConfigDetectParams"; export type { ExternalAgentConfigDetectResponse } from "./ExternalAgentConfigDetectResponse"; export type { ExternalAgentConfigImportCompletedNotification } from "./ExternalAgentConfigImportCompletedNotification"; +export type { ExternalAgentConfigImportHistoriesReadResponse } from "./ExternalAgentConfigImportHistoriesReadResponse"; +export type { ExternalAgentConfigImportHistory } from "./ExternalAgentConfigImportHistory"; export type { ExternalAgentConfigImportItemTypeFailure } from "./ExternalAgentConfigImportItemTypeFailure"; export type { ExternalAgentConfigImportItemTypeSuccess } from "./ExternalAgentConfigImportItemTypeSuccess"; export type { ExternalAgentConfigImportParams } from "./ExternalAgentConfigImportParams"; +export type { ExternalAgentConfigImportProgressNotification } from "./ExternalAgentConfigImportProgressNotification"; export type { ExternalAgentConfigImportResponse } from "./ExternalAgentConfigImportResponse"; export type { ExternalAgentConfigImportTypeResult } from "./ExternalAgentConfigImportTypeResult"; export type { ExternalAgentConfigMigrationItem } from "./ExternalAgentConfigMigrationItem"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 9339e0916..581a7a3b0 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1103,6 +1103,11 @@ client_request_definitions! { serialization: global("config"), response: v2::ExternalAgentConfigImportResponse, }, + ExternalAgentConfigImportHistoriesRead => "externalAgentConfig/import/readHistories" { + params: #[ts(type = "undefined")] #[serde(skip_serializing_if = "Option::is_none")] Option<()>, + serialization: global_shared_read("config"), + response: v2::ExternalAgentConfigImportHistoriesReadResponse, + }, ConfigValueWrite => "config/value/write" { params: v2::ConfigValueWriteParams, serialization: global("config"), @@ -1614,6 +1619,7 @@ server_notification_definitions! { AccountRateLimitsUpdated => "account/rateLimits/updated" (v2::AccountRateLimitsUpdatedNotification), AppListUpdated => "app/list/updated" (v2::AppListUpdatedNotification), RemoteControlStatusChanged => "remoteControl/status/changed" (v2::RemoteControlStatusChangedNotification), + ExternalAgentConfigImportProgress => "externalAgentConfig/import/progress" (v2::ExternalAgentConfigImportProgressNotification), ExternalAgentConfigImportCompleted => "externalAgentConfig/import/completed" (v2::ExternalAgentConfigImportCompletedNotification), FsChanged => "fs/changed" (v2::FsChangedNotification), ReasoningSummaryTextDelta => "item/reasoning/summaryTextDelta" (v2::ReasoningSummaryTextDeltaNotification), diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config.rs b/codex-rs/app-server-protocol/src/protocol/v2/config.rs index c4d113d1c..7190e7876 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -677,6 +677,7 @@ pub struct ExternalAgentConfigImportResponse { #[ts(export_to = "v2/")] pub struct ExternalAgentConfigImportItemTypeFailure { pub item_type: ExternalAgentConfigMigrationItemType, + pub error_type: Option, pub failure_stage: String, pub message: String, pub cwd: Option, @@ -702,6 +703,31 @@ pub struct ExternalAgentConfigImportTypeResult { pub failures: Vec, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ExternalAgentConfigImportHistory { + pub import_id: String, + pub completed_at_ms: i64, + pub successes: Vec, + pub failures: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ExternalAgentConfigImportHistoriesReadResponse { + pub data: Vec, +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct ExternalAgentConfigImportProgressNotification { + pub import_id: String, + pub item_type_results: Vec, +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] diff --git a/codex-rs/app-server/src/config/external_agent_config.rs b/codex-rs/app-server/src/config/external_agent_config.rs index 7b7f0f2e9..79171e171 100644 --- a/codex-rs/app-server/src/config/external_agent_config.rs +++ b/codex-rs/app-server/src/config/external_agent_config.rs @@ -129,11 +129,6 @@ impl ExternalAgentConfigImportItemResult { } } - pub(crate) fn record_successes(&mut self, count: usize) { - let count = u32::try_from(count).unwrap_or(u32::MAX); - self.success_count = self.success_count.saturating_add(count); - } - pub(crate) fn record_error(&mut self, raw_error: ExternalAgentConfigImportRawError) { self.error_count = self.error_count.saturating_add(1); self.raw_errors.push(raw_error); @@ -161,6 +156,7 @@ pub(crate) struct ExternalAgentConfigImportSuccess { #[derive(Debug, Clone, PartialEq, Eq)] pub(crate) struct ExternalAgentConfigImportRawError { pub item_type: ExternalAgentConfigMigrationItemType, + pub error_type: Option, pub failure_stage: String, pub message: String, pub cwd: Option, @@ -254,33 +250,41 @@ impl ExternalAgentConfigService { ); let import_result = match migration_item.item_type { ExternalAgentConfigMigrationItemType::Config => (|| { - let migrated_count = self.import_config(migration_item.cwd.as_deref())?; + if let Some((source, target)) = + self.import_config(migration_item.cwd.as_deref())? + { + item_result.record_success(Some(source), Some(target)); + } emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::Config, /*skills_count*/ None, ); - item_result.record_successes(migrated_count); Ok(()) })(), ExternalAgentConfigMigrationItemType::Skills => (|| { - let skills_count = self.import_skills(migration_item.cwd.as_deref())?; + let imported_skills = self.import_skills(migration_item.cwd.as_deref())?; emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::Skills, - Some(skills_count), + Some(imported_skills.len()), ); - item_result.record_successes(skills_count); + for skill_name in imported_skills { + item_result.record_success(Some(skill_name.clone()), Some(skill_name)); + } Ok(()) })(), ExternalAgentConfigMigrationItemType::AgentsMd => (|| { - let migrated_count = self.import_agents_md(migration_item.cwd.as_deref())?; + if let Some((source, target)) = + self.import_agents_md(migration_item.cwd.as_deref())? + { + item_result.record_success(Some(source), Some(target)); + } emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::AgentsMd, /*skills_count*/ None, ); - item_result.record_successes(migrated_count); Ok(()) })(), ExternalAgentConfigMigrationItemType::Plugins => { @@ -357,44 +361,54 @@ impl ExternalAgentConfigService { .await } ExternalAgentConfigMigrationItemType::McpServerConfig => (|| { - let migrated_count = + let migrated_server_names = self.import_mcp_server_config(migration_item.cwd.as_deref())?; emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::McpServerConfig, /*skills_count*/ None, ); - item_result.record_successes(migrated_count); + for server_name in migrated_server_names { + item_result.record_success(Some(server_name.clone()), Some(server_name)); + } Ok(()) })(), ExternalAgentConfigMigrationItemType::Subagents => (|| { - let subagents_count = self.import_subagents(migration_item.cwd.as_deref())?; + let imported_subagents = + self.import_subagents(migration_item.cwd.as_deref())?; emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::Subagents, - Some(subagents_count), + Some(imported_subagents.len()), ); - item_result.record_successes(subagents_count); + for subagent_name in imported_subagents { + item_result + .record_success(Some(subagent_name.clone()), Some(subagent_name)); + } Ok(()) })(), ExternalAgentConfigMigrationItemType::Hooks => (|| { - let migrated_count = self.import_hooks(migration_item.cwd.as_deref())?; + let migrated_hook_names = self.import_hooks(migration_item.cwd.as_deref())?; emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::Hooks, /*skills_count*/ None, ); - item_result.record_successes(migrated_count); + for hook_name in migrated_hook_names { + item_result.record_success(Some(hook_name.clone()), Some(hook_name)); + } Ok(()) })(), ExternalAgentConfigMigrationItemType::Commands => (|| { - let commands_count = self.import_commands(migration_item.cwd.as_deref())?; + let imported_commands = self.import_commands(migration_item.cwd.as_deref())?; emit_migration_metric( EXTERNAL_AGENT_CONFIG_IMPORT_METRIC, ExternalAgentConfigMigrationItemType::Commands, - Some(commands_count), + Some(imported_commands.len()), ); - item_result.record_successes(commands_count); + for command_name in imported_commands { + item_result.record_success(Some(command_name.clone()), Some(command_name)); + } Ok(()) })(), ExternalAgentConfigMigrationItemType::Sessions => Ok(()), @@ -957,7 +971,7 @@ impl ExternalAgentConfigService { Ok(outcome) } - fn import_config(&self, cwd: Option<&Path>) -> io::Result { + fn import_config(&self, cwd: Option<&Path>) -> io::Result> { let repo_root = find_repo_root(cwd)?; let (source_settings, target_config) = if let Some(repo_root) = repo_root.as_ref() { ( @@ -965,7 +979,7 @@ impl ExternalAgentConfigService { repo_root.join(".codex").join("config.toml"), ) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(None); } else { ( self.external_agent_home.join("settings.json"), @@ -973,11 +987,11 @@ impl ExternalAgentConfigService { ) }; let Some(settings) = effective_external_settings(&source_settings)? else { - return Ok(0); + return Ok(None); }; let migrated = build_config_from_external(&settings)?; if is_empty_toml_table(&migrated) { - return Ok(0); + return Ok(None); } let Some(target_parent) = target_config.parent() else { @@ -986,7 +1000,10 @@ impl ExternalAgentConfigService { fs::create_dir_all(target_parent)?; if !target_config.exists() { write_toml_file(&target_config, &migrated)?; - return Ok(1); + return Ok(Some(( + source_settings.display().to_string(), + target_config.display().to_string(), + ))); } let existing_raw = fs::read_to_string(&target_config)?; @@ -999,14 +1016,17 @@ impl ExternalAgentConfigService { let changed = merge_missing_toml_values(&mut existing, &migrated)?; if !changed { - return Ok(0); + return Ok(None); } write_toml_file(&target_config, &existing)?; - Ok(1) + Ok(Some(( + source_settings.display().to_string(), + target_config.display().to_string(), + ))) } - fn import_mcp_server_config(&self, cwd: Option<&Path>) -> io::Result { + fn import_mcp_server_config(&self, cwd: Option<&Path>) -> io::Result> { let repo_root = find_repo_root(cwd)?; let (source_settings, target_config) = if let Some(repo_root) = repo_root.as_ref() { ( @@ -1014,7 +1034,7 @@ impl ExternalAgentConfigService { repo_root.join(".codex").join("config.toml"), ) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(Vec::new()); } else { ( self.external_agent_home.join("settings.json"), @@ -1031,7 +1051,7 @@ impl ExternalAgentConfigService { settings.as_ref(), )?; if is_empty_toml_table(&migrated) { - return Ok(0); + return Ok(Vec::new()); } let Some(target_parent) = target_config.parent() else { @@ -1039,9 +1059,9 @@ impl ExternalAgentConfigService { }; fs::create_dir_all(target_parent)?; if !target_config.exists() { - let migrated_count = migrated_mcp_server_names(&migrated).len(); + let migrated_server_names = migrated_mcp_server_names(&migrated); write_toml_file(&target_config, &migrated)?; - return Ok(migrated_count); + return Ok(migrated_server_names); } let existing_raw = fs::read_to_string(&target_config)?; @@ -1051,21 +1071,21 @@ impl ExternalAgentConfigService { toml::from_str::(&existing_raw) .map_err(|err| invalid_data_error(format!("invalid existing config.toml: {err}")))? }; - let merged_server_count = merge_missing_mcp_servers(&mut existing, &migrated)?.len(); - if merged_server_count > 0 { + let merged_server_names = merge_missing_mcp_servers(&mut existing, &migrated)?; + if !merged_server_names.is_empty() { write_toml_file(&target_config, &existing)?; } - Ok(merged_server_count) + Ok(merged_server_names) } - fn import_subagents(&self, cwd: Option<&Path>) -> io::Result { + fn import_subagents(&self, cwd: Option<&Path>) -> io::Result> { let (source_agents, target_agents) = if let Some(repo_root) = find_repo_root(cwd)? { ( repo_root.join(EXTERNAL_AGENT_DIR).join("agents"), repo_root.join(".codex").join("agents"), ) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(Vec::new()); } else { ( self.external_agent_home.join("agents"), @@ -1076,7 +1096,7 @@ impl ExternalAgentConfigService { import_subagents(&source_agents, &target_agents) } - fn import_hooks(&self, cwd: Option<&Path>) -> io::Result { + fn import_hooks(&self, cwd: Option<&Path>) -> io::Result> { let (source_external_agent_dir, target_hooks) = if let Some(repo_root) = find_repo_root(cwd)? { ( @@ -1084,7 +1104,7 @@ impl ExternalAgentConfigService { repo_root.join(".codex").join("hooks.json"), ) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(Vec::new()); } else { ( self.external_agent_home.clone(), @@ -1092,20 +1112,22 @@ impl ExternalAgentConfigService { ) }; - Ok(usize::from(import_hooks( - &source_external_agent_dir, - &target_hooks, - )?)) + let hook_names = hook_migration_event_names(&source_external_agent_dir, &target_hooks)?; + if import_hooks(&source_external_agent_dir, &target_hooks)? { + Ok(hook_names) + } else { + Ok(Vec::new()) + } } - fn import_commands(&self, cwd: Option<&Path>) -> io::Result { + fn import_commands(&self, cwd: Option<&Path>) -> io::Result> { let (source_commands, target_skills) = if let Some(repo_root) = find_repo_root(cwd)? { ( repo_root.join(EXTERNAL_AGENT_DIR).join("commands"), repo_root.join(".agents").join("skills"), ) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(Vec::new()); } else { ( self.external_agent_home.join("commands"), @@ -1116,14 +1138,14 @@ impl ExternalAgentConfigService { import_commands(&source_commands, &target_skills) } - fn import_skills(&self, cwd: Option<&Path>) -> io::Result { + fn import_skills(&self, cwd: Option<&Path>) -> io::Result> { let (source_skills, target_skills) = if let Some(repo_root) = find_repo_root(cwd)? { ( repo_root.join(EXTERNAL_AGENT_DIR).join("skills"), repo_root.join(".agents").join("skills"), ) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(Vec::new()); } else { ( self.external_agent_home.join("skills"), @@ -1131,11 +1153,11 @@ impl ExternalAgentConfigService { ) }; if !source_skills.is_dir() { - return Ok(0); + return Ok(Vec::new()); } fs::create_dir_all(&target_skills)?; - let mut copied_count = 0usize; + let mut copied_names = Vec::new(); for entry in fs::read_dir(&source_skills)? { let entry = entry?; @@ -1150,20 +1172,20 @@ impl ExternalAgentConfigService { } copy_dir_recursive(&entry.path(), &target)?; - copied_count += 1; + copied_names.push(entry.file_name().to_string_lossy().to_string()); } - Ok(copied_count) + Ok(copied_names) } - fn import_agents_md(&self, cwd: Option<&Path>) -> io::Result { + fn import_agents_md(&self, cwd: Option<&Path>) -> io::Result> { let (source_agents_md, target_agents_md) = if let Some(repo_root) = find_repo_root(cwd)? { let Some(source_agents_md) = find_repo_agents_md_source(&repo_root)? else { - return Ok(0); + return Ok(None); }; (source_agents_md, repo_root.join("AGENTS.md")) } else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) { - return Ok(0); + return Ok(None); } else { ( self.external_agent_home.join(EXTERNAL_AGENT_CONFIG_MD), @@ -1173,7 +1195,7 @@ impl ExternalAgentConfigService { if !is_non_empty_text_file(&source_agents_md)? || !is_missing_or_empty_text_file(&target_agents_md)? { - return Ok(0); + return Ok(None); } let Some(target_parent) = target_agents_md.parent() else { @@ -1182,7 +1204,10 @@ impl ExternalAgentConfigService { fs::create_dir_all(target_parent)?; rewrite_and_copy_text_file(&source_agents_md, &target_agents_md)?; - Ok(1) + Ok(Some(( + source_agents_md.display().to_string(), + target_agents_md.display().to_string(), + ))) } } @@ -1826,6 +1851,7 @@ pub(crate) fn record_import_error( ) { result.record_error(ExternalAgentConfigImportRawError { item_type: result.item_type, + error_type: None, failure_stage: failure_stage.to_string(), message: message.into(), cwd: result.cwd.clone(), @@ -1856,6 +1882,7 @@ fn plugin_import_raw_error( ) -> ExternalAgentConfigImportRawError { ExternalAgentConfigImportRawError { item_type: ExternalAgentConfigMigrationItemType::Plugins, + error_type: None, failure_stage: failure_stage.to_string(), message, cwd: cwd.map(Path::to_path_buf), diff --git a/codex-rs/app-server/src/config/external_agent_config_tests.rs b/codex-rs/app-server/src/config/external_agent_config_tests.rs index 6675dc32e..d6fa5a927 100644 --- a/codex-rs/app-server/src/config/external_agent_config_tests.rs +++ b/codex-rs/app-server/src/config/external_agent_config_tests.rs @@ -47,11 +47,26 @@ fn assert_single_plugin_raw_error( ExternalAgentConfigMigrationItemType::Plugins ); assert_eq!(raw_error.failure_stage, failure_stage); + assert_eq!(raw_error.error_type, None); assert_eq!(raw_error.cwd, None); assert_eq!(raw_error.source.as_deref(), Some(source)); assert!(!raw_error.message.is_empty()); } +fn import_success( + item_type: ExternalAgentConfigMigrationItemType, + cwd: Option, + source: impl Into, + target: impl Into, +) -> ExternalAgentConfigImportSuccess { + ExternalAgentConfigImportSuccess { + item_type, + cwd, + source: Some(source.into()), + target: Some(target.into()), + } +} + #[tokio::test] async fn detect_home_lists_config_skills_and_agents_md() { let (_root, external_agent_home, codex_home) = fixture_paths(); @@ -1232,7 +1247,15 @@ async fn import_repo_agents_md_rewrites_terms_and_skips_non_empty_targets() { cwd: Some(repo_root.clone()), success_count: 1, error_count: 0, - successes: Vec::new(), + successes: vec![import_success( + ExternalAgentConfigMigrationItemType::AgentsMd, + Some(repo_root.clone()), + repo_root + .join(EXTERNAL_AGENT_CONFIG_MD) + .display() + .to_string(), + repo_root.join("AGENTS.md").display().to_string(), + )], raw_errors: Vec::new(), }, ExternalAgentConfigImportItemResult { @@ -1290,7 +1313,15 @@ async fn import_repo_agents_md_overwrites_empty_targets() { cwd: Some(repo_root.clone()), success_count: 1, error_count: 0, - successes: Vec::new(), + successes: vec![import_success( + ExternalAgentConfigMigrationItemType::AgentsMd, + Some(repo_root.clone()), + repo_root + .join(EXTERNAL_AGENT_CONFIG_MD) + .display() + .to_string(), + repo_root.join("AGENTS.md").display().to_string(), + )], raw_errors: Vec::new(), }] ); @@ -1383,7 +1414,12 @@ async fn import_repo_hooks_preserves_disabled_codex_hooks_feature() { cwd: Some(repo_root.clone()), success_count: 1, error_count: 0, - successes: Vec::new(), + successes: vec![import_success( + ExternalAgentConfigMigrationItemType::Hooks, + Some(repo_root.clone()), + "Stop", + "Stop", + )], raw_errors: Vec::new(), }] ); @@ -1456,7 +1492,12 @@ async fn import_repo_mcp_uses_home_settings_toggles_when_repo_settings_missing() cwd: Some(repo_root.clone()), success_count: 1, error_count: 0, - successes: Vec::new(), + successes: vec![import_success( + ExternalAgentConfigMigrationItemType::McpServerConfig, + Some(repo_root.clone()), + "allowed", + "allowed", + )], raw_errors: Vec::new(), }] ); @@ -2745,7 +2786,7 @@ async fn import_plugins_supports_project_relative_external_agent_plugin_marketpl } #[test] -fn import_skills_returns_only_new_skill_directory_count() { +fn import_skills_returns_only_new_skill_directory_names() { let (_root, external_agent_home, codex_home) = fixture_paths(); let agents_skills = codex_home .parent() @@ -2757,9 +2798,9 @@ fn import_skills_returns_only_new_skill_directory_count() { .expect("create source b"); fs::create_dir_all(agents_skills.join("skill-a")).expect("create existing target"); - let copied_count = service_for_paths(external_agent_home, codex_home) + let copied_names = service_for_paths(external_agent_home, codex_home) .import_skills(/*cwd*/ None) .expect("import skills"); - assert_eq!(copied_count, 1); + assert_eq!(copied_names, vec!["skill-b".to_string()]); } diff --git a/codex-rs/app-server/src/message_processor.rs b/codex-rs/app-server/src/message_processor.rs index 603810f24..c32b92c4e 100644 --- a/codex-rs/app-server/src/message_processor.rs +++ b/codex-rs/app-server/src/message_processor.rs @@ -24,6 +24,7 @@ use crate::request_processors::CommandExecRequestProcessor; use crate::request_processors::ConfigRequestProcessor; use crate::request_processors::EnvironmentRequestProcessor; use crate::request_processors::ExternalAgentConfigRequestProcessor; +use crate::request_processors::ExternalAgentConfigRequestProcessorArgs; use crate::request_processors::FeedbackRequestProcessor; use crate::request_processors::FsRequestProcessor; use crate::request_processors::GitRequestProcessor; @@ -475,7 +476,7 @@ impl MessageProcessor { thread_watch_manager.clone(), Arc::clone(&thread_list_state_permit), thread_goal_processor.clone(), - state_db, + state_db.clone(), log_db, Arc::clone(&skills_watcher), ); @@ -511,15 +512,17 @@ impl MessageProcessor { thread_manager.clone(), analytics_events_client, ); - let external_agent_config_processor = ExternalAgentConfigRequestProcessor::new( - outgoing.clone(), - Arc::clone(&thread_manager), - Arc::clone(&thread_store), - config_manager.clone(), - config_processor.clone(), - arg0_paths, - config.codex_home.to_path_buf(), - ); + let external_agent_config_processor = + ExternalAgentConfigRequestProcessor::new(ExternalAgentConfigRequestProcessorArgs { + outgoing: outgoing.clone(), + thread_manager: Arc::clone(&thread_manager), + thread_store: Arc::clone(&thread_store), + config_manager: config_manager.clone(), + config_processor: config_processor.clone(), + state_db, + arg0_paths, + codex_home: config.codex_home.to_path_buf(), + }); let environment_processor = EnvironmentRequestProcessor::new(thread_manager.environment_manager()); let fs_processor = FsRequestProcessor::new( @@ -947,6 +950,11 @@ impl MessageProcessor { .import(request_id.clone(), params) .await .map(|()| None), + ClientRequest::ExternalAgentConfigImportHistoriesRead { .. } => self + .external_agent_config_processor + .read_import_histories() + .await + .map(|response| Some(response.into())), ClientRequest::ConfigValueWrite { params, .. } => { self.config_processor.value_write(params).await.map(Some) } diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index 640e975a0..cfc411e13 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -506,6 +506,7 @@ pub(crate) use command_exec_processor::CommandExecRequestProcessor; pub(crate) use config_processor::ConfigRequestProcessor; pub(crate) use environment_processor::EnvironmentRequestProcessor; pub(crate) use external_agent_config_processor::ExternalAgentConfigRequestProcessor; +pub(crate) use external_agent_config_processor::ExternalAgentConfigRequestProcessorArgs; pub(crate) use feedback_processor::FeedbackRequestProcessor; pub(crate) use fs_processor::FsRequestProcessor; pub(crate) use git_processor::GitRequestProcessor; diff --git a/codex-rs/app-server/src/request_processors/external_agent_config_processor.rs b/codex-rs/app-server/src/request_processors/external_agent_config_processor.rs index 29faf9a9b..1a9002dd1 100644 --- a/codex-rs/app-server/src/request_processors/external_agent_config_processor.rs +++ b/codex-rs/app-server/src/request_processors/external_agent_config_processor.rs @@ -19,9 +19,12 @@ use codex_app_server_protocol::CommandMigration; use codex_app_server_protocol::ExternalAgentConfigDetectParams; use codex_app_server_protocol::ExternalAgentConfigDetectResponse; use codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification; +use codex_app_server_protocol::ExternalAgentConfigImportHistoriesReadResponse; +use codex_app_server_protocol::ExternalAgentConfigImportHistory; 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; use codex_app_server_protocol::ExternalAgentConfigImportTypeResult as ProtocolImportTypeResult; use codex_app_server_protocol::ExternalAgentConfigMigrationItem; @@ -35,6 +38,9 @@ use codex_app_server_protocol::ServerNotification; use codex_arg0::Arg0DispatchPaths; use codex_core::ThreadManager; use codex_external_agent_sessions::ExternalAgentSessionMigration as CoreSessionMigration; +use codex_rollout::StateDbHandle; +use codex_state::ExternalAgentConfigImportFailureRecord; +use codex_state::ExternalAgentConfigImportSuccessRecord; use codex_thread_store::ThreadStore; use std::collections::HashSet; use std::path::PathBuf; @@ -50,18 +56,32 @@ pub(crate) struct ExternalAgentConfigRequestProcessor { session_importer: ExternalAgentSessionImporter, thread_manager: Arc, config_processor: ConfigRequestProcessor, + state_db: Option, +} + +pub(crate) struct ExternalAgentConfigRequestProcessorArgs { + pub(crate) outgoing: Arc, + pub(crate) thread_manager: Arc, + pub(crate) thread_store: Arc, + pub(crate) config_manager: ConfigManager, + pub(crate) config_processor: ConfigRequestProcessor, + pub(crate) state_db: Option, + pub(crate) arg0_paths: Arg0DispatchPaths, + pub(crate) codex_home: PathBuf, } impl ExternalAgentConfigRequestProcessor { - pub(crate) fn new( - outgoing: Arc, - thread_manager: Arc, - thread_store: Arc, - config_manager: ConfigManager, - config_processor: ConfigRequestProcessor, - arg0_paths: Arg0DispatchPaths, - codex_home: PathBuf, - ) -> Self { + pub(crate) fn new(args: ExternalAgentConfigRequestProcessorArgs) -> Self { + let ExternalAgentConfigRequestProcessorArgs { + outgoing, + thread_manager, + thread_store, + config_manager, + config_processor, + state_db, + arg0_paths, + codex_home, + } = args; let session_importer = ExternalAgentSessionImporter::new( codex_home.clone(), Arc::clone(&thread_manager), @@ -75,6 +95,7 @@ impl ExternalAgentConfigRequestProcessor { session_importer, thread_manager, config_processor, + state_db, } } @@ -207,23 +228,31 @@ impl ExternalAgentConfigRequestProcessor { let mut completed_item_results = Vec::new(); if let Some(session_validation_result) = session_validation_result { + send_import_progress(&self.outgoing, &import_id, &session_validation_result).await; completed_item_results.push(session_validation_result); } for item_result in import_outcome.item_results { + send_import_progress(&self.outgoing, &import_id, &item_result).await; completed_item_results.push(item_result); } let has_background_imports = !import_outcome.pending_plugin_imports.is_empty() || !pending_session_imports.is_empty(); if !has_background_imports { - send_completed_import_notification(&self.outgoing, import_id, &completed_item_results) - .await; + send_completed_import_notification( + &self.outgoing, + self.state_db.as_ref(), + import_id, + &completed_item_results, + ) + .await; return Ok(()); } let session_importer = self.session_importer.clone(); let plugin_processor = self.clone(); let outgoing = Arc::clone(&self.outgoing); + let state_db = self.state_db.clone(); let thread_manager = Arc::clone(&self.thread_manager); let session_import_result = (!pending_session_imports.is_empty()).then(|| { CoreImportItemResult::new( @@ -234,13 +263,19 @@ impl ExternalAgentConfigRequestProcessor { }); let pending_plugin_imports = import_outcome.pending_plugin_imports; tokio::spawn(async move { + let session_progress_outgoing = Arc::clone(&outgoing); + let session_import_id = import_id.clone(); let session_imports = async move { let session_import_result = session_import_result?; let item_result = session_importer .import_sessions(pending_session_imports, session_import_result) .await; + send_import_progress(&session_progress_outgoing, &session_import_id, &item_result) + .await; Some(item_result) }; + let plugin_progress_outgoing = Arc::clone(&outgoing); + let plugin_import_id = import_id.clone(); let plugin_imports = async move { let mut item_results = Vec::new(); for pending_plugin_import in pending_plugin_imports { @@ -265,6 +300,12 @@ impl ExternalAgentConfigRequestProcessor { ); } } + send_import_progress( + &plugin_progress_outgoing, + &plugin_import_id, + &item_result, + ) + .await; item_results.push(item_result); } item_results @@ -280,12 +321,37 @@ impl ExternalAgentConfigRequestProcessor { thread_manager.plugins_manager().clear_cache(); thread_manager.skills_manager().clear_cache(); } - send_completed_import_notification(&outgoing, import_id, &completed_item_results).await; + send_completed_import_notification( + &outgoing, + state_db.as_ref(), + import_id, + &completed_item_results, + ) + .await; }); Ok(()) } + pub(crate) async fn read_import_histories( + &self, + ) -> Result { + let state_db = self + .state_db + .as_ref() + .ok_or_else(|| internal_error("state database is unavailable"))?; + let histories = state_db + .external_agent_config_import_history_records() + .await + .map_err(|err| internal_error(format!("failed to read import histories: {err}")))?; + let data = histories + .into_iter() + .map(protocol_import_history) + .collect::, _>>()?; + + Ok(ExternalAgentConfigImportHistoriesReadResponse { data }) + } + fn validate_pending_session_imports( &self, params: &ExternalAgentConfigImportParams, @@ -467,12 +533,37 @@ impl ExternalAgentConfigRequestProcessor { } } +async fn send_import_progress( + outgoing: &OutgoingMessageSender, + import_id: &str, + item_result: &CoreImportItemResult, +) { + outgoing + .send_server_notification(ServerNotification::ExternalAgentConfigImportProgress( + ExternalAgentConfigImportProgressNotification { + import_id: import_id.to_string(), + item_type_results: vec![protocol_import_type_result(item_result)], + }, + )) + .await; +} + async fn send_completed_import_notification( outgoing: &OutgoingMessageSender, + state_db: Option<&StateDbHandle>, import_id: String, item_results: &[CoreImportItemResult], ) { let notification = completed_notification(import_id, item_results); + if let Some(state_db) = state_db + && let Err(err) = record_completed_import_notification(state_db, ¬ification).await + { + tracing::warn!( + import_id = %notification.import_id, + error = %err, + "failed to record external agent config import completion" + ); + } outgoing .send_server_notification(ServerNotification::ExternalAgentConfigImportCompleted( notification, @@ -480,6 +571,103 @@ async fn send_completed_import_notification( .await; } +async fn record_completed_import_notification( + state_db: &StateDbHandle, + notification: &ExternalAgentConfigImportCompletedNotification, +) -> anyhow::Result<()> { + let successes = notification + .item_type_results + .iter() + .flat_map(|type_result| type_result.successes.iter()) + .map(|success| { + Ok(ExternalAgentConfigImportSuccessRecord { + item_type: serde_json::from_value(serde_json::to_value(success.item_type)?)?, + cwd: success.cwd.clone(), + source: success.source.clone(), + target: success.target.clone(), + }) + }) + .collect::>>()?; + let failures = notification + .item_type_results + .iter() + .flat_map(|type_result| type_result.failures.iter()) + .map(|failure| { + Ok(ExternalAgentConfigImportFailureRecord { + item_type: serde_json::from_value(serde_json::to_value(failure.item_type)?)?, + error_type: failure.error_type.clone(), + failure_stage: failure.failure_stage.clone(), + message: failure.message.clone(), + cwd: failure.cwd.clone(), + source: failure.source.clone(), + }) + }) + .collect::>>()?; + state_db + .record_external_agent_config_import_completed( + notification.import_id.as_str(), + &successes, + &failures, + ) + .await +} + +fn protocol_import_history( + record: codex_state::ExternalAgentConfigImportHistoryRecord, +) -> Result { + let successes = record + .successes + .into_iter() + .map(protocol_import_success_record) + .collect::, _>>()?; + let failures = record + .failures + .into_iter() + .map(protocol_import_failure_record) + .collect::, _>>()?; + + Ok(ExternalAgentConfigImportHistory { + import_id: record.import_id, + completed_at_ms: record.completed_at_ms, + successes, + failures, + }) +} + +fn protocol_import_success_record( + record: ExternalAgentConfigImportSuccessRecord, +) -> Result { + Ok(ProtocolImportSuccess { + item_type: protocol_import_record_item_type(record.item_type)?, + cwd: record.cwd, + source: record.source, + target: record.target, + }) +} + +fn protocol_import_failure_record( + record: ExternalAgentConfigImportFailureRecord, +) -> Result { + Ok(ProtocolImportFailure { + item_type: protocol_import_record_item_type(record.item_type)?, + error_type: record.error_type, + failure_stage: record.failure_stage, + message: record.message, + cwd: record.cwd, + source: record.source, + }) +} + +fn protocol_import_record_item_type( + item_type: String, +) -> Result { + serde_json::from_value(serde_json::Value::String(item_type.clone())).map_err(|err| { + internal_error(format!( + "failed to decode import item type {item_type}: {err}" + )) + }) +} + fn completed_notification( import_id: String, item_results: &[CoreImportItemResult], @@ -529,6 +717,22 @@ fn completed_notification( } } +fn protocol_import_type_result(item_result: &CoreImportItemResult) -> ProtocolImportTypeResult { + ProtocolImportTypeResult { + item_type: protocol_migration_item_type(item_result.item_type), + successes: item_result + .successes + .iter() + .map(protocol_import_success) + .collect(), + failures: item_result + .raw_errors + .iter() + .map(protocol_import_raw_error) + .collect(), + } +} + fn protocol_import_success( success: &crate::config::external_agent_config::ExternalAgentConfigImportSuccess, ) -> ProtocolImportSuccess { @@ -543,6 +747,7 @@ fn protocol_import_success( fn protocol_import_raw_error(raw_error: &CoreImportRawError) -> ProtocolImportFailure { ProtocolImportFailure { item_type: protocol_migration_item_type(raw_error.item_type), + error_type: raw_error.error_type.clone(), failure_stage: raw_error.failure_stage.clone(), message: raw_error.message.clone(), cwd: raw_error.cwd.clone(), diff --git a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs index 8b165a11e..5e90fc2d1 100644 --- a/codex-rs/app-server/tests/suite/v2/external_agent_config.rs +++ b/codex-rs/app-server/tests/suite/v2/external_agent_config.rs @@ -7,6 +7,8 @@ use app_test_support::to_response; use app_test_support::write_mock_responses_config_toml; use codex_app_server_protocol::ExternalAgentConfigDetectResponse; use codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification; +use codex_app_server_protocol::ExternalAgentConfigImportHistoriesReadResponse; +use codex_app_server_protocol::ExternalAgentConfigImportProgressNotification; use codex_app_server_protocol::ExternalAgentConfigImportResponse; use codex_app_server_protocol::ExternalAgentConfigMigrationItemType; use codex_app_server_protocol::JSONRPCError; @@ -42,10 +44,17 @@ fn assert_import_response(response: ExternalAgentConfigImportResponse) -> String async fn external_agent_config_import_sends_completion_notification_for_sync_only_import() -> Result<()> { let codex_home = TempDir::new()?; + let sqlite_home = TempDir::new()?; let home_dir = codex_home.path().display().to_string(); - let mut mcp = - TestAppServer::new_with_env(codex_home.path(), &[("HOME", Some(home_dir.as_str()))]) - .await?; + let sqlite_home_dir = sqlite_home.path().display().to_string(); + let mut mcp = TestAppServer::new_with_env( + codex_home.path(), + &[ + ("HOME", Some(home_dir.as_str())), + ("CODEX_SQLITE_HOME", Some(sqlite_home_dir.as_str())), + ], + ) + .await?; timeout(DEFAULT_TIMEOUT, mcp.initialize()).await??; let request_id = mcp @@ -68,6 +77,21 @@ async fn external_agent_config_import_sends_completion_notification_for_sync_onl .await??; let response: ExternalAgentConfigImportResponse = to_response(response)?; let import_id = assert_import_response(response); + let progress = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_notification_message("externalAgentConfig/import/progress"), + ) + .await??; + assert_eq!(progress.method, "externalAgentConfig/import/progress"); + let progress: ExternalAgentConfigImportProgressNotification = + serde_json::from_value(progress.params.expect("progress params"))?; + assert_eq!(progress.import_id, import_id); + assert_eq!(progress.item_type_results.len(), 1); + assert_eq!( + progress.item_type_results[0].item_type, + ExternalAgentConfigMigrationItemType::Config + ); + let notification = timeout( DEFAULT_TIMEOUT, mcp.read_stream_until_notification_message("externalAgentConfig/import/completed"), @@ -77,6 +101,58 @@ async fn external_agent_config_import_sends_completion_notification_for_sync_onl let completed: ExternalAgentConfigImportCompletedNotification = serde_json::from_value(notification.params.expect("completed params"))?; assert_eq!(completed.import_id, import_id); + let state_db = + codex_state::StateRuntime::init(sqlite_home.path().to_path_buf(), "mock_provider".into()) + .await?; + let details_record = state_db + .external_agent_config_import_details_record(&import_id) + .await? + .expect("completed import details should be recorded by import id"); + let expected_successes = completed + .item_type_results + .iter() + .flat_map(|type_result| type_result.successes.iter()) + .collect::>(); + let expected_failures = completed + .item_type_results + .iter() + .flat_map(|type_result| type_result.failures.iter()) + .collect::>(); + assert_eq!( + serde_json::to_value(&details_record.successes)?, + serde_json::to_value(&expected_successes)? + ); + assert_eq!( + serde_json::to_value(&details_record.failures)?, + serde_json::to_value(&expected_failures)? + ); + + let request_id = mcp + .send_raw_request( + "externalAgentConfig/import/readHistories", + /*params*/ None, + ) + .await?; + let response: JSONRPCResponse = timeout( + DEFAULT_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let response: ExternalAgentConfigImportHistoriesReadResponse = to_response(response)?; + let entry = response + .data + .iter() + .find(|entry| entry.import_id == import_id) + .expect("import history entry should be available"); + assert!(entry.completed_at_ms > 0); + assert_eq!( + serde_json::to_value(&entry.successes)?, + serde_json::to_value(&expected_successes)? + ); + assert_eq!( + serde_json::to_value(&entry.failures)?, + serde_json::to_value(&expected_failures)? + ); Ok(()) } diff --git a/codex-rs/external-agent-migration/src/lib.rs b/codex-rs/external-agent-migration/src/lib.rs index 6ee0b38a2..13a6bc63a 100644 --- a/codex-rs/external-agent-migration/src/lib.rs +++ b/codex-rs/external-agent-migration/src/lib.rs @@ -155,13 +155,13 @@ pub fn missing_subagent_names( Ok(names) } -pub fn import_subagents(source_agents: &Path, target_agents: &Path) -> io::Result { +pub fn import_subagents(source_agents: &Path, target_agents: &Path) -> io::Result> { if !source_agents.is_dir() { - return Ok(0); + return Ok(Vec::new()); } fs::create_dir_all(target_agents)?; - let mut imported = 0usize; + let mut imported = Vec::new(); for source_file in agent_source_files(source_agents)? { let Some(target) = subagent_target_file(&source_file, target_agents) else { continue; @@ -174,7 +174,7 @@ pub fn import_subagents(source_agents: &Path, target_agents: &Path) -> io::Resul continue; }; fs::write(&target, render_agent_toml(&document.body, &metadata)?)?; - imported += 1; + imported.push(metadata.name); } Ok(imported) @@ -195,13 +195,13 @@ pub fn missing_command_names( .collect()) } -pub fn import_commands(source_commands: &Path, target_skills: &Path) -> io::Result { +pub fn import_commands(source_commands: &Path, target_skills: &Path) -> io::Result> { if !source_commands.is_dir() { - return Ok(0); + return Ok(Vec::new()); } fs::create_dir_all(target_skills)?; - let mut imported = 0usize; + let mut imported = Vec::new(); for (source_file, name) in unique_supported_command_sources(source_commands)? { let document = parse_document(&source_file)?; let target_dir = target_skills.join(&name); @@ -217,7 +217,7 @@ pub fn import_commands(source_commands: &Path, target_skills: &Path) -> io::Resu target_dir.join("SKILL.md"), render_command_skill(&document.body, &name, &description, &source_name), )?; - imported += 1; + imported.push(name); } Ok(imported) diff --git a/codex-rs/state/migrations/0038_external_agent_config_imports.sql b/codex-rs/state/migrations/0038_external_agent_config_imports.sql new file mode 100644 index 000000000..74ae0435f --- /dev/null +++ b/codex-rs/state/migrations/0038_external_agent_config_imports.sql @@ -0,0 +1,6 @@ +CREATE TABLE external_agent_config_imports ( + import_id TEXT PRIMARY KEY, + completed_at_ms INTEGER NOT NULL, + successes TEXT NOT NULL, + failures TEXT NOT NULL +); diff --git a/codex-rs/state/src/lib.rs b/codex-rs/state/src/lib.rs index 4b8e3ec3f..6b5b90765 100644 --- a/codex-rs/state/src/lib.rs +++ b/codex-rs/state/src/lib.rs @@ -56,6 +56,10 @@ pub use model::ThreadGoalStatus; pub use model::ThreadMetadata; pub use model::ThreadMetadataBuilder; pub use model::ThreadsPage; +pub use runtime::ExternalAgentConfigImportDetailsRecord; +pub use runtime::ExternalAgentConfigImportFailureRecord; +pub use runtime::ExternalAgentConfigImportHistoryRecord; +pub use runtime::ExternalAgentConfigImportSuccessRecord; pub use runtime::GoalAccountingMode; pub use runtime::GoalAccountingOutcome; pub use runtime::GoalStore; diff --git a/codex-rs/state/src/runtime.rs b/codex-rs/state/src/runtime.rs index 2937bf0c2..0e5dc8558 100644 --- a/codex-rs/state/src/runtime.rs +++ b/codex-rs/state/src/runtime.rs @@ -59,6 +59,7 @@ use tracing::warn; mod agent_jobs; mod backfill; +mod external_agent_config_imports; mod goals; mod logs; mod memories; @@ -68,6 +69,10 @@ mod remote_control; mod test_support; mod threads; +pub use external_agent_config_imports::ExternalAgentConfigImportDetailsRecord; +pub use external_agent_config_imports::ExternalAgentConfigImportFailureRecord; +pub use external_agent_config_imports::ExternalAgentConfigImportHistoryRecord; +pub use external_agent_config_imports::ExternalAgentConfigImportSuccessRecord; pub use goals::GoalAccountingMode; pub use goals::GoalAccountingOutcome; pub use goals::GoalStore; diff --git a/codex-rs/state/src/runtime/external_agent_config_imports.rs b/codex-rs/state/src/runtime/external_agent_config_imports.rs new file mode 100644 index 000000000..4fbbe39ef --- /dev/null +++ b/codex-rs/state/src/runtime/external_agent_config_imports.rs @@ -0,0 +1,136 @@ +use super::StateRuntime; +use crate::model::datetime_to_epoch_millis; +use chrono::Utc; +use serde::Deserialize; +use serde::Serialize; +use sqlx::Row; +use std::path::PathBuf; + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExternalAgentConfigImportSuccessRecord { + pub item_type: String, + pub cwd: Option, + pub source: Option, + pub target: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExternalAgentConfigImportFailureRecord { + pub item_type: String, + pub error_type: Option, + pub failure_stage: String, + pub message: String, + pub cwd: Option, + pub source: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExternalAgentConfigImportDetailsRecord { + pub successes: Vec, + pub failures: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct ExternalAgentConfigImportHistoryRecord { + pub import_id: String, + pub completed_at_ms: i64, + pub successes: Vec, + pub failures: Vec, +} + +impl StateRuntime { + pub async fn record_external_agent_config_import_completed( + &self, + import_id: &str, + successes: &[ExternalAgentConfigImportSuccessRecord], + failures: &[ExternalAgentConfigImportFailureRecord], + ) -> anyhow::Result<()> { + sqlx::query( + r#" +INSERT INTO external_agent_config_imports ( + import_id, + completed_at_ms, + successes, + failures +) VALUES (?, ?, ?, ?) +ON CONFLICT(import_id) DO UPDATE SET + completed_at_ms = excluded.completed_at_ms, + successes = excluded.successes, + failures = excluded.failures +"#, + ) + .bind(import_id) + .bind(datetime_to_epoch_millis(Utc::now())) + .bind(serde_json::to_string(successes)?) + .bind(serde_json::to_string(failures)?) + .execute(self.pool.as_ref()) + .await?; + + Ok(()) + } + + pub async fn external_agent_config_import_details_record( + &self, + import_id: &str, + ) -> anyhow::Result> { + let row = sqlx::query( + r#" +SELECT + successes, + failures +FROM external_agent_config_imports +WHERE import_id = ? +"#, + ) + .bind(import_id) + .fetch_optional(self.pool.as_ref()) + .await?; + + row.map(|row| { + let successes: String = row.try_get("successes")?; + let failures: String = row.try_get("failures")?; + Ok(ExternalAgentConfigImportDetailsRecord { + successes: serde_json::from_str(&successes)?, + failures: serde_json::from_str(&failures)?, + }) + }) + .transpose() + } + + pub async fn external_agent_config_import_history_records( + &self, + ) -> anyhow::Result> { + let rows = sqlx::query( + r#" +SELECT + import_id, + completed_at_ms, + successes, + failures +FROM external_agent_config_imports +ORDER BY completed_at_ms DESC, import_id ASC +"#, + ) + .fetch_all(self.pool.as_ref()) + .await?; + + rows.into_iter() + .map(|row| { + let import_id: String = row.try_get("import_id")?; + let completed_at_ms: i64 = row.try_get("completed_at_ms")?; + let successes: String = row.try_get("successes")?; + let failures: String = row.try_get("failures")?; + Ok(ExternalAgentConfigImportHistoryRecord { + import_id, + completed_at_ms, + successes: serde_json::from_str(&successes)?, + failures: serde_json::from_str(&failures)?, + }) + }) + .collect() + } +} + +#[cfg(test)] +#[path = "external_agent_config_imports_tests.rs"] +mod tests; diff --git a/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs b/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs new file mode 100644 index 000000000..6d77894d4 --- /dev/null +++ b/codex-rs/state/src/runtime/external_agent_config_imports_tests.rs @@ -0,0 +1,145 @@ +use super::*; +use crate::runtime::test_support::unique_temp_dir; +use pretty_assertions::assert_eq; + +#[tokio::test] +async fn records_completion_by_import_id() -> anyhow::Result<()> { + let runtime = StateRuntime::init(unique_temp_dir(), "test-provider".to_string()).await?; + + runtime + .record_external_agent_config_import_completed( + "import-1", + &[ExternalAgentConfigImportSuccessRecord { + item_type: "CONFIG".to_string(), + cwd: None, + source: Some("settings.json".to_string()), + target: Some("config.toml".to_string()), + }], + &[], + ) + .await?; + runtime + .record_external_agent_config_import_completed( + "import-1", + &[ + ExternalAgentConfigImportSuccessRecord { + item_type: "CONFIG".to_string(), + cwd: None, + source: Some("settings.json".to_string()), + target: Some("config.toml".to_string()), + }, + ExternalAgentConfigImportSuccessRecord { + item_type: "MCP_SERVER_CONFIG".to_string(), + cwd: None, + source: Some("github".to_string()), + target: Some("github".to_string()), + }, + ], + &[ExternalAgentConfigImportFailureRecord { + item_type: "MCP_SERVER_CONFIG".to_string(), + error_type: None, + failure_stage: "import".to_string(), + message: "failed".to_string(), + cwd: None, + source: Some("broken".to_string()), + }], + ) + .await?; + + assert_eq!( + runtime + .external_agent_config_import_details_record("import-1") + .await?, + Some(ExternalAgentConfigImportDetailsRecord { + successes: vec![ + ExternalAgentConfigImportSuccessRecord { + item_type: "CONFIG".to_string(), + cwd: None, + source: Some("settings.json".to_string()), + target: Some("config.toml".to_string()), + }, + ExternalAgentConfigImportSuccessRecord { + item_type: "MCP_SERVER_CONFIG".to_string(), + cwd: None, + source: Some("github".to_string()), + target: Some("github".to_string()), + } + ], + failures: vec![ExternalAgentConfigImportFailureRecord { + item_type: "MCP_SERVER_CONFIG".to_string(), + error_type: None, + failure_stage: "import".to_string(), + message: "failed".to_string(), + cwd: None, + source: Some("broken".to_string()), + }], + }) + ); + assert_eq!( + runtime + .external_agent_config_import_history_records() + .await? + .into_iter() + .map(|record| ( + record.import_id, + record.successes, + record.failures, + record.completed_at_ms > 0 + )) + .collect::>(), + vec![( + "import-1".to_string(), + vec![ + ExternalAgentConfigImportSuccessRecord { + item_type: "CONFIG".to_string(), + cwd: None, + source: Some("settings.json".to_string()), + target: Some("config.toml".to_string()), + }, + ExternalAgentConfigImportSuccessRecord { + item_type: "MCP_SERVER_CONFIG".to_string(), + cwd: None, + source: Some("github".to_string()), + target: Some("github".to_string()), + } + ], + vec![ExternalAgentConfigImportFailureRecord { + item_type: "MCP_SERVER_CONFIG".to_string(), + error_type: None, + failure_stage: "import".to_string(), + message: "failed".to_string(), + cwd: None, + source: Some("broken".to_string()), + }], + true + )] + ); + + Ok(()) +} + +#[tokio::test] +async fn reads_all_history_records() -> anyhow::Result<()> { + let runtime = StateRuntime::init(unique_temp_dir(), "test-provider".to_string()).await?; + + runtime + .record_external_agent_config_import_completed("import-1", &[], &[]) + .await?; + runtime + .record_external_agent_config_import_completed("import-2", &[], &[]) + .await?; + + let mut records = runtime + .external_agent_config_import_history_records() + .await?; + records.sort_by(|left, right| left.import_id.cmp(&right.import_id)); + assert_eq!( + records + .into_iter() + .map(|record| record.import_id) + .collect::>(), + vec!["import-1".to_string(), "import-2".to_string()] + ); + + Ok(()) +} diff --git a/codex-rs/tui/src/app/app_server_event_targets.rs b/codex-rs/tui/src/app/app_server_event_targets.rs index fa2bab011..90f688f7e 100644 --- a/codex-rs/tui/src/app/app_server_event_targets.rs +++ b/codex-rs/tui/src/app/app_server_event_targets.rs @@ -161,6 +161,7 @@ pub(super) fn server_notification_thread_target( | ServerNotification::AccountRateLimitsUpdated(_) | ServerNotification::AppListUpdated(_) | ServerNotification::RemoteControlStatusChanged(_) + | ServerNotification::ExternalAgentConfigImportProgress(_) | ServerNotification::ExternalAgentConfigImportCompleted(_) | ServerNotification::DeprecationNotice(_) | ServerNotification::ConfigWarning(_) diff --git a/codex-rs/tui/src/chatwidget/protocol.rs b/codex-rs/tui/src/chatwidget/protocol.rs index c418160de..e2cf31b03 100644 --- a/codex-rs/tui/src/chatwidget/protocol.rs +++ b/codex-rs/tui/src/chatwidget/protocol.rs @@ -205,6 +205,7 @@ impl ChatWidget { | ServerNotification::McpServerOauthLoginCompleted(_) | ServerNotification::AppListUpdated(_) | ServerNotification::RemoteControlStatusChanged(_) + | ServerNotification::ExternalAgentConfigImportProgress(_) | ServerNotification::ExternalAgentConfigImportCompleted(_) | ServerNotification::FsChanged(_) | ServerNotification::TurnModerationMetadata(_)