首页
/ Cline 插件开发指南:从 Manifest 能力声明到七钩子运行时,构建可跨 CLI/IDE/SDK 运行的 Agent 扩展

Cline 插件开发指南:从 Manifest 能力声明到七钩子运行时,构建可跨 CLI/IDE/SDK 运行的 Agent 扩展

2026-09-05 20:36:53作者:伍霜盼Ellen

Cline 插件是一个 TypeScript 模块,用于扩展任何构建在 Cline SDK 之上的 agent。同一个插件可以在 Cline CLI、VS Code 与 JetBrains 扩展,以及任何基于 @cline/core 的自定义应用中运行。读完本篇,你将掌握插件的两类形态(单文件插件与插件包)、manifest 能力声明机制、setup(api, ctx) 注册阶段、七个运行时钩子的语义与拦截技巧,以及消息构建器、自动化事件、插件包分发契约与完整测试流程。

心智模型:注册表的四个阶段与两条不变量

当宿主启动会话时,会构建一个插件注册表(registry)并按四个阶段执行:

  1. resolve — 收集所有插件对象;
  2. validate — 校验每个插件的 manifest:capabilities 必须非空;声明了 hook 阶段就必须有对应的 handler;若存在 hooks 属性,则 "hooks" 必须在 capabilities 中;
  3. setup — 调用每个插件的 setup(api, ctx) 一次,这是你执行 registerToolregisterCommand 等注册操作的地方;
  4. activate — 注册表冻结,agent 循环开始运行,你的钩子与工具正式生效。

注册表强制执行两条不变量:

  • 每一项贡献都需要匹配的能力。若 manifest.capabilities 中没有 "rules",调用 api.registerRule(...) 会直接抛出异常;
  • 能力声明与 handler 必须一致。声明了 "hooks" 却没有 hooks 对象,或反之,都会校验失败。

校验之后,注册是一次性的(one-shot)——会话期间不允许动态注册/注销。

这一流程在源码中可以直接印证:contribution-registry.ts 中注册表用一个 phase 字段在 "resolve" | "validate" | "setup" | "activate" | "run" 之间单向推进;normalizeManifest第 355–401 行 实现了文档中提到的两条校验规则——capabilities 为空数组时抛出 "capabilities must be a non-empty array"(对应源码 第 364 行),定义了 hooks 却没声明 "hooks" 能力时抛出 runtime hooks require the "hooks" capability(对应源码 第 397–401 行)。

另外从源码结构看,注册表在 setup() 阶段支持 tolerateSetupErrors 选项:开启后单个插件 setup 失败会被记录,成功完成的插件仍然会被合并进注册表——这正是 CLI "插件校验/setup 失败时打印清晰错误并继续运行"行为的底层依据。

最小可运行插件

import type { AgentPlugin } from "@cline/core"
import { createTool } from "@cline/core"

const plugin: AgentPlugin = {
  name: "hello-plugin",
  manifest: {
    capabilities: ["tools"],
  },
  setup(api, ctx) {
    api.registerTool(
      createTool({
        name: "say_hello",
        description: "Greet a person by name.",
        inputSchema: {
          type: "object",
          properties: { name: { type: "string" } },
          required: ["name"],
        },
        async execute({ name }: { name: string }) {
          return { greeting: `Hello, ${name}!` }
        },
      }),
    )
  },
}

export default plugin

agent 会把 say_hello 视为一个可调用工具。

Manifest 与完整能力清单

manifest: {
  capabilities: ["tools", "hooks"],   // required, non-empty array
  paths?: string[],                   // optional, multi-entry packages
  providerIds?: string[],             // optional, provider plugins
  modelIds?: string[],                // optional, model plugins
}
Capability api 上解锁的方法
"tools" api.registerTool()
"commands" api.registerCommand()(聊天界面中的斜杠命令)
"rules" api.registerRule()(注入系统提示词的字符串)
"messageBuilders" api.registerMessageBuilder()(重写发往 provider 的消息)
"providers" api.registerProvider()(自定义模型 provider)
"automationEvents" api.registerAutomationEventType()ctx.automation?.ingestEvent()
"hooks" 插件上的运行时 hooks 对象(生命周期回调)

可以声明任意组合——大多数真实插件只需要 1–3 个能力。

