// Codex 技能的展示 ID 与按 Profile 选择(与 Claude 侧的 providers/claude/skills.ts 对应) import fs from 'node:fs' import path from 'node:path' import { COMMUNITY_SKILL_PLUGIN, CUSTOM_SKILL_PLUGIN } from '@agent-server/shared' import type { SkillMetadata } from './protocol/v2' import { NO_CODEX_PLUGINS, type CodexPluginGrant } from '../types' /** 服务场景下始终禁用的系统技能:会联网下载或写入 Codex 自身目录 */ export const ALWAYS_DISABLED = new Set(['skill-installer', 'skill-creator', 'plugin-creator']) /** * 展示 ID: * - 系统技能 → codex-system: * - 从 openai/skills 安装的技能(CODEX_HOME/skills 下有标记文件)→ openai-skills: * - 本地技能目录与第三方技能目录(extraRoots,Codex 按 plugin.json 加了前缀)→ 原样,如 agent-skills:report-conventions * - 其他用户技能 → codex-user: */ export function skillDisplayId(s: Pick, codexHome: string): string { if (s.scope === 'system') return `codex-system:${s.name}` // Codex 用 SKILL.md 的 name 命名(可能是 "Excel Analysis"),Claude 用目录名;第三方技能统一按目录名,两个引擎 ID 一致 for (const plugin of [COMMUNITY_SKILL_PLUGIN, CUSTOM_SKILL_PLUGIN]) { if (s.name.startsWith(`${plugin}:`)) return `${plugin}:${path.basename(path.dirname(s.path))}` } if (s.name.includes(':')) return s.name const managedRoot = path.join(codexHome, 'skills') const dir = path.dirname(s.path) if (path.dirname(dir) === managedRoot && fs.existsSync(path.join(dir, '.agent-server.json'))) return `openai-skills:${s.name}` return `codex-user:${s.name}` } /** * 计算需要在本次对话中禁用的技能(按路径选择);选择"全部技能"时第三方技能仍需单独勾选。 * 远程插件带来的技能(pluginId 非空)跟随插件授权:全部开放时(Playground)全部保留,否则只保留开放的插件的技能 */ export function skillsToDisable( all: SkillMetadata[], selected: string[] | 'all', codexHome: string, localPlugin: string, plugins: CodexPluginGrant = NO_CODEX_PLUGINS, excludedFromAll: string[] = [], ): { path: string; enabled: false }[] { const excluded = new Set(excludedFromAll) const wanted = selected === 'all' ? null : new Set(selected.map(s => (s.includes(':') ? s : `${localPlugin}:${s}`))) return all .filter(s => { if (ALWAYS_DISABLED.has(s.name)) return true if (s.pluginId) return plugins !== 'all' && !plugins.pluginIds.includes(s.pluginId) const id = skillDisplayId(s, codexHome) return wanted === null ? id.startsWith(`${COMMUNITY_SKILL_PLUGIN}:`) || excluded.has(id) : !wanted.has(id) }) .map(s => ({ path: s.path, enabled: false as const })) }