> ## Documentation Index
> Fetch the complete documentation index at: https://docs.qoder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugins

`options.plugins` 用来把本地 plugin 目录加载进当前会话。SDK 会为每个本地 plugin 转成一个 `--plugin-dir <path>` 启动参数；插件里包含的 commands、agents、skills 和 MCP servers 都会参与本次会话的能力发现。

<div id="加载本地-plugin" />

<div id="加载本地plugin" />

## 加载本地 plugin

```python theme={null}
from qoder_agent_sdk import QoderAgentOptions, QoderSDKClient

options = QoderAgentOptions(
    plugins=[
        {"type": "local", "path": "/path/to/my-plugin"},
    ],
)

async with QoderSDKClient(options) as client:
    info = await client.get_server_info()
    print(info.get("commands"))
    print(info.get("agents"))
    print(info.get("plugins"))
```

可以同时传多个本地 plugin，会按顺序写出多次 `--plugin-dir`：

```python theme={null}
options = QoderAgentOptions(
    plugins=[
        {"type": "local", "path": "/path/to/plugin-a"},
        {"type": "local", "path": "/path/to/plugin-b"},
    ],
)
```

> 💡 `query()` 是一次性的消息流，无法在握手后查询 init 响应。要读取插件贡献的命令 / agent / skill，请用 `QoderSDKClient` 并在 `connect()` 之后调用 `get_server_info()`，或在消息流里捕获 `SystemMessage(subtype='init')` 自行读取 `message.data`。

<div id="插件目录布局" />

## 插件目录布局

一个本地 plugin 通常可以包含：

```
my-plugin/
  .qoder-plugin/plugin.json
  commands/
  agents/
  skills/
  .mcp.json
```

`.qoder-plugin/plugin.json` 用来声明插件名、版本、描述。其余目录按文件类型自动被 CLI 扫描。

SDK 不会校验路径是否存在 / 是否合法：