源码中能力枚举定义于 contribution-registry.ts 第 204–214 行。需要说明:当前源码的 ExtensionCapabilityOptions 还包括 "skills""mcp" 两项,后者可解锁 api.registerMcpServer(),把 MCP 服务器暴露为运行时工具(见 第 142–143 行第 539–553 行);使用这两个新能力时,同样遵循"能力必须预先声明"的规则。

setup(api, ctx):注册阶段

setup() 在每个会话中、agent 循环开始前运行一次。你在这里注册的一切都会冻结到该会话结束。

api 对象

每个 register* 方法都要求 manifest 中声明了匹配的能力:

api.registerTool(tool)                              // requires "tools"
api.registerCommand({ name, description, handler }) // requires "commands"
api.registerRule({ id, content, source })            // requires "rules"
api.registerMessageBuilder({ name, build })         // requires "messageBuilders"
api.registerProvider({ name, description })          // requires "providers"
api.registerAutomationEventType({ eventType, source }) // requires "automationEvents"

从源码实现看,这些方法在 setup 阶段只是把贡献 push 进一个 pending 暂存区,全部插件的 setup 成功后才合并进全局注册表(见 setup 方法第 500–582 行)。能力检查也并非所有方法都做硬校验——例如 registerRule 缺少 "rules" 能力会抛出 第 519–523 行 的错误;registerAutomationEventTyperegisterMcpServer 也有对应的硬检查。

ctx 对象:宿主提供的会话上下文

第二个参数携带宿主对当前会话所知的一切。所有字段都是可选的,使用前必须做特性探测(feature-detect)——同一个插件必须能在提供较少上下文的宿主(单元测试、沙箱化插件进程)中工作:

ctx.session?.sessionId       // string, 稳定的 core session id
ctx.client?.name             // 宿主: "cline-cli", "cline-vscode" 等
ctx.user                     // 认证的用户/组织信息(可用时)
ctx.workspaceInfo            // { rootPath, hint, latestGitBranchName,
                             //   latestGitCommitHash, associatedRemoteUrls }
ctx.automation?.ingestEvent  // 发出归一化的自动化事件
ctx.logger?.log              // 作用于该插件的结构化日志
ctx.telemetry                // ITelemetryService,仅进程内存在

这些字段的语义在 PluginSetupContext 接口第 159–202 行 中有完整 JSDoc 说明。其中 telemetry 值得注意:进程内插件直接拿到宿主服务;沙箱化插件拿到的是一个 bridge,转发 capture/recordCounter 等方法到宿主,宿主会把事件命名空间加 plugin. 前缀并打上 plugin_name 标记,用户关闭遥测时直接丢弃。

关于 ctx.workspaceInfo 的两条规则:

  1. 始终优先使用 ctx.workspaceInfo?.rootPath 而非 process.cwd()。CLI 可能以 --cwd 启动而没有调用 chdir,而 VS Code 工作区也不存在单一 CWD。workspaceInfo 来源于会话配置,始终正确;
  2. 不要用 import.meta.url 技巧去找"工作区"。那得到的是插件自身的位置,而不是用户的项目。

跨钩子持久化状态

setup() 先运行,hooks 后触发。共享状态最简单的方式是模块级变量:

let sessionWorkspaceRoot: string | undefined
let sessionBranch: string | undefined

const plugin: AgentPlugin = {
  name: "metrics",
  manifest: { capabilities: ["hooks"] },
  setup(api, ctx) {
    sessionWorkspaceRoot = ctx.workspaceInfo?.rootPath
    sessionBranch = ctx.workspaceInfo?.latestGitBranchName
  },
  hooks: {
    beforeTool({ toolCall, input }) {
      if (sessionBranch === "main" && toolCall.toolName === "run_commands") {
        // inspect input, optionally block
      }
      return undefined
    },
  },
}

单个 Node 进程可能并发承载多个会话。如果你的插件会运行在多会话宿主中,请按 ctx.session?.sessionId 键控状态:

const stateBySession = new Map<string, MyState>()
setup(api, ctx) {
  const id = ctx.session?.sessionId
  if (id) stateBySession.set(id, /* ... */)
}

运行时钩子:七个钩点与拦截语义

运行时钩子是同一 hook 层上的类型化进程内回调——运行在 agent 循环内部,拥有完整类型信息,没有 IPC、没有 JSON 序列化开销。在 manifest.capabilities 中声明 "hooks" 后添加 hooks 属性:

