Extensions 怎样刷新 Agents、Hooks、Skills 与 MCP¶
上一节看到 Compression 怎样替换正在使用的 Chat History,也分清了 GEMINI.md 和 Memory Service 该保存哪些跨会话知识。现在从运行时能力继续往下看:Gemini CLI 的 Extension 可以带来 MCP Server、Tool 排除项、Policy、Hook、Skill 和 Agent,安装目录只说明这些能力来自哪里,只有启用或停用 Extension 时同步刷新多个运行时 Registry(注册表),模型实际能用的能力才会跟着改变。
Extension Manifest / 目录
├→ MCP Client Manager
├→ Tool Registry
├→ Hook System
├→ Agent Registry
└→ Skill Manager
↓
当前 Session 的 Prompt 与工具面
第 1 站:Extension 切换是一次多 Registry 更新¶
await this.config.getMcpClientManager()!.startExtension(extension)
await this.maybeRefreshGeminiTools(extension)
await this.config.getHookSystem()?.initialize()
await this.config.getAgentRegistry().reload()
await this.config.reloadSkills()
- 调用者:Extension Manager 启用、停用或重载某个 Extension。
- 输入:Extension 描述、信任状态与有效配置。
- 状态变化:连接 MCP,重算 Tools,重新初始化 Hooks、Agents 和 Skills。
- 返回:刷新后的运行时能力集合。
- 下一站:后续 Turn 构造新的 Prompt 与 Function Declarations。
只要其中一步失败,运行时就可能停在「一部分能力已经刷新、另一部分还是旧的」这种状态,因此实现既要写清更新顺序,也要处理怎么回滚或重新初始化。整轮刷新并不是一次原子替换。
Hook:先匹配和去重,再决定整批执行方式¶
const matchingEntries = hookEntries.filter(...)
const deduplicatedEntries = this.deduplicateHooks(matchingEntries)
const sequential = deduplicatedEntries
.some((entry) => entry.sequential === true)
- 调用者:Hook System 在某个生命周期事件发生时。
- 输入:事件、Matcher、来源 Extension 和 Hook 配置。
- 状态变化:筛选、去重并生成执行计划;只要一项要求串行,整批按顺序执行。
- 返回:Hook Plan 与后续结果集合。
- 下一站:Executor 按事件契约合并允许、阻断或附加上下文。
「整批串行」能避免有先后依赖的 Hook(钩子)和其他 Hook 交错执行,可只要其中一个 Hook 很慢,后面的所有项都会被它堵住。因此你不能只核对单个 Hook 的配置,还要针对具体事件检查整批任务怎样超时、出错后又怎样收场。
Skill:同名覆盖顺序和激活是两步¶
第 2 站:加载顺序决定同名 Skill 的最终定义¶
await this.discoverBuiltinSkills()
// extension skills -> user skills -> workspace skills
skillMap.set(newSkill.name, newSkill)
- 调用者:启动和 Extension 刷新后的 Skill Manager。
- 输入:Builtin、Extension、User 与 Workspace Roots。
- 状态变化:按加载顺序写入 Map,后加载的同名 Skill 覆盖前者。
- 返回:当前 Skill Catalog。
- 下一站:模型或用户调用 Activate Skill。
源码:查看 Skill 激活
skillManager.activateSkill(skillName)
this.config.getWorkspaceContext()
.addDirectory(path.dirname(skill.location))
llmContent: `<activated_skill ...>${skill.body}...`
只有激活 Skill(技能)以后,系统才会把正文送回模型,同时把 Skill 目录加入可读取的资源范围。因此,目录里能够列出某个 Skill,只能说明系统发现了它,不能说明它的正文已经进入 Context。发现不等于激活。
MCP:发现前有信任与用户配置短路¶
第 3 站:连接成功后才把能力写入 Registry¶
if (!finalConfig.command && !finalConfig.url && !finalConfig.httpUrl) return
if (this.isBlockedBySettings(name)) return
if (await this.isDisabledByUser(name)) return
if (!this.cliConfig.isTrustedFolder()) return
await client.connect()
await client.discoverInto(this.cliConfig, targetRegistries)
- 调用者:启动或 Extension 启用流程。
- 输入:MCP Server Config、Blocklist、用户开关和目录信任。
- 状态变化:通过前置门后建立连接,发现 Tools/Prompts/Resources 并写入目标 Registry。
- 返回:活动 MCP Client 与发现结果。
- 下一站:Tool Registry 过滤模型可见工具,调用时再次经过 Policy。
Agent:本地与远程不是同一个执行器¶
Agent Registry 能发现 Agent Definition(智能体定义),Local Executor 则会在当前进程里创建子会话并配好工具,Remote A2A 路径通过协议交换 Message、Artifact、状态和取消请求。两条路径看起来都可以通过 Agent Tool 调用,可它们的身份归属、认证方式、超时处理和结果边界各不相同。
需要隔离 Context 或者把一段长任务单独跑起来时,创建子 Agent 很合适。如果只是批量读取文件,直接调用普通工具反而省事。父 Agent 收到结果以后,还得核对 Stop Reason 和 Artifact,不能看到一条完成通知就把子运行里的错误盖过去。通知不是结论。
到这里,你已经看过 Extension 按什么顺序刷新各项能力,Hook 怎样整批执行,同名 Skill 怎样覆盖又怎样激活,MCP 连接前要过哪些门,以及 Agent 在本地和远程分别怎么跑。不过,使用者最后能看到什么,还取决于交互 CLI、非交互输出、IDE 与 A2A 各自暴露哪些事件、怎样表示停止,下一篇就来逐项划清这些输出边界。
下一篇:CLI、非交互输出、IDE 与 A2A。