diff --git a/docs/technical/【问题记录】DirectProject客户端Skill自然语言触发能力缺口-2026-09-01.md b/docs/technical/【问题记录】DirectProject客户端Skill自然语言触发能力缺口-2026-09-01.md new file mode 100644 index 000000000..03c9e4b64 --- /dev/null +++ b/docs/technical/【问题记录】DirectProject客户端Skill自然语言触发能力缺口-2026-09-01.md @@ -0,0 +1,185 @@ +# DirectProject 客户端 Skill 自然语言触发能力缺口 + +状态:待提 Issue,当前仅记录现状和候选方向,未修改代码。 + +## 1. 问题摘要 + +AGC 已经可以把用户导入的 Skill 安装到客户端,并在下一次 DirectProject 启动时注入真实 Codex app-server。使用 Codex 原生的 `$skill-name` 形式时,导入的 Skill 可以被读取并执行。 + +目前缺少的是:用户在普通自然语言中直接提到 Skill 名称时,AGC 没有把这次请求确定性地绑定为 Codex 的 Skill 输入项。结果是模型可能只看到用户提到了一个名称,并把“调用 Skill”误解成调用一个工具,返回“没有可调用接口”,而不是读取 `SKILL.md`。 + +这不是 Skill 导入失败,也不是需要把 Skill 改造成 MCP 工具;是 AGC 客户端没有复用 Codex 官方客户端的 Skill 输入适配层。 + +## 2. 当前 AGC 如何使用 Skill + +### 2.1 客户端导入 + +用户在客户端设置的“扩展”页面导入文件、目录或 zip。目录或 zip 中发现的每个 `SKILL.md` 会成为独立的 Skill 扩展项,可以单独启用、禁用、重命名和删除。 + +已识别的 Skill 导入后默认启用。扩展内容保存于 AGC 客户端扩展仓库,不直接写入用户全局 `CODEX_HOME`。 + +### 2.2 DirectProject 启动时注入 + +创建或复用 DirectProject 的 Codex app-server 时,AGC 会: + +1. 读取客户端扩展索引中 `enabled=true` 的 Skill; +2. 在本次隔离运行的临时 HOME 下创建客户端 Skill root; +3. 将每个 Skill 的运行时副本复制到该目录; +4. 对手动重命名的 Skill,只在运行时副本的 frontmatter 中使用当前名称; +5. 调用 Codex app-server 的 `skills/extraRoots/set`; +6. 调用 `skills/list` 重新发现 Skill。 + +AGC 侧的运行时目录准备位于 [`client_extensions.rs`](../../apps/ai-game-creator-shell/src-tauri/src/client_extensions.rs) 的 `prepare_enabled_client_skill_root`,app-server 的 Skill root 注册位于 [`codex_app_server.rs`](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs)。 + +Skill 配置从下一次 DirectProject 启动生效,当前已经运行的 Codex 会话不热更新。首页的 DirectHome 不作为客户端 Skill 的运行时入口。 + +### 2.3 当前可用的触发方式 + +Codex 原生显式触发方式是: + +```text +$import-smoke-skill 请只回复一行结果。 +``` + +这个方式已经在 AGC 中验证成功,测试 Skill 返回: + +```text +SKILL_IMPORT_OK +``` + +这证明导入、启用、运行时 Skill root 注入、Codex 发现和 Skill 正文执行链路已经打通。 + +## 3. 当前缺少的能力 + +### 3.1 普通自然语言名称没有确定性绑定 + +以下输入目前不能稳定触发导入的 Skill: + +```text +请调用 import-smoke-skill skill。 +``` + +```text +请明确使用 import-smoke-skill。 +``` + +实际结果是模型报告“当前没有可调用该 Skill 的工具接口”。这句话在“Skill 不是工具”这一点上没有错,但没有完成 Skill 指令的读取和应用。 + +### 3.2 AGC 没有发送结构化 Skill 输入项 + +Codex 官方 TUI 在识别 `$skill-name` 或用户从 Skill 提及菜单选择后,会向 `turn/start` 输入中追加类似内容: + +```json +{ + "type": "skill", + "name": "import-smoke-skill", + "path": ".../SKILL.md" +} +``` + +对应实现见 Codex 仓库中的 `codex-rs/tui/src/chatwidget/input_submission.rs`。随后 Codex core 会根据结构化输入加载 Skill 正文。 + +AGC 当前的 [`codex_app_server_turn_input`](../../apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server.rs) 只把用户消息整理成普通文本和图片输入,没有追加 `type: "skill"` 项。 + +### 3.3 “能发现”与“本轮已使用”没有明确的客户端绑定 + +AGC 启动时会设置 `skills/extraRoots` 并调用 `skills/list`,这只能证明 Skill 在当前 app-server 进程的发现范围内。它不等于本轮已经选中了某个 Skill,也不等于模型一定会读取该 Skill 的正文。 + +Codex 的 Skill 扩展会把可用 Skill 目录和使用规则放入模型上下文,但普通文本相关性判断主要交给模型;它不是 AGC 客户端已经完成的确定性绑定。 + +## 4. Codex 源码中的相关分层 + +### 4.1 模型侧使用规则 + +Codex 仓库中的 `codex-rs/ext/skills/src/catalog_prompt.rs` 会向模型说明:用户用 `$SkillName` 或普通文本点名 Skill,或者任务符合 Skill 描述时,应使用该 Skill。 + +这是一条模型行为指令,不是一个保证命中的 `if user_text.contains(...)` 路由器。普通自然语言是否命中,仍取决于模型能否看到对应目录、是否理解名称和描述,以及是否调用 `skills.read`。 + +### 4.2 显式 Skill 选择 + +Codex 仓库中的 `codex-rs/ext/skills/src/selection.rs` 会处理结构化 `UserInput::Skill`,也会解析文本中的 `$skill-name`。选中后,core 在回合开始阶段读取并注入 Skill 正文。 + +### 4.3 “隐式 Skill”不是普通自然语言路由 + +源码中的 `implicit invocation` 主要用于命令或脚本访问某个 Skill 的资源目录时记录/识别对应 Skill,入口在 Codex 仓库的 `codex-rs/ext/skills/src/invocation.rs`。它不是一个根据任意用户自然语言自动选择 Skill 的通用分类器。 + +### 4.4 相关性选择实验不是当前确定性注入 + +Codex 的 `shadow_selection` / `skill_search` 相关实现会对任务和 Skill 描述做词法/相关性排序,但当前是 shadow 观测路径,不直接替当前回合注入 Skill,不能作为 AGC 当前缺口的现成解决方案。 + +## 5. 复现步骤 + +准备一个普通的 Skill 导入目录,目录中放置一个 `SKILL.md`。例如: + +```markdown +--- +name: import-smoke-skill +description: 用于验证客户端导入和加载 Skill 的最小测试 Skill。 +--- + +当用户明确要求使用 `import-smoke-skill` 时,先单独输出一行 `SKILL_IMPORT_OK`,再继续回答用户的问题。 +``` + +该目录和文件可以由 Issue 提交者在自己的测试环境中临时创建,不依赖仓库外的固定目录或本机产物。 + +复现: + +1. 在客户端“扩展”中导入包含上述 `SKILL.md` 的目录; +2. 确认列表显示 `import-smoke-skill`、类型为 Skill、状态为“已启用”; +3. 新开一个 DirectProject 会话; +4. 依次发送: + + ```text + 请调用 import-smoke-skill skill,如果找不到这个skill,不要伪造结果。 + ``` + + ```text + 请明确使用 import-smoke-skill,只回复一行结果。 + ``` + + ```text + $import-smoke-skill 请只回复一行结果。 + ``` + +预期现象:前两条不会稳定加载 Skill,第三条返回 `SKILL_IMPORT_OK`。 + +## 6. 可能的修复方向 + +以下只是供 Issue 讨论的候选方向,不在本文锁定具体实现。 + +### 方向 A:客户端显式绑定用户点名的 Skill + +AGC 在发送回合前,根据当前已启用 Skill 列表识别用户明确提到的名称,并把匹配项转换成 Codex 原生的 `type: "skill"` 输入项,同时保留原始用户文本。 + +优点是行为确定、与官方 TUI 的协议一致;需要讨论名称边界、重名处理和用户文本中普通单词与 Skill 名称的冲突。 + +### 方向 B:增加 Skill 提及选择入口 + +在聊天输入框提供 Skill 名称补全或选择入口。用户选择后,客户端保存/发送结构化 Skill 绑定,而不是要求用户手写 `$`。 + +优点是用户不需要了解 Codex 协议标记;需要额外的前端交互设计,且要决定是否在 DirectProject 聊天区增加入口。 + +### 方向 C:仅支持 Codex 原生 `$skill-name` + +保持当前实现,只把 `$skill-name` 作为 Skill 的正式调用方式,并在测试和使用说明中明确这一点。 + +优点是改动最小;缺点是用户需要知道 Codex 的 Skill 提及语法,普通中文“请使用某 Skill”仍不会得到确定性支持。 + +### 方向 D:依赖模型根据 Skill 目录自行判断 + +让 Skill 目录和描述保持模型可见,由模型在普通任务中自行判断是否需要调用 `skills.read`。 + +这种方式可以覆盖“任务适合某 Skill”而不仅是精确名称,但结果受模型判断和上下文影响,不适合作为“用户明确点名后必须使用”的唯一保证。 + +## 7. Issue 建议关注点 + +提 Issue 时建议先确认产品契约,而不是直接指定实现: + +- “用户普通中文提到已启用 Skill 名称”是否必须确定性生效; +- 是否接受用户使用 Codex 原生 `$skill-name`; +- 是否需要聊天输入框提供 Skill 选择/补全; +- Skill 名称与 MCP Server 名称冲突时,客户端按哪类扩展解析; +- 没有找到匹配 Skill 时,是否只给出说明并继续普通对话; +- 是否需要在回合记录或状态 UI 中展示本轮实际注入了哪些 Skill。 + +本 Issue 不应扩大为:重新设计 Skill/MCP 导入格式、把 Skill 做成 MCP 工具、增加第三方内容安全扫描、增加脚本沙箱,或重做 Codex Plugin Runtime。