oh-my-opencode-slim 架构深度解析:从理解到自建 Plugin(插件)
适合读者: 中级 Agent(智能体) 开发者 · Plugin(插件) 作者 · 技术负责人
本文是“从使用 slim 到理解 slim“的桥接文章。如果你已经了解 slim 的基本用法(见 oh-my-opencode-slim:轻量级 Agent 编排方案)和 Plugin API 基础(见 自定义 Agent(智能体) 与 Plugin(插件)),本文将带你深入 slim 的代码架构,读懂它的设计模式,最终能够自建类似的 OpenCode Plugin。
文章概述
oh-my-opencode-slim(以下简称 slim)表面上是一个“开箱即用的轻量编排插件“,但它的代码组织方式本身就是一份优秀的 Plugin 开发参考。本文从三个递进层次展开:
- 解剖 slim — 分析 slim 如何利用 OpenCode Plugin API 实现 Hub-and-Spoke 架构,它的 Plugin 入口、Agent 注册、任务分发机制
- 提取模式 — 从 slim 的关键特性(Preset、LazySkills、Council、Background Agents)中抽象出可复用的 Plugin 设计模式
- 自建 Plugin — 基于 slim 的模式,构建一个你自己的轻量编排 Plugin
⏱ 时间有限?先读这些: Part 2(Agent 编排的 Plugin 实现)→ Part 3(关键特性的代码模式)→ Part 5(实战示例)
前置知识
- 了解 slim 的基本用法和 Hub-and-Spoke 架构概念(见 oh-my-opencode-slim:轻量级 Agent 编排方案)
- 熟悉
definePluginAPI 和 OpenCode Hook 系统(见 自定义 Agent(智能体) 与 Plugin(插件)) - 了解 TypeScript 基础语法
slim 的 Plugin 架构全景
从 Plugin 入口到 Agent 就绪
slim 本质上是一个 OpenCode Plugin。它的入口文件(简化示意)遵循标准的 definePlugin 结构:
// 简化版本的 slim Plugin 入口结构
import { definePlugin } from "opencode";
import { HubOrchestrator } from "./orchestrator";
import { ExplorerAgent, CoderAgent, ReviewerAgent, DebuggerAgent } from "./agents";
import { CompanionAgent, ReflectAgent } from "./background-agents";
import { PresetManager } from "./presets";
import { LazySkillLoader } from "./lazy-skills";
export default definePlugin({
name: "oh-my-opencode-slim",
description: "轻量级 Agent 编排插件",
version: "2.0.0",
/**
* 启动时初始化 Hub 编排器和所有 Agent
*/
async onActivate(context) {
const preset = PresetManager.load(context.config.preset || "opencode-go");
const hub = new HubOrchestrator({
preset,
config: context.config,
logger: context.logger,
});
// 注册 7 个 Agent 到 Hub
hub.register("sisyphus", new SisyphusCoordinator(hub, preset));
hub.register("explorer", new ExplorerAgent(preset.getModelConfig("explorer")));
hub.register("coder", new CoderAgent(preset.getModelConfig("coder")));
hub.register("reviewer", new ReviewerAgent(preset.getModelConfig("reviewer")));
hub.register("debugger", new DebuggerAgent(preset.getModelConfig("debugger")));
// Companion 和 Reflect 作为后台 Agent 注册
hub.registerBackground(new CompanionAgent(preset));
hub.registerBackground(new ReflectAgent(preset));
// 暴露 Hub 实例供其他 Plugin 工具调用
context.services.register("slim:hub", hub);
},
hooks: {
/**
* 拦截用户消息,通过 Sisyphus 分发到合适的 Agent
*/
"session:beforeProcessMessage": async ({ message }, context) => {
const hub = context.services.get("slim:hub");
return hub.dispatch(message);
},
/**
* Companion 的后台心跳
*/
"session:tick": async (_, context) => {
const hub = context.services.get("slim:hub");
await hub.tickBackgroundAgents();
},
},
tools: [
{
name: "slim:get_council_consensus",
description: "多模型共识评估:对高风险决策进行交叉验证",
parameters: {
type: "object",
properties: {
question: { type: "string", description: "需要共识的决策问题" },
options: {
type: "array",
items: { type: "string" },
description: "待评估的选项列表",
},
min_confidence: {
type: "number",
description: "最低置信度阈值(0-1)",
default: 0.7,
},
},
required: ["question", "options"],
},
handler: async (params, context) => {
const hub = context.services.get("slim:hub");
return hub.council.consensus(params.question, params.options, params.min_confidence);
},
},
],
});
这段代码揭示了 slim 作为 Plugin 的三个设计决策:
onActivate初始化 — 在 Plugin 加载时完成全部初始化(Hub 创建、Agent 注册、Preset 加载),而非在运行时懒加载。这保证了 Agent 就绪后零延迟响应。- Hook 点选择 — 只用了
session:beforeProcessMessage和session:tick两个 Hook 点,避免过度 Hook。前者拦截用户消息进行分发,后者为后台 Agent 提供心跳驱动。 services.register模式 — 通过 Plugin API 的 Service Registry 暴露 Hub 实例,让自定义 Tool 可以访问编排器状态。这是一种轻量的依赖注入模式。
以下时序图展示了 slim Plugin 从加载到处理用户消息的完整生命周期:
sequenceDiagram
participant OpenCode as OpenCode 主进程
participant Plugin as slim Plugin
participant Hub as Hub Orchestrator
participant Agent as 子 Agent
participant LazySkills as LazySkills 加载器
OpenCode->>Plugin: 加载 Plugin(onActivate)
Plugin->>Hub: 创建 HubOrchestrator
Hub->>Hub: 注册 5 个子 Agent(Explorer/Coder/Reviewer/Debugger/Companion/Reflect)
Plugin->>OpenCode: 注册 Hook(session:beforeProcessMessage)
Plugin->>OpenCode: 注册 Hook(session:tick)
Plugin->>OpenCode: 注册 Tool(slim:get_council_consensus)
OpenCode-->>Plugin: Plugin 就绪
Note over OpenCode,Agent: 用户发送消息
OpenCode->>Plugin: 触发 session:beforeProcessMessage
Plugin->>Hub: hub.dispatch(message)
Hub->>Hub: classifyIntent(message) → intent
Hub->>Hub: selectAgent(intent) → agent
Hub->>LazySkills: loadFor(agent, intent)
LazySkills-->>Hub: Skills 加载完成
Hub->>Agent: agent.execute(message)
Agent-->>Hub: result
Hub->>Hub: reflectOnTask(task, result)
Hub-->>Plugin: AgentResponse
Plugin-->>OpenCode: 返回处理结果
Hub-and-Spoke 的代码实现
Hub 核心是一个事件驱动的任务分发器。它的骨架如下:
// 简化版本的 Hub Orchestrator 核心实现
class HubOrchestrator {
private agents: Map<string, BaseAgent> = new Map();
private backgroundAgents: BaseAgent[] = [];
private taskQueue: Task[] = [];
private activeTask: Task | null = null;
register(name: string, agent: BaseAgent): void {
this.agents.set(name, agent);
}
registerBackground(agent: BaseAgent): void {
this.backgroundAgents.push(agent);
}
/**
* 核心分发逻辑:根据消息意图选择 Agent
*/
async dispatch(message: UserMessage): Promise<AgentResponse> {
// 1. 意图识别 — 分析用户消息决定派给哪个 Agent
const intent = await this.classifyIntent(message);
// 2. Agent 选择 — 根据意图选择最合适的 Agent
const agent = this.selectAgent(intent);
if (!agent) {
return { fallback: true, message: "请明确任务类型(编码/审查/调试/探索)" };
}
// 3. 任务入队 — 如果当前有活跃任务,排队等待
const task: Task = { intent, agent, message, status: "queued" };
if (this.activeTask) {
this.taskQueue.push(task);
return { queued: true, position: this.taskQueue.length };
}
// 4. 执行任务
return this.executeTask(task);
}
private async executeTask(task: Task): Promise<AgentResponse> {
this.activeTask = task;
try {
// 使用 LazySkills 按需加载 Agent 需要的 Skill
await LazySkillLoader.loadFor(task.agent, task.intent);
const result = await task.agent.execute(task.message);
// Reflect Agent 在任务完成后自动回顾
await this.reflectOnTask(task, result);
return result;
} finally {
this.activeTask = null;
// 执行下一个排队任务
this.processQueue();
}
}
/**
* 后台 Agent 心跳:每个 tick 驱动一次
*/
async tickBackgroundAgents(): Promise<void> {
for (const agent of this.backgroundAgents) {
if (agent.shouldActivate()) {
// 后台 Agent 运行在低优先级,不影响主任务
agent.tick().catch(err => console.warn(`[Background] ${agent.name} tick failed:`, err));
}
}
}
private async processQueue(): Promise<void> {
if (this.taskQueue.length > 0) {
const next = this.taskQueue.shift()!;
await this.executeTask(next);
}
}
}
这段代码展示了三个关键模式:
| 模式 | 说明 | 可复用到何处 |
|---|---|---|
| 任务队列 + 单活跃任务 | 防止多个 Agent 同时操作同一文件,确保确定性 | 任何需要串行化 Agent 操作的 Plugin |
| 意图分类 → Agent 选择 | 将自然语言意图映射到预定义 Agent | 路由型 Plugin(代码审查、文档生成等) |
| 后台 Agent 心跳 | 通过 session:tick Hook 驱动被动 Agent | 监控类、缓存预热类 Plugin |
Hub 的任务分发流程可以用以下流程图直观呈现:
flowchart TB
subgraph Input["输入处理"]
A[用户消息] --> B[意图分类<br/>classifyIntent]
end
subgraph Dispatch["分发决策"]
B --> C{匹配到 Agent?}
C -->|否| D[返回 fallback<br/>请明确任务类型]
C -->|是| E{当前有活跃任务?}
E -->|是| F[入队等待<br/>返回排队位置]
E -->|否| G[执行任务]
end
subgraph Execute["任务执行"]
G --> H[LazySkills 按需加载]
H --> I[Agent 执行主逻辑]
I --> J[Reflect 任务回顾]
end
subgraph Queue["队列处理"]
F --> K[当前任务完成]
K --> L[取出下一个排队任务]
L --> G
end
J --> M[返回结果]
style A fill:#4A90D9,color:#fff
style B fill:#50C878,color:#fff
style C fill:#FF9F43,color:#fff
style D fill:#A66CFF,color:#fff
style E fill:#FF9F43,color:#fff
style F fill:#A66CFF,color:#fff
style G fill:#4A90D9,color:#fff
style H fill:#50C878,color:#fff
style I fill:#4A90D9,color:#fff
style J fill:#50C878,color:#fff
style M fill:#4A90D9,color:#fff
关键特性的 Plugin 实现模式
Preset 驱动:配置即策略
slim 的 Preset 系统本质上是 配置驱动的策略注入。它将模型选择、Token 预算、重试策略封装为可交换的配置对象:
// Preset 的核心数据结构
interface Preset {
name: string;
modelMap: Record<string, string>; // Agent 类型 → 模型名称
budget: {
perAgent: Record<string, number>; // 各 Agent 的 Token 上限
total: number; // 总预算上限($30)
};
retry: {
maxAttempts: number;
fallbackModel: string; // 超限后降级到哪个模型
};
features: {
lazySkills: boolean;
council: boolean;
backgroundAgents: boolean;
};
}
const OPENAI_PRESET: Preset = {
name: "openai",
modelMap: {
sisyphus: "gpt-4o",
explorer: "gpt-4o-mini", // 探索用轻量模型节省 Token
coder: "gpt-4o",
reviewer: "gpt-4o-mini",
debugger: "gpt-4o",
companion: "gpt-4o-mini",
reflect: "gpt-4o-mini",
},
budget: {
perAgent: {
sisyphus: 8000,
explorer: 4000,
coder: 16000,
reviewer: 4000,
debugger: 8000,
companion: 2000,
reflect: 2000,
},
total: 30, // 单位:美元
},
retry: { maxAttempts: 3, fallbackModel: "gpt-4o-mini" },
features: { lazySkills: true, council: false, backgroundAgents: true },
};
可复用的设计:将策略与实现分离。你的 Plugin 可以定义多个 Preset(如 "strict"、"fast"、"cheap"),用户通过一行配置切换整套行为。
LazySkills:上下文窗口优化
LazySkills 解决的核心问题是:传统 Plugin 在启动时加载所有 Skill,浪费上下文窗口。slim 的做法是延迟加载 + 频率缓存:
class LazySkillLoader {
private static loadedSkills: Map<string, SkillDefinition> = new Map();
private static accessCount: Map<string, number> = new Map();
private static readonly MAX_SKILLS = 5; // 最多同时缓存 5 个 Skill
private static readonly UNLOAD_THRESHOLD = 3; // 3 次 tick 未使用则卸载
/**
* 按需加载 Agent 需要的 Skill
*/
static async loadFor(agent: BaseAgent, intent: Intent): Promise<void> {
const requiredSkills = agent.getSkillsFor(intent);
for (const skillName of requiredSkills) {
if (this.loadedSkills.has(skillName)) {
// 已加载,更新访问计数
this.accessCount.set(skillName, (this.accessCount.get(skillName) || 0) + 1);
continue;
}
// 未加载且达到上限 → 卸载最不常用的
if (this.loadedSkills.size >= this.MAX_SKILLS) {
this.evictLeastUsed();
}
// 动态加载 Skill 定义
const skill = await this.fetchSkill(skillName);
this.loadedSkills.set(skillName, skill);
this.accessCount.set(skillName, 1);
}
}
private static evictLeastUsed(): void {
let minAccess = Infinity;
let evictKey = "";
for (const [name, count] of this.accessCount) {
if (count < minAccess) {
minAccess = count;
evictKey = name;
}
}
if (evictKey) {
this.loadedSkills.delete(evictKey);
this.accessCount.delete(evictKey);
}
}
}
可复用的设计:任何需要管理上下文压力的 Plugin 都可以实现类似的加载器——不仅是 Skill,也可以是 Tool 定义、配置片段、Prompt 模板。
LazySkills 的加载和卸载决策流程如下:
flowchart TB
subgraph Request["请求处理"]
A[Agent 请求 Skill] --> B{Skill 在缓存中?}
end
subgraph CacheHit["命中缓存"]
B -->|是| C[更新访问计数]
C --> D[从缓存加载 Skill]
end
subgraph CacheMiss["未命中缓存"]
B -->|否| E{缓存已达上限?}
E -->|否| F[直接加载 Skill]
E -->|是| G[淘汰最不常用 Skill]
G --> F
end
subgraph Execution["执行"]
D --> H[Agent 执行任务]
F --> H
H --> I[任务完成]
end
subgraph Maintenance["后台维护"]
I --> J[session:tick 检查]
J --> K{Skill 访问间隔<br/>超过阈值?}
K -->|是| L[卸载低频 Skill<br/>释放上下文]
K -->|否| M[保持加载]
end
style A fill:#4A90D9,color:#fff
style B fill:#FF9F43,color:#fff
style C fill:#50C878,color:#fff
style D fill:#50C878,color:#fff
style E fill:#FF9F43,color:#fff
style F fill:#50C878,color:#fff
style G fill:#A66CFF,color:#fff
style H fill:#4A90D9,color:#fff
style I fill:#50C878,color:#fff
style L fill:#A66CFF,color:#fff
style M fill:#50C878,color:#fff
Council:多模型共识的 Tool 封装
Council 的实现展示了如何将“多模型调用“封装为标准的 OpenCode Tool:
class Council {
private members: CouncilMember[];
async consensus(question: string, options: string[], minConfidence: number): Promise<CouncilResult> {
// 并行调用多个模型
const votes = await Promise.all(
this.members.map(member => this.queryMember(member, question, options))
);
// 加权投票
const scores: Record<string, number> = {};
for (const vote of votes) {
for (const option of vote.preferences) {
scores[option.name] = (scores[option.name] || 0) + option.weight * vote.member.weight;
}
}
// 判断是否达到共识
const totalWeight = this.members.reduce((s, m) => s + m.weight, 0);
const result = Object.entries(scores)
.map(([name, score]) => ({ name, confidence: score / totalWeight }))
.sort((a, b) => b.confidence - a.confidence);
return {
consensus: result[0].confidence >= minConfidence ? result[0].name : null,
ranking: result,
votes: votes.map(v => ({ member: v.member.name, choice: v.preferences[0].name })),
};
}
private async queryMember(member: CouncilMember, question: string, options: string[]) {
// 通过 OpenCode API 调用指定模型
const response = await opencode.invokeModel(member.model, {
messages: [
{ role: "system", content: `你是一个决策评估专家。请从以下选项中选择最合适的:${options.join(", ")}` },
{ role: "user", content: question },
],
temperature: 0.3,
});
return {
member,
preferences: this.parseResponse(response, options),
};
}
}
可复用的设计:Council 模式适用于任何需要“多模型交叉验证“的场景——安全审计(多个模型审查代码)、架构决策(多个模型评估方案)、内容审核(多个模型判断合规)。
Background Agents:Hook 驱动的心跳模式
Background Agents 的实现核心是 session:tick Hook——OpenCode 在每个会话心跳周期(约 1-2 秒)触发一次:
// Companion Agent 的实现骨架
class CompanionAgent extends BackgroundAgent {
private observations: Observation[] = [];
private fileWatcher: FileWatcher;
async tick(): Promise<void> {
// 1. 检查文件变化
const changes = this.fileWatcher.getChanges();
// 2. 记录观察
for (const change of changes) {
this.observations.push({
type: "file_change",
path: change.path,
timestamp: Date.now(),
summary: change.summary,
});
}
// 3. 如果检测到关键变化,触发低干扰提醒
if (this.hasCriticalChanges(changes)) {
await this.suggestAction(changes);
}
}
shouldActivate(): boolean {
// 只在有观察记录且上次提醒已过冷却期时激活
return this.observations.length > 0 && Date.now() - this.lastSuggestion > 30000;
}
}
可复用的设计:session:tick Hook 可以驱动多种后台行为——文件监控、缓存预热、进度上报、自动保存。
从读取到自建:Plugin 设计决策框架
当你理解了 slim 的模式后,自建 Plugin 的核心挑战不再是“怎么写代码“,而是“做什么决策“。以下是基于 slim 模式提炼的决策框架。
决策 1:选择架构模式
| 你的需求 | 推荐模式 | 参考自 |
|---|---|---|
| 多个 Agent 围绕一个协调器工作 | Hub-and-Spoke | slim 的核心架构 |
| 任务有明确的阶段顺序(A→B→C) | Pipeline | OMO 的 Hook 链 |
| Agent 之间需要自由通信 | Event Bus | OMO 的 Event 系统 |
| 单个 Agent 处理所有请求 | 直接 Hook | 简单场景无需编排 |
决策 2:选择 Hook 点
| 你想做的事情 | 合适的 Hook 点 |
|---|---|
| 拦截用户消息进行路由 | session:beforeProcessMessage |
| 在工具调用前后注入逻辑 | tool:before / tool:after |
| 驱动后台周期性任务 | session:tick |
| 阻止危险的文件操作 | file:beforeWrite |
决策 3:选择扩展方式
| 场景 | 推荐方式 |
|---|---|
| 需要 Agent 直接调用 | 注册为 Tool(如 slim 的 slim:get_council_consensus) |
| 需要在关键节点拦截 | 注册为 Hook |
| 需要在后台持续运行 | 注册为 Service + session:tick |
决策 4:配置策略
参考 slim 的 Preset 模式,将可变参数提取为配置:
{
"plugin": {
"my-plugin": {
"path": "./plugins/my-plugin/index.ts",
"enabled": true,
"config": {
"mode": "standard", // "standard" | "strict" | "lightweight"
"maxAgents": 3,
"budget": 15,
"features": {
"autoRetry": true,
"parallelTasks": false
}
}
}
}
}
实战:构建轻量代码审查 Plugin
下面我们基于 slim 的模式,从零构建一个专用的 代码审查 Plugin。这个 Plugin 只有 3 个 Agent(Reviewer、Explorer、Reflect),专注于“提交代码后自动审查“场景。
import { definePlugin } from "opencode";
// 1. 定义审查 Agent
class ReviewAgent {
async review(filePath: string, diff: string): Promise<ReviewResult> {
const response = await opencode.invokeModel("gpt-4o-mini", {
messages: [
{
role: "system",
content: `你是一个代码审查专家。审查以下代码变更,关注:
1. 逻辑错误和边界情况
2. 安全漏洞(注入、泄露等)
3. 代码风格和可维护性
4. 性能问题
对每个问题标注严重级别:critical / major / minor`,
},
{ role: "user", content: `文件: ${filePath}\n\n变更内容:\n${diff}` },
],
temperature: 0.2,
});
return this.parseReview(response);
}
}
// 2. 定义探索 Agent
class ExploreAgent {
async findRelatedFiles(filePath: string): Promise<string[]> {
// 查找与被修改文件相关的其他文件
const imports = await opencode.grep(`import.*from.*${filePath.replace(/\..*$/, "")}`, {
filePattern: "*.ts",
});
return imports.map(i => i.file);
}
}
// 3. 定义反思 Agent
class ReflectAgent {
async summarize(findings: ReviewResult[]): Promise<string> {
const critical = findings.filter(f => f.severity === "critical").length;
const major = findings.filter(f => f.severity === "major").length;
const minor = findings.filter(f => f.severity === "minor").length;
if (critical > 0) return `⛔ 发现 ${critical} 个严重问题,建议修复后再合并`;
if (major > 2) return `⚠️ 发现 ${major} 个主要问题,建议修复`;
return `✅ 仅 ${minor} 个次要问题,可直接合并`;
}
}
// 4. 注册为 Plugin
export default definePlugin({
name: "code-review-lite",
description: "轻量代码审查 Plugin:自动审查 Git 变更",
onActivate(context) {
this.reviewAgent = new ReviewAgent();
this.exploreAgent = new ExploreAgent();
this.reflectAgent = new ReflectAgent();
},
hooks: {
"git:afterCommit": async ({ commitHash, files }) => {
// 获取文件变更
const diff = await opencode.exec(`git diff ${commitHash}^..${commitHash}`);
// 并行审查所有变更文件
const reviewPromises = files.map(async (file) => {
const review = await this.reviewAgent.review(file, diff);
const relatedFiles = await this.exploreAgent.findRelatedFiles(file);
return { file, review, relatedFiles };
});
const results = await Promise.all(reviewPromises);
// 总结
const summary = await this.reflectAgent.summarize(results.map(r => r.review));
// 通过 OpenCode 通知用户
await opencode.notify(`[Code Review Lite] ${summary}`);
},
},
tools: [
{
name: "review:check_current_changes",
description: "审查当前分支的未提交变更",
parameters: { type: "object", properties: {} },
handler: async () => {
const diff = await opencode.exec("git diff HEAD");
if (!diff) return "没有未提交的变更";
const review = await this.reviewAgent.review("working-tree", diff);
return formatReview(review);
},
},
],
});
这个示例展示了如何复用 slim 的三种模式:
| slim 模式 | 本示例中的映射 |
|---|---|
| Hub + 多 Agent | ReviewAgent + ExploreAgent + ReflectAgent |
| 并行执行 | Promise.all 并行审查多个文件 |
| Reflect 总结 | ReflectAgent.summarize 聚合审查结果 |
| Tool 暴露 | review:check_current_changes 提供按需触发 |
小结
oh-my-opencode-slim 不仅仅是一个“开箱即用的编排插件“——它的代码本身就是一份 Plugin 架构的教科书。通过本文的分析,你应该掌握了三层能力:
- 读懂 slim — 理解 Hub-and-Spoke 的代码实现、Preset 驱动模式、LazySkills 的上下文优化策略
- 提取模式 — 从 slim 中抽象出可复用的设计模式(任务队列、后台心跳、配置注入、多模型共识)
- 自建 Plugin — 应用 slim 的决策框架和实现模式,构建你自己的 OpenCode Plugin
如果你已经完成了 自定义 Agent(智能体) 与 Plugin(插件) 的 Plugin 基础学习,并且理解了 oh-my-opencode-slim:轻量级 Agent 编排方案,那么现在你已经具备了自建 Plugin 的全套能力。
延伸思考
- 如果想让 Plugin 支持并行 Agent 执行:参考
Promise.all+ git worktree 隔离模式(见 slim 的 Worktrees 集成) - 如果想让 Plugin 支持更长周期的后台任务:参考
session:tick+ 任务持久化(将任务状态写入文件或数据库) - 如果想让 Plugin 支持可视化 Dashboard:参考 OpenCode Plugin(插件) 系统参考 中的 UI 扩展点
关联章节
- → oh-my-opencode-slim:轻量级 Agent 编排方案 — slim 的使用视角(如果你还不熟悉 slim 的基本用法,先读这篇)
- → 自定义 Agent(智能体) 与 Plugin(插件) — Plugin API 的完整参考(
definePlugin、Hook 点体系、Tool 注册) - ← OpenCode Plugin(插件) 系统参考 — OpenCode Plugin 系统的全景
- → 案例:团队级 Skill(技能) 市场 — Plugin 在企业团队中的实际应用