mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
Support detect and import MCP, Subagents, hooks, commands from external (#19949)
## Why This PR expands the migration path so Codex can detect and import MCP server config, hooks, commands, and subagents configs in a Codex-native shape. ## What changed - Added a `codex-external-agent-migration` crate that owns conversion logic for external-agent MCP servers, hooks, commands, and subagents. - Extended the app-server external-agent config detection/import API with migration item types for MCP server config, hooks, commands, and subagents. ## Migration strategy The migration is intentionally conservative: Codex only imports external-agent config that can be represented safely in Codex today. Unsupported or ambiguous config is skipped instead of being partially translated into behavior that may not match the source system. - **MCP servers**: import supported stdio and HTTP MCP server definitions into `mcp_servers`. Disabled servers and servers filtered out by source `enabledMcpjsonServers` / `disabledMcpjsonServers` are skipped. Project-scoped MCP entries from `.claude.json` are included when they match the repo path. - **Hooks**: import only supported command hooks into `.codex/hooks.json`. Unsupported hook features such as conditional groups, async handlers, prompt/http hooks, or unknown fields are skipped. Referenced hook scripts are copied into `.codex/hooks/`, preserving any existing target scripts. - **Commands**: import supported external commands as Codex skills under `.agents/skills/source-command-*`. Commands that rely on source runtime expansion such as `$ARGUMENTS`, `$1`, `@file` references, shell interpolation, or colliding generated names are skipped. - **Subagents**: import valid subagent Markdown files into `.codex/agents/*.toml` when they have the minimum Codex agent fields. Source model names are not migrated, so imported agents keep the user’s Codex default model; compatible reasoning effort and sandbox mode are migrated when present. - **Skills and project guidance**: copy missing skill directories into `.agents/skills` and migrate `CLAUDE.md` guidance into `AGENTS.md`, rewriting source-agent terminology to Codex terminology where appropriate. - **Detection details**: detected migration items include lightweight details for UI preview, such as MCP server names, hook event names, generated command skill names, and subagent names. Import still recomputes from disk instead of trusting details as the source of truth. - Adds focused coverage for the new migration behavior and app-server import flow. ## Verification - `cargo test -p codex-external-agent-migration` - `cargo test -p codex-hooks` - `cargo test -p codex-app-server external_agent_config` - `just bazel-lock-check`
This commit is contained in:
committed by
GitHub
Unverified
parent
ebdf3a878c
commit
cb8b1bbcd6
Generated
+13
@@ -1853,6 +1853,7 @@ dependencies = [
|
||||
"codex-core-plugins",
|
||||
"codex-device-key",
|
||||
"codex-exec-server",
|
||||
"codex-external-agent-migration",
|
||||
"codex-external-agent-sessions",
|
||||
"codex-features",
|
||||
"codex-feedback",
|
||||
@@ -2683,6 +2684,18 @@ dependencies = [
|
||||
"syn 2.0.114",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-external-agent-migration"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"codex-hooks",
|
||||
"pretty_assertions",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"tempfile",
|
||||
"toml 0.9.11+spec-1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "codex-external-agent-sessions"
|
||||
version = "0.0.0"
|
||||
|
||||
@@ -40,6 +40,7 @@ members = [
|
||||
"exec-server",
|
||||
"execpolicy",
|
||||
"execpolicy-legacy",
|
||||
"external-agent-migration",
|
||||
"external-agent-sessions",
|
||||
"keyring-store",
|
||||
"file-search",
|
||||
@@ -147,6 +148,7 @@ codex-exec = { path = "exec" }
|
||||
codex-file-system = { path = "file-system" }
|
||||
codex-exec-server = { path = "exec-server" }
|
||||
codex-execpolicy = { path = "execpolicy" }
|
||||
codex-external-agent-migration = { path = "external-agent-migration" }
|
||||
codex-external-agent-sessions = { path = "external-agent-sessions" }
|
||||
codex-experimental-api-macros = { path = "codex-experimental-api-macros" }
|
||||
codex-features = { path = "features" }
|
||||
|
||||
@@ -354,6 +354,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ConfigBatchWriteParams": {
|
||||
"properties": {
|
||||
"edits": {
|
||||
@@ -850,6 +861,9 @@
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -1390,6 +1404,17 @@
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ImageDetail": {
|
||||
"enum": [
|
||||
"auto",
|
||||
@@ -1696,6 +1721,17 @@
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerOauthLoginParams": {
|
||||
"properties": {
|
||||
"name": {
|
||||
@@ -1779,6 +1815,27 @@
|
||||
},
|
||||
"MigrationDetails": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/CommandMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mcpServers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpServerMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"plugins": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -1792,6 +1849,13 @@
|
||||
"$ref": "#/definitions/SessionMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SubagentMigration"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -3158,6 +3222,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TextElement": {
|
||||
"properties": {
|
||||
"byteRange": {
|
||||
|
||||
+75
@@ -6786,6 +6786,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -8347,6 +8358,9 @@
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -9556,6 +9570,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookOutputEntry": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
@@ -10469,6 +10494,17 @@
|
||||
"title": "McpResourceReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerOauthLoginCompletedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -10793,6 +10829,27 @@
|
||||
},
|
||||
"MigrationDetails": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/CommandMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/HookMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mcpServers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/McpServerMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"plugins": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -10806,6 +10863,13 @@
|
||||
"$ref": "#/definitions/v2/SessionMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/v2/SubagentMigration"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -14136,6 +14200,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TerminalInteractionNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
|
||||
+75
@@ -3285,6 +3285,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"Config": {
|
||||
"additionalProperties": true,
|
||||
"properties": {
|
||||
@@ -4846,6 +4857,9 @@
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
@@ -6166,6 +6180,17 @@
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"HookOutputEntry": {
|
||||
"properties": {
|
||||
"kind": {
|
||||
@@ -7123,6 +7148,17 @@
|
||||
"title": "McpResourceReadResponse",
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerOauthLoginCompletedNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
@@ -7447,6 +7483,27 @@
|
||||
},
|
||||
"MigrationDetails": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/CommandMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mcpServers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpServerMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"plugins": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -7460,6 +7517,13 @@
|
||||
"$ref": "#/definitions/SessionMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SubagentMigration"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -12022,6 +12086,17 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"TerminalInteractionNotification": {
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"properties": {
|
||||
|
||||
+75
@@ -1,6 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExternalAgentConfigMigrationItem": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
@@ -40,12 +51,58 @@
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MigrationDetails": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/CommandMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mcpServers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpServerMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"plugins": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -59,6 +116,13 @@
|
||||
"$ref": "#/definitions/SessionMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SubagentMigration"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -101,6 +165,17 @@
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
+75
@@ -1,6 +1,17 @@
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"definitions": {
|
||||
"CommandMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"ExternalAgentConfigMigrationItem": {
|
||||
"properties": {
|
||||
"cwd": {
|
||||
@@ -40,12 +51,58 @@
|
||||
"SKILLS",
|
||||
"PLUGINS",
|
||||
"MCP_SERVER_CONFIG",
|
||||
"SUBAGENTS",
|
||||
"HOOKS",
|
||||
"COMMANDS",
|
||||
"SESSIONS"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"HookMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"McpServerMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"MigrationDetails": {
|
||||
"properties": {
|
||||
"commands": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/CommandMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"hooks": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/HookMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"mcpServers": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/McpServerMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"plugins": {
|
||||
"default": [],
|
||||
"items": {
|
||||
@@ -59,6 +116,13 @@
|
||||
"$ref": "#/definitions/SessionMigration"
|
||||
},
|
||||
"type": "array"
|
||||
},
|
||||
"subagents": {
|
||||
"default": [],
|
||||
"items": {
|
||||
"$ref": "#/definitions/SubagentMigration"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
@@ -101,6 +165,17 @@
|
||||
"path"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SubagentMigration": {
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name"
|
||||
],
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export type CommandMigration = { name: string, };
|
||||
+1
-1
@@ -2,4 +2,4 @@
|
||||
|
||||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type ExternalAgentConfigMigrationItemType = "AGENTS_MD" | "CONFIG" | "SKILLS" | "PLUGINS" | "MCP_SERVER_CONFIG" | "SESSIONS";
|
||||
export type ExternalAgentConfigMigrationItemType = "AGENTS_MD" | "CONFIG" | "SKILLS" | "PLUGINS" | "MCP_SERVER_CONFIG" | "SUBAGENTS" | "HOOKS" | "COMMANDS" | "SESSIONS";
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export type HookMigration = { name: string, };
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export type McpServerMigration = { name: string, };
|
||||
@@ -1,7 +1,11 @@
|
||||
// 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 { CommandMigration } from "./CommandMigration";
|
||||
import type { HookMigration } from "./HookMigration";
|
||||
import type { McpServerMigration } from "./McpServerMigration";
|
||||
import type { PluginsMigration } from "./PluginsMigration";
|
||||
import type { SessionMigration } from "./SessionMigration";
|
||||
import type { SubagentMigration } from "./SubagentMigration";
|
||||
|
||||
export type MigrationDetails = { plugins: Array<PluginsMigration>, sessions: Array<SessionMigration>, };
|
||||
export type MigrationDetails = { plugins: Array<PluginsMigration>, sessions: Array<SessionMigration>, mcpServers: Array<McpServerMigration>, hooks: Array<HookMigration>, subagents: Array<SubagentMigration>, commands: Array<CommandMigration>, };
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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.
|
||||
|
||||
export type SubagentMigration = { name: string, };
|
||||
@@ -58,6 +58,7 @@ export type { CommandExecutionRequestApprovalParams } from "./CommandExecutionRe
|
||||
export type { CommandExecutionRequestApprovalResponse } from "./CommandExecutionRequestApprovalResponse";
|
||||
export type { CommandExecutionSource } from "./CommandExecutionSource";
|
||||
export type { CommandExecutionStatus } from "./CommandExecutionStatus";
|
||||
export type { CommandMigration } from "./CommandMigration";
|
||||
export type { Config } from "./Config";
|
||||
export type { ConfigBatchWriteParams } from "./ConfigBatchWriteParams";
|
||||
export type { ConfigEdit } from "./ConfigEdit";
|
||||
@@ -154,6 +155,7 @@ export type { HookCompletedNotification } from "./HookCompletedNotification";
|
||||
export type { HookEventName } from "./HookEventName";
|
||||
export type { HookExecutionMode } from "./HookExecutionMode";
|
||||
export type { HookHandlerType } from "./HookHandlerType";
|
||||
export type { HookMigration } from "./HookMigration";
|
||||
export type { HookOutputEntry } from "./HookOutputEntry";
|
||||
export type { HookOutputEntryKind } from "./HookOutputEntryKind";
|
||||
export type { HookPromptFragment } from "./HookPromptFragment";
|
||||
@@ -209,6 +211,7 @@ export type { McpResourceReadResponse } from "./McpResourceReadResponse";
|
||||
export type { McpServerElicitationAction } from "./McpServerElicitationAction";
|
||||
export type { McpServerElicitationRequestParams } from "./McpServerElicitationRequestParams";
|
||||
export type { McpServerElicitationRequestResponse } from "./McpServerElicitationRequestResponse";
|
||||
export type { McpServerMigration } from "./McpServerMigration";
|
||||
export type { McpServerOauthLoginCompletedNotification } from "./McpServerOauthLoginCompletedNotification";
|
||||
export type { McpServerOauthLoginParams } from "./McpServerOauthLoginParams";
|
||||
export type { McpServerOauthLoginResponse } from "./McpServerOauthLoginResponse";
|
||||
@@ -313,6 +316,7 @@ export type { SkillsListExtraRootsForCwd } from "./SkillsListExtraRootsForCwd";
|
||||
export type { SkillsListParams } from "./SkillsListParams";
|
||||
export type { SkillsListResponse } from "./SkillsListResponse";
|
||||
export type { SortDirection } from "./SortDirection";
|
||||
export type { SubagentMigration } from "./SubagentMigration";
|
||||
export type { TerminalInteractionNotification } from "./TerminalInteractionNotification";
|
||||
export type { TextElement } from "./TextElement";
|
||||
export type { TextPosition } from "./TextPosition";
|
||||
|
||||
@@ -1092,6 +1092,15 @@ pub enum ExternalAgentConfigMigrationItemType {
|
||||
#[serde(rename = "MCP_SERVER_CONFIG")]
|
||||
#[ts(rename = "MCP_SERVER_CONFIG")]
|
||||
McpServerConfig,
|
||||
#[serde(rename = "SUBAGENTS")]
|
||||
#[ts(rename = "SUBAGENTS")]
|
||||
Subagents,
|
||||
#[serde(rename = "HOOKS")]
|
||||
#[ts(rename = "HOOKS")]
|
||||
Hooks,
|
||||
#[serde(rename = "COMMANDS")]
|
||||
#[ts(rename = "COMMANDS")]
|
||||
Commands,
|
||||
#[serde(rename = "SESSIONS")]
|
||||
#[ts(rename = "SESSIONS")]
|
||||
Sessions,
|
||||
@@ -1121,11 +1130,47 @@ pub struct SessionMigration {
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct McpServerMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct HookMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct SubagentMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct CommandMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq, Eq, JsonSchema, TS)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[ts(export_to = "v2/")]
|
||||
pub struct MigrationDetails {
|
||||
#[serde(default)]
|
||||
pub plugins: Vec<PluginsMigration>,
|
||||
#[serde(default)]
|
||||
pub sessions: Vec<SessionMigration>,
|
||||
#[serde(default)]
|
||||
pub mcp_servers: Vec<McpServerMigration>,
|
||||
#[serde(default)]
|
||||
pub hooks: Vec<HookMigration>,
|
||||
#[serde(default)]
|
||||
pub subagents: Vec<SubagentMigration>,
|
||||
#[serde(default)]
|
||||
pub commands: Vec<CommandMigration>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
|
||||
@@ -7876,7 +7921,7 @@ mod tests {
|
||||
marketplace_name: "team-marketplace".to_string(),
|
||||
plugin_names: vec!["asana".to_string()],
|
||||
}],
|
||||
sessions: Vec::new(),
|
||||
..Default::default()
|
||||
}),
|
||||
}
|
||||
);
|
||||
@@ -7913,7 +7958,7 @@ mod tests {
|
||||
marketplace_name: "team-marketplace".to_string(),
|
||||
plugin_names: vec!["asana".to_string()],
|
||||
}],
|
||||
sessions: Vec::new(),
|
||||
..Default::default()
|
||||
}),
|
||||
}],
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ codex-core = { workspace = true }
|
||||
codex-core-plugins = { workspace = true }
|
||||
codex-device-key = { workspace = true }
|
||||
codex-exec-server = { workspace = true }
|
||||
codex-external-agent-migration = { workspace = true }
|
||||
codex-external-agent-sessions = { workspace = true }
|
||||
codex-features = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
|
||||
@@ -9,6 +9,15 @@ use codex_core_plugins::marketplace::find_marketplace_manifest_path;
|
||||
use codex_core_plugins::marketplace_add::MarketplaceAddRequest;
|
||||
use codex_core_plugins::marketplace_add::add_marketplace;
|
||||
use codex_core_plugins::marketplace_add::is_local_marketplace_source;
|
||||
use codex_external_agent_migration::build_mcp_config_from_external;
|
||||
use codex_external_agent_migration::count_missing_commands;
|
||||
use codex_external_agent_migration::count_missing_subagents;
|
||||
use codex_external_agent_migration::hook_migration_event_names;
|
||||
use codex_external_agent_migration::import_commands;
|
||||
use codex_external_agent_migration::import_hooks;
|
||||
use codex_external_agent_migration::import_subagents;
|
||||
use codex_external_agent_migration::missing_command_names;
|
||||
use codex_external_agent_migration::missing_subagent_names;
|
||||
use codex_external_agent_sessions::ExternalAgentSessionMigration;
|
||||
use codex_external_agent_sessions::detect_recent_sessions;
|
||||
use codex_protocol::protocol::Product;
|
||||
@@ -43,6 +52,9 @@ pub(crate) enum ExternalAgentConfigMigrationItemType {
|
||||
AgentsMd,
|
||||
Plugins,
|
||||
McpServerConfig,
|
||||
Subagents,
|
||||
Hooks,
|
||||
Commands,
|
||||
Sessions,
|
||||
}
|
||||
|
||||
@@ -53,9 +65,18 @@ pub(crate) struct PluginsMigration {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct NamedMigration {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub(crate) struct MigrationDetails {
|
||||
pub plugins: Vec<PluginsMigration>,
|
||||
pub sessions: Vec<ExternalAgentSessionMigration>,
|
||||
pub mcp_servers: Vec<NamedMigration>,
|
||||
pub hooks: Vec<NamedMigration>,
|
||||
pub subagents: Vec<NamedMigration>,
|
||||
pub commands: Vec<NamedMigration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
@@ -182,7 +203,38 @@ impl ExternalAgentConfigService {
|
||||
/*skills_count*/ None,
|
||||
);
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => {}
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => {
|
||||
self.import_mcp_server_config(migration_item.cwd.as_deref())?;
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_IMPORT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
/*skills_count*/ None,
|
||||
);
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Subagents => {
|
||||
let subagents_count = self.import_subagents(migration_item.cwd.as_deref())?;
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_IMPORT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::Subagents,
|
||||
Some(subagents_count),
|
||||
);
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Hooks => {
|
||||
self.import_hooks(migration_item.cwd.as_deref())?;
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_IMPORT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::Hooks,
|
||||
/*skills_count*/ None,
|
||||
);
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Commands => {
|
||||
let commands_count = self.import_commands(migration_item.cwd.as_deref())?;
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_IMPORT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::Commands,
|
||||
Some(commands_count),
|
||||
);
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Sessions => {}
|
||||
}
|
||||
}
|
||||
@@ -241,6 +293,81 @@ impl ExternalAgentConfigService {
|
||||
}
|
||||
}
|
||||
|
||||
let source_root = self.source_root(repo_root);
|
||||
let mcp_settings = self.mcp_settings(repo_root, settings.clone())?;
|
||||
let migrated_mcp = build_mcp_config_from_external(
|
||||
source_root.as_path(),
|
||||
Some(self.external_agent_home.as_path()),
|
||||
mcp_settings.as_ref(),
|
||||
)?;
|
||||
let mcp_server_names = migrated_mcp_server_names(&migrated_mcp);
|
||||
if !is_empty_toml_table(&migrated_mcp) {
|
||||
let mut should_include = true;
|
||||
if target_config.exists() {
|
||||
let existing_raw = fs::read_to_string(&target_config)?;
|
||||
let mut existing = if existing_raw.trim().is_empty() {
|
||||
TomlValue::Table(Default::default())
|
||||
} else {
|
||||
toml::from_str::<TomlValue>(&existing_raw).map_err(|err| {
|
||||
invalid_data_error(format!("invalid existing config.toml: {err}"))
|
||||
})?
|
||||
};
|
||||
should_include = merge_missing_toml_values(&mut existing, &migrated_mcp)?;
|
||||
}
|
||||
|
||||
if should_include {
|
||||
items.push(ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
description: format!(
|
||||
"Migrate MCP servers from {} into {}",
|
||||
source_root.display(),
|
||||
target_config.display()
|
||||
),
|
||||
cwd: cwd.clone(),
|
||||
details: Some(MigrationDetails {
|
||||
mcp_servers: named_migrations(mcp_server_names.clone()),
|
||||
..Default::default()
|
||||
}),
|
||||
});
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_DETECT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
/*skills_count*/ None,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let source_external_agent_dir = repo_root.map_or_else(
|
||||
|| self.external_agent_home.clone(),
|
||||
|repo_root| repo_root.join(EXTERNAL_AGENT_DIR),
|
||||
);
|
||||
let target_hooks = repo_root.map_or_else(
|
||||
|| self.codex_home.join("hooks.json"),
|
||||
|repo_root| repo_root.join(".codex").join("hooks.json"),
|
||||
);
|
||||
let hook_event_names =
|
||||
hook_migration_event_names(source_external_agent_dir.as_path(), &target_hooks)?;
|
||||
if !hook_event_names.is_empty() && is_missing_or_empty_text_file(&target_hooks)? {
|
||||
items.push(ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Hooks,
|
||||
description: format!(
|
||||
"Migrate hooks from {} to {}",
|
||||
source_external_agent_dir.display(),
|
||||
target_hooks.display()
|
||||
),
|
||||
cwd: cwd.clone(),
|
||||
details: Some(MigrationDetails {
|
||||
hooks: named_migrations(hook_event_names),
|
||||
..Default::default()
|
||||
}),
|
||||
});
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_DETECT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::Hooks,
|
||||
/*skills_count*/ None,
|
||||
);
|
||||
}
|
||||
|
||||
let source_skills = repo_root.map_or_else(
|
||||
|| self.external_agent_home.join("skills"),
|
||||
|repo_root| repo_root.join(EXTERNAL_AGENT_DIR).join("skills"),
|
||||
@@ -268,6 +395,62 @@ impl ExternalAgentConfigService {
|
||||
);
|
||||
}
|
||||
|
||||
let source_commands = source_external_agent_dir.join("commands");
|
||||
let target_command_skills = repo_root.map_or_else(
|
||||
|| self.home_target_skills_dir(),
|
||||
|repo_root| repo_root.join(".agents").join("skills"),
|
||||
);
|
||||
let commands_count = count_missing_commands(&source_commands, &target_command_skills)?;
|
||||
if commands_count > 0 {
|
||||
let command_names = missing_command_names(&source_commands, &target_command_skills)?;
|
||||
items.push(ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Commands,
|
||||
description: format!(
|
||||
"Migrate commands from {} to {}",
|
||||
source_commands.display(),
|
||||
target_command_skills.display()
|
||||
),
|
||||
cwd: cwd.clone(),
|
||||
details: Some(MigrationDetails {
|
||||
commands: named_migrations(command_names),
|
||||
..Default::default()
|
||||
}),
|
||||
});
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_DETECT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::Commands,
|
||||
Some(commands_count),
|
||||
);
|
||||
}
|
||||
|
||||
let source_subagents = source_external_agent_dir.join("agents");
|
||||
let target_subagents = repo_root.map_or_else(
|
||||
|| self.codex_home.join("agents"),
|
||||
|repo_root| repo_root.join(".codex").join("agents"),
|
||||
);
|
||||
let subagents_count = count_missing_subagents(&source_subagents, &target_subagents)?;
|
||||
if subagents_count > 0 {
|
||||
let subagent_names = missing_subagent_names(&source_subagents, &target_subagents)?;
|
||||
items.push(ExternalAgentConfigMigrationItem {
|
||||
item_type: ExternalAgentConfigMigrationItemType::Subagents,
|
||||
description: format!(
|
||||
"Migrate subagents from {} to {}",
|
||||
source_subagents.display(),
|
||||
target_subagents.display()
|
||||
),
|
||||
cwd: cwd.clone(),
|
||||
details: Some(MigrationDetails {
|
||||
subagents: named_migrations(subagent_names),
|
||||
..Default::default()
|
||||
}),
|
||||
});
|
||||
emit_migration_metric(
|
||||
EXTERNAL_AGENT_CONFIG_DETECT_METRIC,
|
||||
ExternalAgentConfigMigrationItemType::Subagents,
|
||||
Some(subagents_count),
|
||||
);
|
||||
}
|
||||
|
||||
let source_agents_md = if let Some(repo_root) = repo_root {
|
||||
find_repo_agents_md_source(repo_root)?
|
||||
} else {
|
||||
@@ -357,8 +540,8 @@ impl ExternalAgentConfigService {
|
||||
),
|
||||
cwd: None,
|
||||
details: Some(MigrationDetails {
|
||||
plugins: Vec::new(),
|
||||
sessions,
|
||||
..Default::default()
|
||||
}),
|
||||
});
|
||||
emit_migration_metric(
|
||||
@@ -379,6 +562,41 @@ impl ExternalAgentConfigService {
|
||||
.unwrap_or_else(|| PathBuf::from(".agents").join("skills"))
|
||||
}
|
||||
|
||||
fn mcp_settings(
|
||||
&self,
|
||||
repo_root: Option<&Path>,
|
||||
source_settings: Option<JsonValue>,
|
||||
) -> io::Result<Option<JsonValue>> {
|
||||
if repo_root.is_some() && source_settings.is_none() {
|
||||
let home_settings = self.external_agent_home.join("settings.json");
|
||||
match read_external_settings(&home_settings) {
|
||||
Ok(settings) => Ok(settings),
|
||||
Err(err) => {
|
||||
tracing::warn!(
|
||||
path = %home_settings.display(),
|
||||
error = %err,
|
||||
"ignoring invalid external agent home settings during repo MCP migration"
|
||||
);
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Ok(source_settings)
|
||||
}
|
||||
}
|
||||
|
||||
fn source_root(&self, repo_root: Option<&Path>) -> PathBuf {
|
||||
repo_root.map_or_else(
|
||||
|| {
|
||||
self.external_agent_home
|
||||
.parent()
|
||||
.map(Path::to_path_buf)
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
},
|
||||
Path::to_path_buf,
|
||||
)
|
||||
}
|
||||
|
||||
fn detect_plugin_migration(
|
||||
&self,
|
||||
source_settings: &Path,
|
||||
@@ -445,11 +663,11 @@ impl ExternalAgentConfigService {
|
||||
|
||||
let local_details = (!local_plugins.is_empty()).then_some(MigrationDetails {
|
||||
plugins: local_plugins,
|
||||
sessions: Vec::new(),
|
||||
..Default::default()
|
||||
});
|
||||
let remote_details = (!remote_plugins.is_empty()).then_some(MigrationDetails {
|
||||
plugins: remote_plugins,
|
||||
sessions: Vec::new(),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
Ok((local_details, remote_details))
|
||||
@@ -535,7 +753,8 @@ impl ExternalAgentConfigService {
|
||||
}
|
||||
|
||||
fn import_config(&self, cwd: Option<&Path>) -> io::Result<()> {
|
||||
let (source_settings, target_config) = if let Some(repo_root) = find_repo_root(cwd)? {
|
||||
let repo_root = find_repo_root(cwd)?;
|
||||
let (source_settings, target_config) = if let Some(repo_root) = repo_root.as_ref() {
|
||||
(
|
||||
repo_root.join(EXTERNAL_AGENT_DIR).join("settings.json"),
|
||||
repo_root.join(".codex").join("config.toml"),
|
||||
@@ -586,6 +805,112 @@ impl ExternalAgentConfigService {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
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() {
|
||||
(
|
||||
repo_root.join(EXTERNAL_AGENT_DIR).join("settings.json"),
|
||||
repo_root.join(".codex").join("config.toml"),
|
||||
)
|
||||
} else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) {
|
||||
return Ok(());
|
||||
} else {
|
||||
(
|
||||
self.external_agent_home.join("settings.json"),
|
||||
self.codex_home.join("config.toml"),
|
||||
)
|
||||
};
|
||||
let settings = self.mcp_settings(
|
||||
repo_root.as_deref(),
|
||||
read_external_settings(&source_settings)?,
|
||||
)?;
|
||||
let migrated = build_mcp_config_from_external(
|
||||
self.source_root(repo_root.as_deref()).as_path(),
|
||||
Some(self.external_agent_home.as_path()),
|
||||
settings.as_ref(),
|
||||
)?;
|
||||
if is_empty_toml_table(&migrated) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let Some(target_parent) = target_config.parent() else {
|
||||
return Err(invalid_data_error("config target path has no parent"));
|
||||
};
|
||||
fs::create_dir_all(target_parent)?;
|
||||
if !target_config.exists() {
|
||||
write_toml_file(&target_config, &migrated)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let existing_raw = fs::read_to_string(&target_config)?;
|
||||
let mut existing = if existing_raw.trim().is_empty() {
|
||||
TomlValue::Table(Default::default())
|
||||
} else {
|
||||
toml::from_str::<TomlValue>(&existing_raw)
|
||||
.map_err(|err| invalid_data_error(format!("invalid existing config.toml: {err}")))?
|
||||
};
|
||||
if merge_missing_toml_values(&mut existing, &migrated)? {
|
||||
write_toml_file(&target_config, &existing)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_subagents(&self, cwd: Option<&Path>) -> io::Result<usize> {
|
||||
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);
|
||||
} else {
|
||||
(
|
||||
self.external_agent_home.join("agents"),
|
||||
self.codex_home.join("agents"),
|
||||
)
|
||||
};
|
||||
|
||||
import_subagents(&source_agents, &target_agents)
|
||||
}
|
||||
|
||||
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)? {
|
||||
(
|
||||
repo_root.join(EXTERNAL_AGENT_DIR),
|
||||
repo_root.join(".codex").join("hooks.json"),
|
||||
)
|
||||
} else if cwd.is_some_and(|cwd| !cwd.as_os_str().is_empty()) {
|
||||
return Ok(());
|
||||
} else {
|
||||
(
|
||||
self.external_agent_home.clone(),
|
||||
self.codex_home.join("hooks.json"),
|
||||
)
|
||||
};
|
||||
|
||||
import_hooks(&source_external_agent_dir, &target_hooks)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn import_commands(&self, cwd: Option<&Path>) -> io::Result<usize> {
|
||||
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);
|
||||
} else {
|
||||
(
|
||||
self.external_agent_home.join("commands"),
|
||||
self.home_target_skills_dir(),
|
||||
)
|
||||
};
|
||||
|
||||
import_commands(&source_commands, &target_skills)
|
||||
}
|
||||
|
||||
fn import_skills(&self, cwd: Option<&Path>) -> io::Result<usize> {
|
||||
let (source_skills, target_skills) = if let Some(repo_root) = find_repo_root(cwd)? {
|
||||
(
|
||||
@@ -730,7 +1055,7 @@ fn extract_plugin_migration_details(
|
||||
|
||||
Some(MigrationDetails {
|
||||
plugins,
|
||||
sessions: Vec::new(),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1167,6 +1492,21 @@ fn write_toml_file(path: &Path, value: &TomlValue) -> io::Result<()> {
|
||||
fs::write(path, format!("{}\n", serialized.trim_end()))
|
||||
}
|
||||
|
||||
fn migrated_mcp_server_names(value: &TomlValue) -> Vec<String> {
|
||||
value
|
||||
.get("mcp_servers")
|
||||
.and_then(TomlValue::as_table)
|
||||
.map(|servers| servers.keys().cloned().collect())
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn named_migrations(names: Vec<String>) -> Vec<NamedMigration> {
|
||||
names
|
||||
.into_iter()
|
||||
.map(|name| NamedMigration { name })
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn is_empty_toml_table(value: &TomlValue) -> bool {
|
||||
match value {
|
||||
TomlValue::Table(table) => table.is_empty(),
|
||||
@@ -1193,10 +1533,18 @@ fn migration_metric_tags(
|
||||
ExternalAgentConfigMigrationItemType::AgentsMd => "agents_md",
|
||||
ExternalAgentConfigMigrationItemType::Plugins => "plugins",
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => "mcp_server_config",
|
||||
ExternalAgentConfigMigrationItemType::Subagents => "subagents",
|
||||
ExternalAgentConfigMigrationItemType::Hooks => "hooks",
|
||||
ExternalAgentConfigMigrationItemType::Commands => "commands",
|
||||
ExternalAgentConfigMigrationItemType::Sessions => "sessions",
|
||||
};
|
||||
let mut tags = vec![("migration_type", migration_type.to_string())];
|
||||
if item_type == ExternalAgentConfigMigrationItemType::Skills {
|
||||
if matches!(
|
||||
item_type,
|
||||
ExternalAgentConfigMigrationItemType::Skills
|
||||
| ExternalAgentConfigMigrationItemType::Subagents
|
||||
| ExternalAgentConfigMigrationItemType::Commands
|
||||
) {
|
||||
tags.push(("skills_count", skills_count.unwrap_or(0).to_string()));
|
||||
}
|
||||
tags
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,17 +2,22 @@ use crate::config::external_agent_config::ExternalAgentConfigDetectOptions;
|
||||
use crate::config::external_agent_config::ExternalAgentConfigMigrationItem as CoreMigrationItem;
|
||||
use crate::config::external_agent_config::ExternalAgentConfigMigrationItemType as CoreMigrationItemType;
|
||||
use crate::config::external_agent_config::ExternalAgentConfigService;
|
||||
use crate::config::external_agent_config::NamedMigration as CoreNamedMigration;
|
||||
use crate::config::external_agent_config::PendingPluginImport;
|
||||
use crate::error_code::internal_error;
|
||||
use crate::error_code::invalid_params;
|
||||
use codex_app_server_protocol::CommandMigration;
|
||||
use codex_app_server_protocol::ExternalAgentConfigDetectParams;
|
||||
use codex_app_server_protocol::ExternalAgentConfigDetectResponse;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportParams;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItem;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItemType;
|
||||
use codex_app_server_protocol::HookMigration;
|
||||
use codex_app_server_protocol::JSONRPCErrorError;
|
||||
use codex_app_server_protocol::McpServerMigration;
|
||||
use codex_app_server_protocol::MigrationDetails;
|
||||
use codex_app_server_protocol::PluginsMigration;
|
||||
use codex_app_server_protocol::SubagentMigration;
|
||||
use codex_external_agent_sessions::ExternalAgentSessionMigration as CoreSessionMigration;
|
||||
use codex_external_agent_sessions::PendingSessionImport;
|
||||
use codex_external_agent_sessions::PrepareSessionImportsError;
|
||||
@@ -68,6 +73,13 @@ impl ExternalAgentConfigApi {
|
||||
CoreMigrationItemType::McpServerConfig => {
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig
|
||||
}
|
||||
CoreMigrationItemType::Subagents => {
|
||||
ExternalAgentConfigMigrationItemType::Subagents
|
||||
}
|
||||
CoreMigrationItemType::Hooks => ExternalAgentConfigMigrationItemType::Hooks,
|
||||
CoreMigrationItemType::Commands => {
|
||||
ExternalAgentConfigMigrationItemType::Commands
|
||||
}
|
||||
CoreMigrationItemType::Sessions => {
|
||||
ExternalAgentConfigMigrationItemType::Sessions
|
||||
}
|
||||
@@ -92,6 +104,30 @@ impl ExternalAgentConfigApi {
|
||||
title: session.title,
|
||||
})
|
||||
.collect(),
|
||||
mcp_servers: details
|
||||
.mcp_servers
|
||||
.into_iter()
|
||||
.map(|mcp_server| McpServerMigration {
|
||||
name: mcp_server.name,
|
||||
})
|
||||
.collect(),
|
||||
hooks: details
|
||||
.hooks
|
||||
.into_iter()
|
||||
.map(|hook| HookMigration { name: hook.name })
|
||||
.collect(),
|
||||
subagents: details
|
||||
.subagents
|
||||
.into_iter()
|
||||
.map(|subagent| SubagentMigration {
|
||||
name: subagent.name,
|
||||
})
|
||||
.collect(),
|
||||
commands: details
|
||||
.commands
|
||||
.into_iter()
|
||||
.map(|command| CommandMigration { name: command.name })
|
||||
.collect(),
|
||||
}),
|
||||
})
|
||||
.collect(),
|
||||
@@ -182,6 +218,15 @@ impl ExternalAgentConfigApi {
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig => {
|
||||
CoreMigrationItemType::McpServerConfig
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Subagents => {
|
||||
CoreMigrationItemType::Subagents
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Hooks => {
|
||||
CoreMigrationItemType::Hooks
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Commands => {
|
||||
CoreMigrationItemType::Commands
|
||||
}
|
||||
ExternalAgentConfigMigrationItemType::Sessions => {
|
||||
CoreMigrationItemType::Sessions
|
||||
}
|
||||
@@ -209,6 +254,30 @@ impl ExternalAgentConfigApi {
|
||||
title: session.title,
|
||||
})
|
||||
.collect(),
|
||||
mcp_servers: details
|
||||
.mcp_servers
|
||||
.into_iter()
|
||||
.map(|mcp_server| CoreNamedMigration {
|
||||
name: mcp_server.name,
|
||||
})
|
||||
.collect(),
|
||||
hooks: details
|
||||
.hooks
|
||||
.into_iter()
|
||||
.map(|hook| CoreNamedMigration { name: hook.name })
|
||||
.collect(),
|
||||
subagents: details
|
||||
.subagents
|
||||
.into_iter()
|
||||
.map(|subagent| CoreNamedMigration {
|
||||
name: subagent.name,
|
||||
})
|
||||
.collect(),
|
||||
commands: details
|
||||
.commands
|
||||
.into_iter()
|
||||
.map(|command| CoreNamedMigration { name: command.name })
|
||||
.collect(),
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -48,6 +48,7 @@ use codex_app_server_protocol::ExperimentalFeatureEnablementSetParams;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportCompletedNotification;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportParams;
|
||||
use codex_app_server_protocol::ExternalAgentConfigImportResponse;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItem;
|
||||
use codex_app_server_protocol::ExternalAgentConfigMigrationItemType;
|
||||
use codex_app_server_protocol::InitializeResponse;
|
||||
use codex_app_server_protocol::JSONRPCError;
|
||||
@@ -1172,6 +1173,7 @@ impl MessageProcessor {
|
||||
request_id: ConnectionRequestId,
|
||||
params: ExternalAgentConfigImportParams,
|
||||
) -> Result<(), JSONRPCErrorError> {
|
||||
let needs_runtime_refresh = migration_items_need_runtime_refresh(¶ms.migration_items);
|
||||
let has_plugin_imports = params.migration_items.iter().any(|item| {
|
||||
matches!(
|
||||
item.item_type,
|
||||
@@ -1182,7 +1184,7 @@ impl MessageProcessor {
|
||||
.external_agent_config_api
|
||||
.prepare_pending_session_imports(¶ms)?;
|
||||
let pending_plugin_imports = self.external_agent_config_api.import(params).await?;
|
||||
if has_plugin_imports {
|
||||
if needs_runtime_refresh {
|
||||
self.handle_config_mutation().await;
|
||||
}
|
||||
for pending_session_import in pending_session_imports {
|
||||
@@ -1241,5 +1243,60 @@ impl MessageProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
fn migration_items_need_runtime_refresh(items: &[ExternalAgentConfigMigrationItem]) -> bool {
|
||||
items.iter().any(|item| {
|
||||
matches!(
|
||||
item.item_type,
|
||||
ExternalAgentConfigMigrationItemType::Config
|
||||
| ExternalAgentConfigMigrationItemType::Skills
|
||||
| ExternalAgentConfigMigrationItemType::McpServerConfig
|
||||
| ExternalAgentConfigMigrationItemType::Hooks
|
||||
| ExternalAgentConfigMigrationItemType::Commands
|
||||
| ExternalAgentConfigMigrationItemType::Plugins
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tracing_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn migration_item(
|
||||
item_type: ExternalAgentConfigMigrationItemType,
|
||||
) -> ExternalAgentConfigMigrationItem {
|
||||
ExternalAgentConfigMigrationItem {
|
||||
item_type,
|
||||
description: String::new(),
|
||||
cwd: None,
|
||||
details: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn migration_items_that_update_runtime_sources_trigger_refresh() {
|
||||
assert!(migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::Config,
|
||||
)]));
|
||||
assert!(migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::Skills,
|
||||
)]));
|
||||
assert!(migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::McpServerConfig,
|
||||
)]));
|
||||
assert!(migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::Hooks,
|
||||
)]));
|
||||
assert!(migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::Commands,
|
||||
)]));
|
||||
assert!(migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::Plugins,
|
||||
)]));
|
||||
assert!(!migration_items_need_runtime_refresh(&[migration_item(
|
||||
ExternalAgentConfigMigrationItemType::Sessions,
|
||||
)]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
load("//:defs.bzl", "codex_rust_crate")
|
||||
|
||||
codex_rust_crate(
|
||||
name = "external-agent-migration",
|
||||
crate_name = "codex_external_agent_migration",
|
||||
)
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "codex-external-agent-migration"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
name = "codex_external_agent_migration"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codex-hooks = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
toml = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
tempfile = { workspace = true }
|
||||
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,28 @@ mod registry;
|
||||
mod schema;
|
||||
mod types;
|
||||
|
||||
/// Hook event names as they appear in hooks JSON and config files.
|
||||
pub const HOOK_EVENT_NAMES: [&str; 6] = [
|
||||
"PreToolUse",
|
||||
"PermissionRequest",
|
||||
"PostToolUse",
|
||||
"SessionStart",
|
||||
"UserPromptSubmit",
|
||||
"Stop",
|
||||
];
|
||||
|
||||
/// Hook event names whose matcher fields are meaningful during dispatch.
|
||||
///
|
||||
/// Other events can appear in hooks JSON, but Codex ignores their matcher
|
||||
/// fields because those events do not dispatch against a tool or session-start
|
||||
/// source.
|
||||
pub const HOOK_EVENT_NAMES_WITH_MATCHERS: [&str; 4] = [
|
||||
"PreToolUse",
|
||||
"PermissionRequest",
|
||||
"PostToolUse",
|
||||
"SessionStart",
|
||||
];
|
||||
|
||||
pub use events::permission_request::PermissionRequestDecision;
|
||||
pub use events::permission_request::PermissionRequestOutcome;
|
||||
pub use events::permission_request::PermissionRequestRequest;
|
||||
|
||||
@@ -799,7 +799,7 @@ mod tests {
|
||||
plugin_names: vec!["warehouse".to_string()],
|
||||
},
|
||||
],
|
||||
sessions: Vec::new(),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user