Claude Code 的 Skill 与 Plugin 扩展系统
这一章要解决的问题是:Claude Code 怎样让一个 Markdown 文件变成模型可调用的能力?又怎样把多个技能、命令、Hook、MCP Server 和配置打包成可安装、可更新、可审计的插件?
源码里的答案不是“读一个目录里的 md 文件”这么简单。Skill 是可调用的提示词模板,Plugin 是扩展能力的容器。前者影响模型如何行动,后者决定能力从哪里来、是否可信、如何安装、如何卸载、是否能触发 Hook 或提供新工具。
核心直觉
Skill 解决“能力如何被模型发现和调用”,Plugin 解决“能力如何被分发和治理”。Claude Code 把扩展系统做成多层信任模型:内置技能、用户技能、项目技能、MCP 远程技能、插件技能和企业托管技能拥有不同的加载、预算和权限边界。
先看源码入口
| 源码定位 | 关键符号 | 负责什么 |
|---|---|---|
skills/bundledSkills.ts | BundledSkillDefinition、registerBundledSkill() | 内置技能注册,把定义转换为标准 Command |
skills/bundled/index.ts | initBundledSkills() | 按 Feature Flag 和用户类型注册内置技能 |
skills/loadSkillsDir.ts | getSkillDirCommands()、parseSkillFrontmatterFields()、createSkillCommand() | 加载用户、项目、策略、附加目录和旧 commands 技能 |
tools/SkillTool/SkillTool.ts | SkillTool.call()、checkPermissions()、validateInput() | 模型调用 Skill 的入口,处理 inline/fork、远程技能和权限 |
tools/SkillTool/prompt.ts | SKILL_BUDGET_CONTEXT_PERCENT、formatCommandsWithinBudget() | 控制技能列表注入上下文的预算和截断 |
skills/mcpSkillBuilders.ts | registerMCPSkillBuilders() | 打破 MCP 技能加载和普通技能加载之间的循环依赖 |
utils/hooks/skillImprovement.ts | initSkillImprovement()、createSkillImprovementHook() | 从用户纠正中自动改进项目级技能 |
utils/skills/skillChangeDetector.ts | FILE_STABILITY_THRESHOLD_MS、skillsChanged | 监听技能文件变更,防抖后清缓存热重载 |
utils/plugins/schemas.ts | PluginManifestSchema | 插件清单 Zod Schema,定义插件能提供的 11 类组件 |
utils/plugins/pluginLoader.ts | marketplace plugins、session plugins | 插件发现、安装缓存和组件加载 |
utils/plugins/pluginOptionsStorage.ts | loadPluginOptions() | 敏感配置进 secure storage,非敏感配置进 settings |
这些入口形成两条链路:Skill 从文件到模型上下文;Plugin 从市场或本地目录到运行时组件注册。
总流程图
这张图里的关键点是:技能列表只是发现层,完整技能内容在调用时加载;插件只是来源之一,最终也要变成 Command、Skill、Hook 或 MCP 组件才能进入 Agent。
技能的本质:提示词命令
内置技能通过 BundledSkillDefinition 注册。这个类型本身已经把 Skill 的能力边界暴露出来。
export type BundledSkillDefinition = {
name: string
description: string
aliases?: string[]
whenToUse?: string
argumentHint?: string
allowedTools?: string[]
model?: string
disableModelInvocation?: boolean
userInvocable?: boolean
isEnabled?: () => boolean
hooks?: HooksSettings
context?: 'inline' | 'fork'
agent?: string
files?: Record<string, string>
getPromptForCommand: (
args: string,
context: ToolUseContext,
) => Promise<ContentBlockParam[]>
}
这段代码证明:Skill 不只是 Markdown 文本。它可以声明调用名、触发时机、允许工具、模型、执行上下文、附带文件和 Hook。但它的核心仍然是 getPromptForCommand(),也就是生成一段要注入模型上下文的内容。
几个字段尤其重要:
| 字段 | 说明 | 工程影响 |
|---|---|---|
whenToUse | 告诉模型什么时候该主动使用技能 | 进入技能发现列表,影响模型选择 |
allowedTools | 技能执行时自动授权的工具 | 敏感字段,需要权限确认 |
context | inline 或 fork | 决定污染主上下文还是隔离执行 |
disableModelInvocation | 禁止模型主动调用 | 大规模操作只能用户显式触发 |
files | 技能附带参考文件 | 首次调用时提取到磁盘 |
hooks | 技能附带 Hook | 能影响工具执行链,必须当作敏感能力 |
内置技能注册:记忆化 Promise 防竞态
registerBundledSkill() 会把定义转换为 Command。如果技能带 files,源码会延迟提取文件,并用同一个 Promise 防止并发重复写入。
if (files && Object.keys(files).length > 0) {
skillRoot = getBundledSkillExtractDir(definition.name)
let extractionPromise: Promise<string | null> | undefined
const inner = definition.getPromptForCommand
getPromptForCommand = async (args, ctx) => {
extractionPromise ??= extractBundledSkillFiles(definition.name, files)
const extractedDir = await extractionPromise
const blocks = await inner(args, ctx)
if (extractedDir === null) return blocks
return prependBaseDir(blocks, extractedDir)
}
}
这段代码证明:内置技能的参考文件不是启动时全部落盘,而是首次调用时惰性提取。extractionPromise ??= 保证多个并发调用等待同一个提取结果,避免两个调用者同时写同一批文件。
内置文件写入还用了 O_NOFOLLOW | O_EXCL、owner-only 权限和 per-process nonce。原文注释说明威胁模型:nonce 是主防线,O_NOFOLLOW 和 O_EXCL 是防符号链接预创建的补充防线。这和权限系统一样,是 fail-closed 的扩展加载策略。
用户技能加载:四层来源与 frontmatter
用户自定义技能通常长这样:
.claude/skills/
my-skill/
SKILL.md
reference.ts
SKILL.md 用 YAML frontmatter 声明元数据:
---
description: My custom skill
when_to_use: When the user asks for X
allowed-tools: Read, Grep, Bash
context: fork
model: opus
effort: high
arguments: [target, scope]
paths: src/components/**
---
getSkillDirCommands() 从多种来源并行加载:
const [
managedSkills,
userSkills,
projectSkillsNested,
additionalSkillsNested,
legacyCommands,
] = await Promise.all([
loadSkillsFromSkillsDir(managedSkillsDir, 'policySettings'),
loadSkillsFromSkillsDir(userSkillsDir, 'userSettings'),
// project / add-dir / legacy commands
])
这段代码证明:技能来源不是单一目录,而是策略管理、用户全局、项目本地、附加目录和旧版 commands 的组合。
| 来源 | 目录 | 开关或限制 |
|---|---|---|
| 策略管理 | @@INLINE_0@@/.claude/skills/ | 企业策略,除非 CLAUDE_CODE_DISABLE_POLICY_SKILLS |
| 用户全局 | ~/.claude/skills/ | 受 userSettings 和 skillsLocked 控制 |
| 项目本地 | .claude/skills/ | 受 projectSettings 和 skillsLocked 控制 |
--add-dir | @@INLINE_0@@/.claude/skills/ | 附加目录 |
| 旧版 commands | .claude/commands/ | 兼容路径,已废弃 |
skillsLocked 来自 isRestrictedToPluginOnly('skills')。如果企业策略要求只能使用插件技能,本地技能加载会被跳过。这说明 Skill 系统本身已经接入组织治理,不是纯个人脚本目录。
元数据解析不是随意读字段
解析入口是 parseSkillFrontmatterFields()。
export function parseSkillFrontmatterFields(
frontmatter: FrontmatterData,
markdownContent: string,
resolvedName: string,
): {
displayName: string | undefined
description: string
allowedTools: string[]
argumentHint: string | undefined
whenToUse: string | undefined
model: ReturnType<typeof parseUserSpecifiedModel> | undefined
disableModelInvocation: boolean
hooks: HooksSettings | undefined
executionContext: 'fork' | undefined
agent: string | undefined
effort: EffortValue | undefined
shell: FrontmatterShell | undefined
}
这段代码证明:Skill 元数据有固定解析边界。effort、model、hooks、agent、shell 都不是普通文本,而是会影响运行时行为的字段。
effort 解析采用宽容策略:无效值会被忽略并记录调试日志,而不是让整个技能加载失败。这说明技能加载对用户编辑错误保持容错,但对敏感能力仍然通过权限链控制。
技能调用:变量替换、shell 执行与 MCP 降权
createSkillCommand() 的 getPromptForCommand 会在调用时处理 Markdown。
安全边界在源码里很明确:
// Security: MCP skills are remote and untrusted - never execute inline
// shell commands (!`…` / ```! … ```) from their markdown body.
if (loadedFrom !== 'mcp') {
finalContent = await executeShellCommandsInPrompt(...)
}
这段代码证明:远程 MCP 技能可以提供 Markdown 指令,但不能让它的 Markdown 触发本地 shell。Claude Code 没有把所有技能来源等价对待,而是按来源降低执行能力。
条件技能:路径触发,而不是全部塞进上下文
Skill 可以通过 paths 声明只在特定文件路径相关时激活。
---
paths: src/components/**, src/hooks/**
---
加载时,带 paths 的技能不会直接进入活跃列表,而是放入 conditionalSkills。
for (const skill of deduplicatedSkills) {
if (
skill.type === 'prompt' &&
skill.paths &&
skill.paths.length > 0 &&
!activatedConditionalSkillNames.has(skill.name)
) {
newConditionalSkills.push(skill)
} else {
unconditionalSkills.push(skill)
}
}
这段代码证明:条件技能默认不占技能列表预算。只有当用户通过 Read、Write、Edit 等工具触碰匹配路径,activateConditionalSkillsForPaths() 才会用 gitignore 风格匹配把它激活。
一旦激活,activatedConditionalSkillNames 在清缓存时不会重置。这是“会话内保持激活”的语义:触摸过相关文件后,模型接下来都能看到该技能。
Monorepo 还有动态目录发现:操作深层文件时,系统会从文件目录向上走到 cwd,在每一级检查 .claude/skills/,并跳过 gitignore 路径。这个设计让包级技能可以随代码位置动态进入会话。
技能列表预算:只给 1% 上下文
技能发现列表会注入到 system-reminder,但这个列表不能无限增长。tools/SkillTool/prompt.ts 里有硬预算。
export const SKILL_BUDGET_CONTEXT_PERCENT = 0.01
export const CHARS_PER_TOKEN = 4
export const DEFAULT_CHAR_BUDGET = 8_000
export const MAX_LISTING_DESC_CHARS = 250
这段代码证明:技能列表预算是上下文窗口的 1%。以 200K token 窗口估算,约 8,000 字符。每个技能描述还受 250 字符硬上限。
formatCommandsWithinBudget() 使用三级降级:
| 层级 | 输出 | 触发条件 |
|---|---|---|
| Level 1 | 技能名 + 完整描述 | 总大小在预算内 |
| Level 2 | 内置技能完整描述,非内置技能截短描述 | 超预算但还能保留至少 20 字符描述 |
| Level 3 | 内置技能完整描述,非内置技能仅名称 | 描述预算太小 |
原文注释给出关键判断:列表只是 discovery,真正执行时 SkillTool 会加载完整内容。冗长 whenToUse 会浪费首轮 cache_creation tokens,却不一定提高匹配率。
容易误解
技能列表被截断,不代表技能内容被截断。列表负责让模型知道“有哪些能力”,调用后才加载完整 SKILL.md。
技能工具权限:安全属性白名单
并非所有技能调用都需要确认。SkillTool.checkPermissions 用白名单判断“只含安全属性”的技能可以自动授权。
const SAFE_SKILL_PROPERTIES = new Set([
'type', 'progressMessage', 'contentLength', 'model', 'effort',
'source', 'name', 'description', 'isEnabled', 'isHidden',
'aliases', 'argumentHint', 'whenToUse', 'paths', 'version',
'disableModelInvocation', 'userInvocable', 'loadedFrom',
])
这段代码证明:自动授权是白名单模式,不是黑名单模式。新增字段默认不安全,直到明确加入白名单。allowedTools、hooks 这类字段不在白名单内,因此会触发用户确认。
权限规则支持精确匹配和前缀通配:
const ruleMatches = (ruleContent: string): boolean => {
const normalizedRule = ruleContent.startsWith('/')
? ruleContent.substring(1)
: ruleContent
if (normalizedRule === commandName) return true
if (normalizedRule.endsWith(':*')) {
const prefix = normalizedRule.slice(0, -2)
return commandName.startsWith(prefix)
}
return false
}
这段代码证明:用户可以写 Skill(review:*) allow 之类的规则,一次性授权命名空间下的技能。权限规则不只是 UI 开关,而是能表达组织化命名空间。
内联与分叉:技能执行的两种上下文
SkillTool.call() 的关键分支是 command.context === 'fork'。
if (command?.type === 'prompt' && command.context === 'fork') {
return executeForkedSkill(...)
}
// inline 执行路径
这段代码证明:技能可以选择污染主对话,或在隔离上下文中执行。
| 模式 | 行为 | 适合 |
|---|---|---|
inline | 技能提示词进入主消息流,后续对话继续携带这些指令 | 用户希望模型直接按流程继续操作 |
fork | 启动子 Agent 执行,返回摘要或结果 | 大量搜索、审查、生成,不希望主上下文膨胀 |
inline 模式通过 contextModifier 注入临时工具授权和模型覆盖,而不是永久修改全局状态。这一点很重要:Skill 的影响范围应当绑定在本次调用,而不是悄悄改变整个会话环境。
远程技能桥接:用注册器断开循环依赖
MCP 技能需要复用普通技能的 createSkillCommand() 和 parseSkillFrontmatterFields(),但直接互相 import 会形成循环依赖。源码用一次性注册模式拆开。
export type MCPSkillBuilders = {
createSkillCommand: typeof createSkillCommand
parseSkillFrontmatterFields: typeof parseSkillFrontmatterFields
}
let builders: MCPSkillBuilders | null = null
export function registerMCPSkillBuilders(b: MCPSkillBuilders): void {
builders = b
}
export function getMCPSkillBuilders(): MCPSkillBuilders {
if (!builders) {
throw new Error(
'MCP skill builders not registered - loadSkillsDir.ts has not been evaluated yet',
)
}
return builders
}
这段代码证明:MCP 技能不是另一套解析器,而是复用普通技能构建器,只是通过注册器避免模块加载环。原文还提到不能简单用动态 import(),因为 Bun 的 bunfs 虚拟文件系统和 dependency-cruiser 都会带来额外问题。
远程技能使用 _canonical_@@INLINE_0@@ 前缀,在 SkillTool.validateInput() 中绕过本地命令注册表,直接查找本会话已发现的远程技能。远程技能可自动授权,但这个授权放在 deny 规则之后,所以用户仍然可以配置 Skill(_canonical_:*) deny。
技能改进:从用户纠正回写 SKILL.md
SKILL_IMPROVEMENT 是技能系统最像“学习闭环”的部分。初始化受构建时和运行时双重门控。
export function initSkillImprovement(): void {
if (
feature('SKILL_IMPROVEMENT') &&
getFeatureValue_CACHED_MAY_BE_STALE('tengu_copper_panda', false)
) {
registerPostSamplingHook(createSkillImprovementHook())
}
}
这段代码证明:即使代码被编进内部构建,也要经过 GrowthBook flag 才运行。技能自动改写会改变用户文件,必须能远程关闭。
触发条件也很窄:
- 只分析项目级技能,通常带
projectSettings:前缀。 - 每 5 轮用户消息触发一次。
- 只看上次检查后的新增对话片段。
- 检测请求添加、修改、删除步骤、偏好表达和纠正。
- 应用阶段用独立侧信道 LLM 改写
.claude/skills/@@INLINE_0@@/SKILL.md。 - 改写时保留 frontmatter,不删除已有内容,除非用户明确替换。
这说明 Skill Improvement 不是把聊天记录随便追加到技能末尾,而是把用户纠正当作结构化更新信号。
文件改写后,skillChangeDetector.ts 用 chokidar 监听变化。Bun 环境里改用 polling,因为原生 fs.watch() 存在死锁问题。变更检测还有 1 秒稳定阈值和 300ms 防抖,避免模型或编辑器写一半时就重载。
插件:扩展能力的容器
Skill 解决单个能力的定义。Plugin 解决的是分发和治理。插件清单 plugin.json 的 Schema 很大,原文指出 schemas.ts 约 1681 行,是 Claude Code 中最大的单个 Schema 定义之一。
顶层结构由 11 个子 Schema 组合:
export const PluginManifestSchema = lazySchema(() =>
z.object({
...PluginManifestMetadataSchema().shape,
...PluginManifestHooksSchema().partial().shape,
...PluginManifestCommandsSchema().partial().shape,
...PluginManifestAgentsSchema().partial().shape,
...PluginManifestSkillsSchema().partial().shape,
...PluginManifestOutputStylesSchema().partial().shape,
...PluginManifestChannelsSchema().partial().shape,
...PluginManifestMcpServerSchema().partial().shape,
...PluginManifestLspServerSchema().partial().shape,
...PluginManifestSettingsSchema().partial().shape,
...PluginManifestUserConfigSchema().partial().shape,
}),
)
这段代码证明:插件不是“技能包”的同义词。它可以提供 Hook、Command、Agent、Skill、Output Style、Channel、MCP Server、LSP Server、Settings 和 User Config。除 Metadata 外,其余子 Schema 都 .partial(),说明插件可以只提供任意子集。
清单路径有安全约束:文件路径必须以 ./ 开头,不能包含 ..。市场名也有保留机制,阻止插件市场冒充官方名称,同时刻意不过度拦截间接变体,降低误伤社区市场的概率。
插件生命周期:发现、安装、验证、加载、启用
插件加载的来源按优先级分为 marketplace-based plugins 和 session-only plugins。
1. Marketplace-based plugins
2. Session-only plugins from --plugin-dir or SDK plugins option
典型生命周期:
版本化缓存的路径类似:
~/.claude/plugins/cache/{marketplace}/{plugin}/{version}/
这证明插件不会直接从原始位置随意运行。缓存按 marketplace、plugin 和 version 隔离,允许同一插件不同版本并存,也让卸载和离线启动更可控。
组件加载使用 memoize。getPluginCommands()、getPluginSkills() 这类函数不会在每次工具调用时重新解析插件文件。对 Hook 尤其重要,因为 Hook 可能在每次工具执行前后触发,重复读 keychain 和文件会变成明显延迟。
插件信任模型:持续警告、项目信任、敏感值隔离
插件比普通技能更危险,因为它可以带 Hook、MCP Server 和 settings。Claude Code 的策略是分层信任。
第一层是插件管理界面的持续警告。PluginTrustWarning 不是安装时闪一下,而是在 /plugin 管理界面持续展示“安装、更新或使用前确认信任”。这说明信任不是一次性动作,而是管理插件时反复提醒的前置条件。
第二层是项目级信任。TrustDialog 会审计项目目录里是否存在 MCP Server、Hook、bash 权限、API key helper、危险环境变量等。信任状态沿目录层级向上查找,父目录信任可以覆盖子目录。
第三层是敏感配置隔离。
// sensitive: true -> secureStorage
// everything else -> settings.json pluginConfigs[pluginId].options
加载时,安全存储覆盖普通配置:
return { ...nonSensitive, ...sensitive }
这段代码证明:如果用户手动在 settings 里写了同名值,secure storage 仍然优先。源码注释还指出 memoize 是性能和安全共同需要,因为 macOS keychain 读取会触发 security find-generic-password 子进程,频繁读会拖慢 Hook。
插件市场与依赖解析
市场源支持 URL、GitHub、git、npm、本地文件、目录、hostPattern、pathPattern、settings 等多种来源。加载函数采用 graceful degradation:一个市场失败不影响其他市场。
依赖字段也在 schema 里明确:
dependencies: z
.array(DependencyRefSchema())
.optional()
.describe(
'Plugins that must be enabled for this plugin to function. Bare names (no "@marketplace") are resolved against the declaring plugin\'s own marketplace.',
)
这段代码证明:插件依赖可以写裸名称,默认解析到声明插件所在市场。这减少了同市场依赖的冗余,也避免跨市场依赖被意外解析。
安装作用域有四级:
| 作用域 | 存储位置 | 可见范围 | 用途 |
|---|---|---|---|
user | ~/.claude/plugins/ | 所有项目 | 个人常用扩展 |
project | .claude/plugins/ | 项目协作者 | 团队标准能力 |
local | .claude-code.json | 当前本地会话 | 临时测试 |
managed | managed-settings.json | 策略控制 | 企业统一管理 |
这和技能来源一样,体现出 Claude Code 的扩展系统不是个人玩具,而是要进入团队和企业环境。
错误治理:PluginError 是 discriminated union
原文列出的 PluginError 类型包含 25 种左右错误变体。它不是字符串匹配。
export type PluginError =
| { type: 'path-not-found'; source: string; plugin?: string; path: string; component: PluginComponent }
| { type: 'git-auth-failed'; source: string; plugin?: string; gitUrl: string; authType: 'ssh' | 'https' }
| { type: 'git-timeout'; source: string; plugin?: string; gitUrl: string; operation: 'clone' | 'pull' }
| { type: 'network-error'; source: string; plugin?: string; url: string; details?: string }
| { type: 'manifest-parse-error'; source: string; plugin?: string; manifestPath: string; parseError: string }
| { type: 'manifest-validation-error'; source: string; plugin?: string; manifestPath: string; validationErrors: string[] }
| { type: 'dependency-unsatisfied'; source: string; plugin: string; dependency: string; reason: 'not-enabled' | 'not-found' }
| { type: 'generic-error'; source: string; plugin?: string; error: string }
这段代码证明:插件系统希望错误能被 UI 和遥测结构化处理。git-auth-failed 带 authType,依赖缺失带 reason,市场被策略阻止带 allowed sources。这样的错误可以指导用户下一步,而不是只给一个“加载失败”。
源码注释还说明部分错误类型先定义、后逐步接入生产。这是一种类型先行的演进策略:先把错误空间建模出来,再逐渐替换泛化错误。
状态和数据结构
| 数据结构 | 关键字段 | 行为影响 |
|---|---|---|
BundledSkillDefinition | allowedTools、context、files、hooks、getPromptForCommand | 决定技能如何被调用、是否提取文件、是否需要确认 |
PromptCommand | source、loadedFrom、whenToUse、paths | 统一内置、用户、MCP 和插件技能 |
SAFE_SKILL_PROPERTIES | 安全字段白名单 | 新增未知字段默认需要权限 |
conditionalSkills | Map@@INLINE_0@@ | 路径匹配前不注入上下文 |
activatedConditionalSkillNames | Set@@INLINE_0@@ | 会话内一旦激活就保持可见 |
PluginManifest | 11 个组件子 Schema | 插件能力的完整声明面 |
PluginOptionValues | sensitive / non-sensitive | 敏感配置进 secure storage |
PluginError | type 判别字段 | 错误 UI、遥测和恢复路径可结构化 |
设计取舍
第一,Skill 是提示词,不是插件脚本。 Skill 的中心是 getPromptForCommand() 和 Markdown 正文。它可以声明工具、模型和上下文,但执行主体仍然是 LLM。这让最佳实践能以文本形式快速迭代。
第二,发现内容和执行内容分离。 技能列表只占 1% 上下文,调用时再加载完整内容。这个设计同时保护上下文预算和 Prompt Cache。
第三,安全用白名单而不是黑名单。 SAFE_SKILL_PROPERTIES 没有把危险字段列出来,而是只列安全字段。未来新增字段默认进入确认路径,这是扩展系统里很重要的保守默认。
第四,远程来源降权。 MCP 技能可以被发现和调用,但不能执行 Markdown 里的 shell。插件可以带 MCP 和 Hook,但需要项目信任和插件信任提醒。
第五,Plugin 是能力治理边界。 插件清单的 11 个子 Schema 不是“配置项很多”,而是在定义 Agent 能力的 11 个可插拔维度。安装作用域、版本缓存、依赖和错误类型都是为了让这个能力集合可治理。
读源码抓手
读源码抓手
读扩展系统时,不要先看某个具体技能写了什么。先追“一个技能如何进入模型上下文”和“一个插件如何变成多个运行时组件”。
建议路线:
- 先读
skills/bundledSkills.ts,理解内置技能如何被转换成 Command。 - 再读
skills/loadSkillsDir.ts的parseSkillFrontmatterFields()和createSkillCommand(),看用户技能如何解析、替换变量和执行 shell。 - 接着看
tools/SkillTool/prompt.ts,理解技能列表为什么只有 1% 上下文预算。 - 然后读
tools/SkillTool/SkillTool.ts的checkPermissions(),重点看SAFE_SKILL_PROPERTIES和规则匹配。 - 再读
skills/mcpSkillBuilders.ts,看 MCP 技能如何复用普通技能构建器但降低能力。 - 最后进入
utils/plugins/schemas.ts和utils/plugins/pluginLoader.ts,把 PluginManifest、版本缓存和组件加载串起来。
小结
小结
- Skill 是可调用的提示词模板,Plugin 是可分发、可治理的能力容器。 - 技能发现列表受 1% 上下文预算控制,完整内容在调用时才加载。 - SAFE_SKILL_PROPERTIES 让新增敏感能力默认需要确认,这是扩展系统的 fail-closed 核心。 - MCP 技能和插件技能不是“低配本地技能”,而是来自不同信任域,需要不同执行边界。 - PluginManifest 的 11 个组件子 Schema 定义了 Agent 能力可插拔的完整表面。