const plugin: AgentPlugin = {
  name: "metrics",
  manifest: { capabilities: ["hooks"] },
  hooks: {
    beforeRun(ctx) { /* ... */ },
    beforeTool({ toolCall, input }) { /* ... */ },
    afterTool({ toolCall, result }) { /* ... */ },
    afterRun({ result }) { /* ... */ },
    onEvent(event) { /* ... */ },
  },
}
钩子 触发时机 能停止循环吗 常见用途
beforeRun 运行时循环开始前 问候、日志、附加会话元数据
afterRun 循环结束后(成功、中止或失败) 不能 通知、指标、持久化日志
beforeModel 每次模型请求前 能(可变更 req) 注入上下文、最后一英里提示词编辑
afterModel 每次模型响应后、工具执行前 基于模型输出拦截
beforeTool 每次工具执行前 能({ stop }) 审计、脱敏、拦截危险工具
afterTool 每次工具执行后 可替换 result 后处理、脱敏工具输出中的密钥
onEvent 运行时发出的每个 AgentRuntimeEvent 不能 流式 UI、遥测管道

钩子的类型定义在 agent.ts 第 420–449 行的 AgentRuntimeHooks。从源码结构看,各钩子的返回对象还支持一个文档未强调的通用字段 appendContext:例如 AgentAfterToolResult 第 406 行appendContext 会跨钩子收集,并在本轮工具结果之后以 <hook_context> 用户消息追加,使模型在下次请求中能看到——这是向模型"追加旁注"的正式通道。

从钩子中停止循环

多个钩子返回可选的控制对象。最常见模式是 beforeTool 拦截破坏性工具调用:

beforeTool({ toolCall, input }) {
  if (toolCall.toolName === "run_commands") {
    const { commands } = input as { commands?: string[] }
    if (sessionBranch === "main" && commands?.some(c => c.startsWith("git push"))) {
      return { stop: true, reason: "Blocked git push on protected branch" }
    }
  }
  return undefined  // 显式"继续"
}

返回 undefined(或省略 return)让执行正常继续。

afterRun 语义

afterRun所有终态触发——completedabortedfailed。只想在成功时行动:

afterRun({ result }) {
  if (result.status !== "completed") return
  // notify, log success metrics, etc.
}

插件钩子 vs 文件钩子

运行时支持两套 hook 系统:

  • 文件钩子.cline/hooks/ 下的外部脚本,以序列化 JSON 调用。适合不随代码分发的用户/工作区特定脚本;
  • 插件运行时钩子 — 类型化的进程内回调。适合行为属于可复用扩展、且需要类型化访问运行时的场景。

Core 会把文件钩子适配到运行时 hook 层,因此你无需两套都写。如果你在发布插件,请写成运行时钩子。

消息构建器(Message Builders)

消息构建器在模型调用前重写"发往 provider 的消息列表"。它们在运行时消息转换为 SDK 消息块之后、core 内建安全 builder 之前运行。

适用场景:

  • 自定义压缩策略(用摘要替换中间历史);
  • 在到达 provider 前脱敏 PII 或密钥;
  • 针对特定模型的强项重塑上下文。
api.registerMessageBuilder({
  name: "summarize-middle-history",
  build(messages) {
    if (estimateTokens(messages) < THRESHOLD) return messages
    return [...prefix, summary, ...recent]
  },
})

多个构建器按注册顺序执行,前一个的输出是后一个的输入。

何时改用 beforeModel:只有当你需要运行时快照、或想直接变更请求对象本身时,才使用 beforeModel 钩子。纯粹的消息重写应该放在 builder 里。

自动化事件(Automation Events)

插件可以声明归一化事件类型,并将其发出到 Cline 自动化管线。未启用 automation 的宿主会同时忽略两者——请特性探测 ctx.automation:

manifest: { capabilities: ["automationEvents"] },

setup(api, ctx) {
  api.registerAutomationEventType({
    eventType: "github.pull_request.opened",
    source: "github",
    description: "A new GitHub PR was opened",
    attributesSchema: { /* JSON Schema for envelope.attributes */ },
  })

  if (!ctx.automation) return  // 宿主没有 automation
  ctx.automation.ingestEvent({
    eventId: "pr-1234",
    eventType: "github.pull_request.opened",
    source: "github",
    subject: "owner/repo#1234",
    occurredAt: new Date().toISOString(),
    attributes: { /* ... */ },
  })
}

