Skip to content

[Bug]: 1:工具与插件页面 Skills 始终显示 0,2:插件无法跨供应商 Profile 继承 #549

@Ghostkyi

Description

@Ghostkyi

当前行为 (Current Behavior)

观察到两个问题:

问题 1:Skills 始终显示 0

relayContextConfigContents 为空时,Codex++ 如预期显示 "Skills: 0"。但即便在 ~/.codex/skills/(106 个 SKILL.md)、~/.agents/skills/(48 个)、~/.codex/vendor_imports/skills/(39 个)中存在大量 Skill 文件,"工具与插件"页仍然只从 TOML [skills.*] 节中读取,完全忽略文件系统的技能定义。

问题 2:插件配置无法跨 Profile 继承

每个 Relay Profile 的 configContents 中包含独立的 [plugins.*][marketplaces.*][features][memories] 等配置。新建 Profile(例如 MiniMax)时不会自动从已有 Profile 复制这些上下文。切换 Profile 后,之前可用的插件可能在新的 config.toml 中完全消失。

relayCommonConfigContents 字段在全新安装时为空字符串。即便所有 Profile 的 useCommonConfig = true,空 common config 也使得共享机制形同虚设。


预期行为 (Expected Behavior)

  1. Skills 应基于文件系统检测read_live_context_entries 应扫描 ~/.codex/skills/~/.agents/skills/~/.codex/vendor_imports/skills/,解析 SKILL.md 的 YAML frontmatter 中的 namedescription,与 TOML 型 skill 合并展示。

  2. 新 Profile 应自动继承共享配置:若 relayCommonConfigContents 非空,新 Profile 应默认 useCommonConfig = true,并在 UI 中提示"将继承 X 个插件、Y 个技能"。

  3. 首次启动应自动填充 relayCommonConfigContents:当该字段为空而 config.toml 已存在时,应自动调用 extract_common_config_from_config() 初始化。


复现步骤 (Reproduction Steps)

Skills 显示 0

  1. 安装 Codex 并安装多个 Skill(例如从 Marketplace 导入)
  2. 通过 Codex++ 启动器启动
  3. 打开 "Codex++ 管理工具" → "工具与插件" 页
  4. 观察到:Skills 显示 0,但实际在对话中可正常使用 @skill-name 调用

插件跨 Profile 丢失

  1. 配置 Profile A(含完整插件),应用后 config.toml 正常
  2. 创建 Profile B(新 API),configContents 仅含 model/provider/base_url
  3. 切换到 Profile B → config.toml 中 [plugins] 全部消失
  4. 对话框 @-mention 列表无任何插件

日志 / 配置片段 (Logs / Config)

Codex++ 运行状态

Status: running
Codex App: Codex v26.527.7698.0 (Windows Store)
Codex++: v1.1.9
Debug port: 9229, Helper port: 57321

relayCommonConfigContents(修复前)

(空字符串,长度 0)

relayContextConfigContents(始终为空)

(空字符串,长度 0)

MiniMax Profile configContents(修复前示例)

model = "MiniMax-M3"
model_provider = "custom"
model_reasoning_effort = "high"

[model_providers.custom]
name = "custom"
wire_api = "responses"
requires_openai_auth = true
base_url = "https://api.minimaxi.com/v1"
experimental_bearer_token = "[REDACTED]"

[features]
# 空节——无 goals, memories

[mcp_servers.node_repl]
# ... (仅 node_repl)

DeepSeek Profile configContents(有插件但仅在单 profile 内)

model_provider = "custom"
model = "deepseek-v4-pro"
# ...
[plugins."computer-use@openai-bundled"]
enabled = true
[plugins."documents@openai-primary-runtime"]
enabled = true
# ... 共 12 个 plugin 节(全部仅存于此 profile 内)

技能目录结构

~/.codex/skills/        → 106 SKILL.md(YAML frontmatter: name + description)
~/.agents/skills/       → 48 SKILL.md
~/.codex/vendor_imports/skills/  → 39 SKILL.md

完整脱敏版 settings.json 见附件 redacted-settings.json


环境 (Environment)

项目
OS Windows 11 24H2
Codex 版本 26.527.7698.0 (Windows Store)
Codex++ 版本 1.1.9
Codex++ 安装方式 CodexPlusPlus-1.1.9-windows-x64-setup.exe
wire_api responses
复现概率 100%