* 不存在的 `--plugin-dir` 路径在 SDK 模式里会被静默忽略，会话仍正常初始化。
* 损坏的 frontmatter / `.mcp.json` 不阻断 init，损坏的命令不会出现在 init 响应中。
* 想显式诊断 plugin 加载失败，目前只能依赖 [`reload_plugins().error_count`](#运行中重新加载-plugins) 兜底。

<div id="插件贡献-slash-commands" />

<div id="插件贡献slashcommands" />

## 插件贡献 slash commands

插件里的 `commands/*.md` 会出现在 `get_server_info()['commands']` 中，名称以 `<plugin>:<cmd>` 限定名形式出现。

```python theme={null}
async with QoderSDKClient(options) as client:
    info = await client.get_server_info()
    for cmd in info.get("commands", []):
        print(cmd["name"], cmd.get("description"))
```

<div id="插件贡献-agents" />

<div id="插件贡献agents" />

## 插件贡献 agents

插件里的 `agents/*.md` 会出现在 `get_server_info()['agents']` 中。SDK 还提供同步便捷方法直接拿到这份列表：

```python theme={null}
async with QoderSDKClient(options) as client:
    for agent in client.supported_agents():
        print(agent["name"], agent.get("description"))
```

<div id="插件贡献-skills" />

<div id="插件贡献skills" />

## 插件贡献 skills

插件里的 `skills/<name>/SKILL.md` 会以插件限定名（`plugin:skill`）注册。要让主会话能调用，需要在 `options.skills` 里显式列出，见 [Skills 文档](/zh/cli/sdk/python/skills#启用插件内-skill)。

<div id="插件贡献-mcp-servers" />

<div id="插件贡献mcpservers" />

## 插件贡献 MCP servers

插件里的 `.mcp.json` 会由 CLI 启动并纳入 MCP 状态，可以通过 `get_mcp_status()` 读取：

```python theme={null}
async with QoderSDKClient(options) as client:
    status = await client.get_mcp_status()
    for server in status["mcpServers"]:
        print(server["name"], server["status"])
```

<div id="临时覆盖已安装同名插件" />

## 临时覆盖已安装同名插件

通过 `options.plugins` 加载的本地 plugin 是 session-scoped。当前会话中，如果它和已安装插件同名，本地 plugin 会优先参与本次会话能力发现。这个能力适合做 plugin 开发、调试和灰度验证。

```python theme={null}
options = QoderAgentOptions(
    # 本地版本只在这个 query 会话内生效，不会改动用户全局安装状态
    plugins=[{"type": "local", "path": "./my-plugin-dev"}],
)
```

<div id="运行中重新加载-plugins" />

<div id="运行中重新加载plugins" />

## 运行中重新加载 Plugins

如果 plugin 目录发生变化，可以在同一个 `QoderSDKClient` 会话里调用 `reload_plugins()`，让 CLI 重新扫描 plugin 资源。

```python theme={null}
async with QoderSDKClient(options) as client:
    refreshed = await client.reload_plugins()

    print(refreshed["commands"])
    print(refreshed["agents"])
    print(refreshed["plugins"])
    print(refreshed["mcpServers"])
    print(refreshed["error_count"])
```

典型用途：

* 开发 plugin 时新增或删除 `commands/*.md` 后刷新。
* 安装或更新本地 plugin 后，不重启宿主应用。
* 宿主 UI 需要展示 reload 后的 commands、agents、plugins 和 MCP 状态。

> 注意：`reload_plugins()` 只在 `QoderSDKClient`（streaming）模式下有意义；一次性的 `query()` 流没有运行时控制 channel。

<div id="options-速查" />

<div id="options速查" />

## Options 速查

| 字段                | 类型                                                  | 说明                                                           |
| ----------------- | --------------------------------------------------- | ------------------------------------------------------------ |
| `plugins`         | `list[SdkPluginConfig]`                             | 加载本地 plugin 目录，目前常用 `{"type": "local", "path": ...}`         |
| `settings`        | `str \| Path \| dict[str, Any] \| None`             | 透传给 CLI 的 settings，可以包含 `enabledPlugins`、`pluginConfigs` 等字段 |
| `setting_sources` | `list[Literal["user", "project", "local"]] \| None` | 控制 CLI 读取哪些 settings 来源                                      |

`settings.enabledPlugins`、`settings.pluginConfigs`、`settings.allowedChannelPlugins`、`settings.strictPluginOnlyCustomization` 这类企业策略字段 SDK 类型已透传，但实际链路依赖具体 CLI 版本，使用前请结合自己环境验证。

<div id="返回值参考" />

## 返回值参考

<div id="clientget_server_info-与-systemmessagesubtypeinitdata" />

<div id="clientget_server_info与systemmessagesubtypeinitdata" />

### `client.get_server_info()` 与 `SystemMessage(subtype='init').data`

```python theme={null}
{
    "commands": [
        {"name": "plugin-a:greet", "description": "...", "argumentHint": "..."},
        ...
    ],
    "agents": [
        {"name": "plugin-a:helper", "description": "...", "model": "sonnet"},
        ...
    ],
    "skills": [
        {"name": "plugin-a:echo", "description": "...", "source": "plugin"},
        ...
    ],
    "plugins": [
        {"name": "plugin-a", "path": "/path/to/plugin-a", "source": "local"},
        ...
    ],
    # 还包含 models / account / output_style 等字段
}
```

<div id="clientreload_plugins" />

<div id="client-reload_plugins" />

### `client.reload_plugins()`

返回 `ReloadPluginsResponse`：

```python theme={null}
{
    "commands": list[Any],
    "agents": list[Any],
    "plugins": list[PluginInfo],            # {"name": str, "path": str, "source": str?}
    "mcpServers": list[McpServerStatus],    # 见 mcp.md 的 McpServerStatus
    "error_count": int,                     # 本次 reload 加载失败的 plugin 数
}
```

<div id="最佳实践" />

## 最佳实践

* **给宿主 UI 先读 `get_server_info()`**：它是展示 commands、agents、skills、plugins 的稳定入口。一次性 `query()` 想要这份数据时改用 `QoderSDKClient`，或在消息流里读 `SystemMessage(subtype='init').data`。
* **开发 plugin 时使用 `options.plugins`**：它只影响当前会话，不需要修改用户全局安装状态。
* **reload 之前先准备好用户提示**：`reload_plugins()` 会触发 CLI 重新扫描磁盘，可能短暂改变可用资源列表，UI 上最好同步更新。
* **错误诊断看 `error_count`**：reload 后 `error_count > 0` 表示有 plugin 资源加载失败，应该把它的来源展示给用户。

<div id="当前限制" />

## 当前限制

* 部分 qodercli 版本下，本地 plugin 的 commands、agents、MCP 可以正常出现在初始化结果中，但 plugin skills 可能没有出现在 `get_server_info()['skills']`，这属于 CLI 侧发现链路问题。
* 当前 qodercli 实现下，不存在的 `--plugin-dir` 路径在 SDK 模式里会被静默忽略；如果需要显式诊断 plugin 加载失败，目前只能通过 `reload_plugins().error_count` 兜底。
* `reload_plugins()` 是 `QoderSDKClient` 暴露的运行时控制 API；如果当前 CLI 版本返回 `this._plugins` 相关内部错误，需要升级到修复后的 qodercli。
* `settings.strictPluginOnlyCustomization`、`settings.allowedChannelPlugins`、`settings.pluginConfigs.<pid>.options` 模板替换都是 dict 透传字段，SDK 不解析，能否生效完全取决于 CLI 版本。