从源码看,事件类型贡献要求 eventTypesource 非空,否则在 normalizeAutomationEventType 第 409–438 行 抛错;另外注册表在 setup 阶段会对未声明 automationEvents 能力的插件把 ctx.automation 强制置为 undefined(第 555–560 行)——即"没有能力,连入口都拿不到",这比简单的 feature-detect 更严格。

加载插件的三种方式

1. 自动发现(CLI)

CLI 在启动时扫描以下目录:

  • <workspace>/.cline/plugins/ — 项目作用域插件;
  • ~/.cline/plugins/ — 用户作用域插件。

放入 .ts.js 文件,运行 cline,完成:

mkdir -p .cline/plugins
cp my-plugin.ts .cline/plugins/
cline -i "do the thing my plugin enables"

2. SDK 配置中显式 extensions

当你用 ClineCore 构建自己的宿主时,直接传入插件对象:

import plugin from "./my-plugin"
import { ClineCore } from "@cline/core"

const host = await ClineCore.create({ backendMode: "local" })
await host.start({
  config: {
    providerId: "anthropic",
    modelId: "claude-sonnet-4-6",
    apiKey: process.env.ANTHROPIC_API_KEY ?? "",
    cwd: process.cwd(),
    enableTools: true,
    systemPrompt: "You are a helpful assistant.",
    extensions: [plugin],
    extensionContext: {
      workspace: { rootPath: process.cwd(), cwd: process.cwd() },
    },
  },
  prompt: "...",
  interactive: false,
})

3. pluginPaths 指向目录型插件

当插件是一个带 package.json 的目录时,把 pluginPaths 指向该目录:

config: {
  pluginPaths: ["./path/to/my-plugin-package"],
}

或者用 CLI 安装:

cline plugin install ./path/to/my-plugin-package
cline plugin install @scope/my-cline-plugin       # from npm
cline plugin install --git github.com/owner/repo  # from git

路径加载的底层实现是 plugin-loader.ts 中的 loadAgentPluginFromPath:它动态 import 模块,优先取 default 导出(否则取名为 plugin 的具名导出),随后经 validatePluginExport 第 83–103 行 校验——导出必须是对象、name 必须是非空字符串、manifest.capabilities 必须是非空字符串数组。值得注意的是 第 122–140 行:loader 会包装你的 setup,把从会话注入的 session/client/user/workspaceInfo/automation/logger/telemetry 合并进 ctx 后再调用原始 setup——这解释了为什么文档强调宿主上下文"始终来源于会话配置"。批量加载时(第 160–213 行),同名插件后者覆盖前者并产生 duplicate_plugin_override 警告,单个插件加载失败只会记入 failures 而不中断整体。

单文件插件模板

保存为 my-plugin.ts,放入 .cline/plugins/:

import { type AgentPlugin, ClineCore, createTool } from "@cline/core"

let sessionRoot: string | undefined

const plugin: AgentPlugin = {
  name: "my-plugin",
  manifest: {
    capabilities: ["tools", "hooks"],
  },

  setup(api, ctx) {
    sessionRoot = ctx.workspaceInfo?.rootPath

    api.registerTool(
      createTool({
        name: "do_thing",
        description: "Do the thing this plugin exists for.",
        inputSchema: {
          type: "object",
          properties: { target: { type: "string" } },
          required: ["target"],
        },
        async execute(input) {
          const { target } = input as { target: string }
          return { ok: true, target, root: sessionRoot }
        },
      }),
    )
  },

  hooks: {
    beforeRun() {
      console.log("[my-plugin] run started")
    },
    afterRun({ result }) {
      if (result.status !== "completed") return
      console.log(`[my-plugin] done in ${result.iterations} iteration(s)`)
    },
  },
}

async function runDemo(): Promise<void> {
  const host = await ClineCore.create({ backendMode: "local" })
  try {
    const result = await host.start({
      config: {
        providerId: "anthropic",
        modelId: "claude-sonnet-4-6",
        apiKey: process.env.ANTHROPIC_API_KEY ?? "",
        cwd: process.cwd(),
        enableTools: true,
        systemPrompt: "You are a helpful assistant. Use tools when needed.",
        extensions: [plugin],
        extensionContext: {
          workspace: { rootPath: process.cwd(), cwd: process.cwd() },
        },
      },
      prompt: "Use do_thing on the target 'world'.",
      interactive: false,
    })
    console.log(result.result?.text ?? "")
  } finally {
    await host.dispose()
  }
}

if (import.meta.main) {
  await runDemo()
}

export { plugin, runDemo }
export default plugin