建议修复方案 (Proposed Fix)

Patch 1:文件系统技能检测(relay_config.rs

新增函数扫描 ~/.codex/skills/~/.agents/skills/~/.codex/vendor_imports/skills/

use std::collections::HashSet;

pub fn discover_file_based_skills(codex_home: &Path) -> Vec<CodexContextEntry> {
    let agents_home = dirs_home().join(".agents").join("skills");
    let vendor_home = codex_home.join("vendor_imports").join("skills");
    let roots = [codex_home.join("skills"), agents_home, vendor_home];

    let mut entries: Vec<CodexContextEntry> = Vec::new();
    let mut seen: HashSet<String> = HashSet::new();

    for root in &roots {
        if !root.exists() { continue; }
        if let Ok(walker) = walkdir::WalkDir::new(root).max_depth(4).into_iter() {
            for entry in walker.filter_map(|e| e.ok()) {
                if entry.file_name() != "SKILL.md" { continue; }
                let Ok(content) = std::fs::read_to_string(entry.path()) else { continue; };
                let (id, title, summary) = parse_skill_frontmatter(&content);
                if !seen.insert(id.clone()) { continue; }
                entries.push(CodexContextEntry {
                    id, kind: "skill".to_string(), title, summary,
                    toml_body: String::new(), enabled: true,
                });
            }
        }
    }
    entries.sort_by(|a, b| a.title.cmp(&b.title));
    entries
}

fn parse_skill_frontmatter(content: &str) -> (String, String, String) {
    let mut name = String::new();
    let mut desc = String::new();
    let mut in_fm = false;
    for line in content.lines() {
        let t = line.trim();
        if t == "---" { in_fm = !in_fm; continue; }
        if !in_fm { continue; }
        if let Some(v) = t.strip_prefix("name:") {
            name = v.trim().trim_matches('"').to_string();
        } else if let Some(v) = t.strip_prefix("description:") {
            desc = v.trim().trim_matches('"').to_string();
            if desc.len() > 120 { desc = format!("{}...", &desc[..117]); }
        }
    }
    if name.is_empty() { name = "unknown".to_string(); }
    (name.clone(), name, desc)
}

list_context_entries_from_common_config 中合并:

pub fn list_context_entries_from_common_config(
    common_config: &str,
    codex_home: Option<&Path>,
) -> anyhow::Result<CodexContextEntries> {
    // ...existing TOML parsing...
    let mut entries = CodexContextEntries { /* ... */ };
    if let Some(home) = codex_home {
        let file_skills = discover_file_based_skills(home);
        for fs in file_skills {
            if !entries.skills.iter().any(|s| s.id == fs.id) {
                entries.skills.push(fs);
            }
        }
    }
    Ok(entries)
}

新增 Cargo 依赖 (crates/codex-plus-core/Cargo.toml):

walkdir = "2"

Patch 2:首次启动自动初始化 relayCommonConfigContents

在 profile 应用逻辑中增加守卫:

if settings.relay_common_config_contents.trim().is_empty() {
    if let Ok(live) = std::fs::read_to_string(home.join("config.toml")) {
        if let Ok(common) = extract_common_config_from_config(&live) {
            if !common.trim().is_empty() {
                settings.relay_common_config_contents = common;
            }
        }
    }
}

Patch 3:管理工具新建 Profile 流程

在创建新 Profile 的 UI 步骤中:

  1. 展示"将继承 X 个插件、Y 个 MCP、Z 个技能"预览
  2. 提供"从其他 Profile 复制上下文"的下拉选择
  3. 默认勾选 useCommonConfig = true

额外建议 (Additional Recommendations)

  1. 诊断页增加配置校验:检测 relayCommonConfigContents 是否为空并显示警告;对比各 Profile 的 contextSelection 差异
  2. 一键修复按钮:从当前 config.toml 重新提取 relayCommonConfigContents
  3. Profile 创建模板化:定义 Profile 创建时的最小必需字段(model, provider, base_url),其余从 common config 继承

附件 (Attachments)

  • redacted-settings.json:脱敏后的完整 Codex++ settings.json(含 7 个 Profile 的完整对比数据)

redacted-settings.json

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions