Thanks to visit codestin.com
Credit goes to docs.github.com

Skip to main content

插件目录

使用插件目录,通过单个清单加载技能、钩子、MCP 服务器、自定义代理和 LSP 设置。

本指南介绍插件文件夹布局、如何从目录中加载插件、何时使用插件目录与注册各个扩展,以及如何使插件集具有确定性。

何时使用插件目录

在以下情况下,请使用插件目录:

  • 将一组能力作为一个整体分发,例如一个“TypeScript 审查器”包,其中包含一个技能、一个执行 lint 检查的 preToolUse 钩子,以及一个运行该审查器的自定义代理。
  • 供应商功能包到存储库中 ,以便主机应用程序的每个克隆都能确定地加载相同的扩展。
  • 在本地开发插件 ,然后将其发布到市场。
  • 使用本地签出替代或扩展市场安装的插件以进行测试。

如果你只需要添加单个 MCP 服务器、单个钩子或单个自定义代理,则可以通过 SDK 配置(mcpServershookscustomAgents)直接以内联方式注册。 一旦有三个或更多相关的扩展一起交付,插件目录就非常有用。

插件文件夹布局

Copilot CLI 会在每个插件目录中扫描,以查找 plugin.json 清单文件或位于根级别的 SKILL.md。 最小的插件如下所示:

my-plugin/
├── plugin.json              # manifest (required unless using SKILL.md only)
├── SKILL.md                 # optional: top-level skill
├── hooks.json               # optional: hooks config
├── .mcp.json                # optional: MCP server config
├── agents/                  # optional: custom agents (one .md file per agent)
│   └── code-reviewer.md
└── skills/                  # optional: additional skills
    └── lint-fix/
        └── SKILL.md

清单也可能位于 .github/plugin.json.github/plugin/plugin.json,这样插件就可以放在现有代码库中,而无需更改其根目录结构。 每个子系统(挂钩、MCP、LSP、技能、代理)都有自己的加载程序,并且是可选的 — 插件只需要它贡献的部分。

有关完整的清单架构,请参阅你的 CLI 的 /plugin 斜杠命令所引用的运行时文档。

从 SDK 加载插件目录

当 SDK 生成插件目录时,将 --plugin-dir <path> 传递给 Copilot CLI 来加载插件目录。 每种语言都通过运行时连接的 extra-args 选项公开此内容。 可以重复此标志来加载多个插件。

TypeScript
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";

async function main() {
  const client = new CopilotClient({
    connection: RuntimeConnection.forStdio({
      args: [
        "--plugin-dir", "./plugins/code-reviewer",
        "--plugin-dir", "./plugins/lint-fix",
      ],
    }),
  });

  await client.start();
}

main();
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";

const client = new CopilotClient({
  connection: RuntimeConnection.forStdio({
    args: [
      "--plugin-dir", "./plugins/code-reviewer",
      "--plugin-dir", "./plugins/lint-fix",
    ],
  }),
});

await client.start();
Python
from copilot import CopilotClient, StdioRuntimeConnection

client = CopilotClient(
    connection=StdioRuntimeConnection(
        args=(
            "--plugin-dir", "./plugins/code-reviewer",
            "--plugin-dir", "./plugins/lint-fix",
        ),
    ),
)
await client.start()
Go
package main

import (
    "context"

    copilot "github.com/github/copilot-sdk/go"
)

func main() {
    ctx := context.Background()
    client := copilot.NewClient(&copilot.ClientOptions{
        Connection: copilot.StdioConnection{
            Args: []string{
                "--plugin-dir", "./plugins/code-reviewer",
                "--plugin-dir", "./plugins/lint-fix",
            },
        },
    })
    if err := client.Start(ctx); err != nil {
        return
    }
}
client := copilot.NewClient(&copilot.ClientOptions{
    Connection: copilot.StdioConnection{
        Args: []string{
            "--plugin-dir", "./plugins/code-reviewer",
            "--plugin-dir", "./plugins/lint-fix",
        },
    },
})
if err := client.Start(ctx); err != nil {
    return err
}
.NET
using GitHub.Copilot;

await using var client = new CopilotClient(new CopilotClientOptions
{
    Connection = RuntimeConnection.ForStdio(args: new[]
    {
        "--plugin-dir", "./plugins/code-reviewer",
        "--plugin-dir", "./plugins/lint-fix",
    }),
});

await client.StartAsync();
Java
import com.github.copilot.CopilotClient;
import com.github.copilot.rpc.CopilotClientOptions;

public class PluginDirectoriesExample {
    public static void main(String[] args) throws Exception {
        var options = new CopilotClientOptions()
            .setCliArgs(new String[] {
                "--plugin-dir", "./plugins/code-reviewer",
                "--plugin-dir", "./plugins/lint-fix",
            });

        var client = new CopilotClient(options);
        client.start().get();
    }
}
var options = new CopilotClientOptions()
    .setCliArgs(new String[] {
        "--plugin-dir", "./plugins/code-reviewer",
        "--plugin-dir", "./plugins/lint-fix",
    });

var client = new CopilotClient(options);
client.start().get();
Rust
use github_copilot_sdk::{Client, ClientOptions};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let _client = Client::start(
        ClientOptions::new().with_extra_args([
            "--plugin-dir", "./plugins/code-reviewer",
            "--plugin-dir", "./plugins/lint-fix",
        ]),
    )
    .await?;
    Ok(())
}
use github_copilot_sdk::{Client, ClientOptions};

let client = Client::start(
    ClientOptions::new().with_extra_args([
        "--plugin-dir", "./plugins/code-reviewer",
        "--plugin-dir", "./plugins/lint-fix",
    ]),
)
.await?;

上面的示例使用 stdio 运行时连接 - SDK 捆绑 CLI 时的默认值。 如果通过 URL(forUri / ForUri)连接到外部运行时,请在启动长时间运行的 CLI 服务器时将 --plugin-dir 传给它;SDK 不会将 --plugin-dir 转发给它未启动的运行时。

插件可提供的功能

加载插件目录会使扩展对客户端创建的每个会话可见。 运行时会将插件提供的扩展与你以内联方式注册的内容合并:

插件贡献在会话中显示为
技能(SKILL.mdskills/*/SKILL.md
session.skills.list() 中的项;可按名称注入
自定义代理 (agents/*.md可通过 task(agent_type=...) 工具进行调度
钩子(hooks.json与通过 SDK 注册的钩子同时触发
MCP 服务器 (.mcp.json可通过 session.mcp.* 访问的工具和资源
LSP 服务器 (.lsp.json通过 session.lsp.initialize(...) 初始化

插件代理是 机队模式 中的一级子代理:父代理可以通过 agent_type 分派它们,运行时会像对待其他任何子代理一样为它们触发 subagentStart / subagentStop 钩子。

Plugin-dir 与应用市场插件

运行时有两种方法来安装插件,两种方法最终都与会话相同:

  • 插件市场/直接仓库插件 可通过 CLI 的 /plugin 斜杠命令或底层的 installedPlugins 用户设置进行持久安装。 它们是全局性的——针对同一用户配置运行的每个会话都能看到它们,并且它们会参与插件发现规则。
  • --plugin-dir 插件显式的临时 插件 , 它们仅适用于使用该标志启动的 CLI 进程。 它们的优先级高于自动发现,并且会与市场中具有相同缓存路径的条目进行去重,因此当两个来源都引用同一个插件时,该插件不会被加载两次。

对于 SDK 驱动的应用程序, --plugin-dir 通常是正确的选择:它使插件设置在应用程序的控制之下,而不是取决于每台计算机的用户状态。

使插件集具有确定性

当主机可能安装了其他插件(市场或个人插件)时,在运行时环境中设置 COPILOT_PLUGIN_DIR_ONLY=true 以禁止自动插件发现。 只有通过 --plugin-dir 传入的目录才会被加载。

Node.js/TypeScript
import { CopilotClient, RuntimeConnection } from "@github/copilot-sdk";

async function main() {
  process.env.COPILOT_PLUGIN_DIR_ONLY = "true";
  const client = new CopilotClient({
    connection: RuntimeConnection.forStdio({
      args: ["--plugin-dir", "./plugins/code-reviewer"],
    }),
  });
  await client.start();
}

main();
process.env.COPILOT_PLUGIN_DIR_ONLY = "true";

const client = new CopilotClient({
  connection: RuntimeConnection.forStdio({
    args: ["--plugin-dir", "./plugins/code-reviewer"],
  }),
});
await client.start();

在 CI 中,在无外设服务器部署中,以及想要一个不依赖于主机用户配置的可重现插件集的任何位置使用此插件。

检查已加载了哪些插件

创建会话后,列出活动插件以确认正确选取了目录:

Node.js/TypeScript
import { CopilotClient } from "@github/copilot-sdk";

async function main() {
  const client = new CopilotClient();
  await client.start();
  const session = await client.createSession({
    onPermissionRequest: async () => ({ kind: "approve-once" }),
  });

  const plugins = await session.rpc.plugins.list();
  for (const plugin of plugins.plugins) {
    console.log(`${plugin.name} (${plugin.enabled ? "enabled" : "disabled"})`);
  }
}

main();
const plugins = await session.rpc.plugins.list();
for (const plugin of plugins.plugins) {
  console.log(`${plugin.name} (${plugin.enabled ? "enabled" : "disabled"})`);
}

通过 --plugin-dir 加载的插件会出现在此列表中,其缓存路径会被设置为你提供的目录。 从市场安装的内容会标记其注册表来源。

故障排除

  • “在 dir< 中>找不到 plugin.json 或 SKILL.md” — 目录存在,但不符合插件的条件。 在根目录(或在 .github/ 下)添加一个 plugin.json 清单文件,或包含一个顶层 SKILL.md
  • 插件已加载,但代理/技能不可见 — 确保插件清单声明其贡献的代理/技能,或使用隐式布局(agents/*.mdskills/*/SKILL.md)。 然后调用 session.rpc.skills.reload(),无需重启即可使更改生效。
  • 重复钩子触发 — 运行时会按 cache_path 去重,但仅当同一目录同时作为应用市场安装和 --plugin-dir 被引用时。 如果两个不同的目录包含相同的插件,则两者都将加载。 删除一个或使用 COPILOT_PLUGIN_DIR_ONLY=true
  • --plugin-dir 连接到外部运行时时被忽略 — SDK 仅在生成 CLI 本身时转发额外的参数。 对于外部运行时(forUri/ForUri),请传递 --plugin-dir 启动运行时服务器的命令行。