复制后改个工具名、换入你的逻辑。runDemo() 函数让你可以用 ANTHROPIC_API_KEY=sk-... bun run my-plugin.ts 直接测试。

插件包(Plugin Package)

当需要 npm 依赖、多入口、打包资源或 npm/git 分发时,使用插件包。

目录结构

my-cline-plugin/
+-- package.json
+-- tsconfig.json          (optional, for local typechecking)
+-- index.ts               (the plugin entry point)
+-- README.md
+-- assets/                (optional, bundled content)
    +-- templates/
    +-- schemas/

package.json —— 发现契约

{
  "name": "my-cline-plugin",
  "version": "0.1.0",
  "private": true,
  "description": "What this plugin does, in one sentence.",
  "type": "module",
  "exports": {
    ".": "./index.ts"
  },
  "cline": {
    "plugins": [
      {
        "paths": ["./index.ts"],
        "capabilities": ["tools", "hooks"]
      }
    ]
  },
  "peerDependencies": {
    "@cline/core": "*"
  },
  "peerDependenciesMeta": {
    "@cline/core": { "optional": true }
  },
  "dependencies": {
    "zod": "^4.1.5"
  }
}

关键字段:

  • type: "module" — 必需。Cline 插件是 ES 模块;
  • cline.plugins — 发现契约。数组的每个条目含 paths(入口文件)与 capabilities(预先声明、import 前校验);
  • @cline/corepeerDependencies — 宿主已提供它。标记为 optional 让你可以独立做类型检查。

打包资源

import.meta.url 而非 process.cwd() 解析资源路径:

import { dirname, join } from "node:path"
import { fileURLToPath } from "node:url"
import { readFileSync, existsSync } from "node:fs"

const MODULE_DIR = dirname(fileURLToPath(import.meta.url))
const TEMPLATES_DIR = join(MODULE_DIR, "assets", "templates")

function loadTemplate(name: string): string | undefined {
  const path = join(TEMPLATES_DIR, `${name}.md`)
  return existsSync(path) ? readFileSync(path, "utf8") : undefined
}

这是插件中 import.meta.url唯一适用场景——定位插件包内部的文件。工作区路径始终用 ctx.workspaceInfo?.rootPath

覆盖模式(Bundled / Global / Project)

包可以内置默认资源并允许用户覆盖。约定是三层查找,按 name 后者胜出(last write wins):

  1. bundled — 插件包内文件(随插件分发的默认值);
  2. global~/.cline/data/settings/<kind>/ 下文件(用户覆盖);
  3. project<workspace>/.cline/<kind>/ 下文件(项目覆盖)。

多插件条目

如果包暴露多个插件,在 cline.plugins 中逐个列出:

"cline": {
  "plugins": [
    { "paths": ["./tools-plugin.ts"], "capabilities": ["tools"] },
    { "paths": ["./hooks-plugin.ts"], "capabilities": ["hooks"] }
  ]
}

每个入口文件应各自 export default 一个插件对象。

测试你的插件

单元测试

插件对象是纯数据。用最小上下文驱动 setup(),直接调用工具:

import plugin from "../my-plugin"

const tools: unknown[] = []
const api = {
  registerTool: (t: unknown) => tools.push(t),
  registerCommand: () => {},
  registerRule: () => {},
  registerMessageBuilder: () => {},
  registerProvider: () => {},
  registerAutomationEventType: () => {},
}
await plugin.setup?.(api as never, {
  workspaceInfo: { rootPath: "/tmp/fake-workspace" },
})

// Now `tools` contains the registered tools -- call tool.execute(input, ctx)

端到端:runDemo()

在插件文件中加一个 runDemo()(见上方单文件模板),启动真实的 ClineCore 会话:

ANTHROPIC_API_KEY=sk-... bun run my-plugin.ts

CLI 冒烟测试

mkdir -p .cline/plugins
cp my-plugin.ts .cline/plugins/
cline -i "trigger something that exercises the plugin"

包的形式:

cline plugin install ./my-cline-plugin
cline -i "..."

如果插件校验或 setup 失败,CLI 会打印清晰错误并继续运行(不带该插件)。

常见陷阱清单

  • "capabilities must be a non-empty array" — 你漏写了 manifest.capabilities,或它是 [];
  • "registerRule requires the 'rules' capability" — 能力/handler 漂移。把 "rules" 加进 capabilities,或停止调用 registerRule;
  • 工具对模型不可见 — 检查会话配置中 enableTools: true,以及 capabilities 中声明了 "tools";
  • SDK 测试中 ctx.workspaceInfo 为 undefined — 宿主没有传 extensionContext.workspace。在 SDK 代码中显式设置(见上面 ClineCore 加载示例);
  • 状态跨会话泄漏 — 模块级变量在同一进程的多个会话间共享。若宿主并发跑多会话,按 ctx.session?.sessionId 键控;
  • afterRun 在中止时也触发 — 用 if (result.status !== "completed") return 守卫;
  • setup() 中做重活setup() 会阻塞会话启动。把昂贵工作推迟到首次工具调用或 beforeRun;
  • 导入宿主内部模块 — 只从 @cline/core 导入。伸手进宿主特定包(如 CLI 内部)会在非 CLI 宿主中失效;
  • 沙箱插件与 telemetry — telemetry 是进程内的。特性探测 ctx.telemetry,并预期它在沙箱化插件进程中为 undefined;
  • 解析打包资源 — 找包内文件用 import.meta.url + fileURLToPath,绝不用 process.cwd()。工作区路径反过来:用 ctx.workspaceInfo?.rootPath,绝不用 import.meta.url;
  • 插件名冲突name 在会话内必须唯一。两个插件同名时校验失败。按包命名空间化(my-org-redactor,而非 redactor)。

决策指南:该选哪个扩展点?

你想要…… 使用
给模型一个新能力 registerTool
在聊天界面加斜杠命令 registerCommand
向系统提示词注入文本 registerRule
在消息到达 provider 前重写 registerMessageBuilder
添加自定义模型 provider registerProvider
发出归一化的 cron/webhook 事件 registerAutomationEventType + ctx.automation
观察或引导 agent 循环 hooks.*
拦截危险工具调用 hooks.beforeTool 返回 { stop: true }
完成时通知 hooks.afterRun(用 status === "completed" 门控)
微调每次模型请求 hooks.beforeModel
把事件流式送到 UI hooks.onEvent
随插件分发可复用模板 把资源打进 index.ts 旁边,用 import.meta.url 解析
让用户在全局或项目级覆盖默认值 三层查找:bundled / global / project

发布前检查清单

  • manifest.capabilities 是非空数组;
  • 每个 api.register* 调用都有对应的能力声明;
  • 若存在 hooks,"hooks"capabilities 中;
  • 工作区路径使用 ctx.workspaceInfo?.rootPath(不是 process.cwd());
  • 可选 ctx 字段都做了特性探测;
  • 工具名是 snake_case 动词;description 是写给模型看的;
  • 工具输入有带 required 的 JSON Schema;
  • 只关心成功时,afterRun handler 用 result.status === "completed" 门控;
  • 不能跨并发会话泄漏的状态按 ctx.session?.sessionId 键控;
  • (包)package.jsontype: "module"cline.plugins,以及把 @cline/core 声明为 optional peer 依赖;
  • (包)打包资源用 import.meta.url 而非 process.cwd() 解析;
  • 冒烟测试:把插件放进 .cline/plugins/(或 cline plugin install),跑 cline -i "...",亲眼看它工作。

SDK 仓库中的官方插件示例

本仓库 sdk/examples/plugins/ 目录提供了可直接研读的示例:

插件 说明
weather-metrics.ts 工具注册 + 生命周期指标,并用 ctx.workspaceInfoctx.telemetry 演示宿主上下文
mac-notify.ts macOS 通知中心提醒
custom-compaction.ts 通过消息构建器实现自定义消息压缩
background-terminal.ts 脱离式 shell 作业管理
automation-events.ts 插件发出的自动化事件
gitignore-read-files-guard.ts 通过 beforeTool 强制文件访问策略
web-search.ts 基于 Exa API 的网络搜索
typescript-lsp/ TypeScript 语言服务工具(插件包形态)
agents-squad/ 多 agent 团队编排(插件包形态)

其中 weather-metrics.ts 与本节描述的"模块级状态 + setup 填充 + 钩子消费"模式逐行对应:它用 sessionWorkspaceRoot/sessionBranch 等模块变量在 setup() 中从 ctx.workspaceInfo 取值,再由 beforeRun/beforeTool/afterTool/afterRun 统计工具调用次数,并通过 ctx.telemetry 把指标推入宿主遥测服务。

相关文档

登录后查看全文
热门项目推荐
相关项目推荐