OpenClaw 插件系统:如何打造全能私人助理 --OpenClaw源码系列第2期

插件系统如何让 OpenClaw 变成最得力的私人助理

------ OpenClaw 爆火的奥秘(源码视角,100% 可对照)


早上 9 点,你打开聊天软件,本来只想回一句"收到"。结果下一秒,你又开始在日历、邮箱、Notion、工单系统、群聊线程之间横跳 😮‍💨

真正让人崩溃的不是事情多,而是**"要去多少地方才能把一件事理清楚"**。

OpenClaw 爆火的核心原因,不是"模型更强",而是它把你每天待着时间最长的地方------聊天------变成了真正意义上的驾驶舱 🎛️。你不需要换工作流,助理就已经在你常用的渠道里等着了。

而撑起这一切的,是一套插件系统:让 OpenClaw 能不断"长出新器官"(渠道入口、工具、后台服务、网关方法、CLI......),而不是靠散装脚本凑出来的运气。

本文坚持一个写法原则:主线只讲"怎么做成",源码引用 100% 可对照,避坑细节放 Notes 留给想深挖的人。


目录

  1. 私人助理最重要的不是聪明,是"出现在你常用的地方"
  2. [插件是 Tool?是 Hook?都不是------它是能力工厂](#插件是 Tool?是 Hook?都不是——它是能力工厂 "#2-%E6%8F%92%E4%BB%B6%E6%98%AF-tool%E6%98%AF-hook%E9%83%BD%E4%B8%8D%E6%98%AF%E5%AE%83%E6%98%AF%E8%83%BD%E5%8A%9B%E5%B7%A5%E5%8E%82")
  3. [有 MCP 了,还需要 Plugin 干什么](#有 MCP 了,还需要 Plugin 干什么 "#3-%E6%9C%89-mcp-%E4%BA%86%E8%BF%98%E9%9C%80%E8%A6%81-plugin-%E5%B9%B2%E4%BB%80%E4%B9%88")
  4. 装上插件,它怎么被发现?怎么被启用?
  5. [TypeScript 插件为什么能即写即跑------Jiti + alias](#TypeScript 插件为什么能即写即跑——Jiti + alias "#5-typescript-%E6%8F%92%E4%BB%B6%E4%B8%BA%E4%BB%80%E4%B9%88%E8%83%BD%E5%8D%B3%E5%86%99%E5%8D%B3%E8%B7%91jiti--alias")
  6. [为什么强制要 configSchema?](#为什么强制要 configSchema? "#6-%E4%B8%BA%E4%BB%80%E4%B9%88%E5%BC%BA%E5%88%B6%E8%A6%81-configschema")
  7. [为什么 register 必须同步?](#为什么 register 必须同步? "#7-%E4%B8%BA%E4%BB%80%E4%B9%88-register-%E5%BF%85%E9%A1%BB%E5%90%8C%E6%AD%A5")
  8. [闭环示例:把"日程 + 重要邮件"每天推到你的聊天里](#闭环示例:把"日程 + 重要邮件"每天推到你的聊天里 "#8-%E9%97%AD%E7%8E%AF%E7%A4%BA%E4%BE%8B%E6%8A%8A%E6%97%A5%E7%A8%8B--%E9%87%8D%E8%A6%81%E9%82%AE%E4%BB%B6%E6%AF%8F%E5%A4%A9%E6%8E%A8%E5%88%B0%E4%BD%A0%E7%9A%84%E8%81%8A%E5%A4%A9%E9%87%8C")

1. 私人助理最重要的不是聪明,是"出现在你常用的地方" 📍

为什么单有一个聪明的模型,助理还是不够得力?

因为"得力"的第一要素不是能力,而是入口摩擦为零。你不会为了问一个问题,专门打开一个新 App。

OpenClaw 的 12 个官方扩展,几乎都在解决同一件事:把 OpenClaw 塞进 Teams、Google Chat、Matrix、Mattermost、iMessage、Twitch、语音电话......这些"你已经在用的地方"。而承载这件事的核心抽象,是 ChannelPlugin

ChannelPlugin 不是一个简单的 sendMessage 接口,而是一套按需组合的渠道能力模块

typescript 复制代码
export type ChannelPlugin<ResolvedAccount = any> = {
  id: ChannelId;
  meta: ChannelMeta;
  capabilities: ChannelCapabilities;
  onboarding?: ChannelOnboardingAdapter;    // 引导用户配置接入
  config: ChannelConfigAdapter;             // 账号/凭证管理
  pairing?: ChannelPairingAdapter;          // 用户 ID 映射与白名单
  outbound?: ChannelOutboundAdapter;        // 发送消息
  gateway?: ChannelGatewayAdapter;          // 入站监听(startAccount)
  threading?: ChannelThreadingAdapter;      // 线程上下文
  streaming?: ChannelStreamingAdapter;      // 流式合并策略
  status?: ChannelStatusAdapter;            // 健康检查
  actions?: ChannelMessageActionAdapter;    // 编辑/撤回/反应等
  directory?: ChannelDirectoryAdapter;      // 联系人/群组目录
  // ... 还有十几个可选适配器
};

每个适配器都是可选的------一个最小可用渠道插件只需要实现 config + gateway + outbound,其余能力按平台特性按需加。这就是"私人助理能适配不同渠道"而又保持核心逻辑一致的关键。

Notesthreading(线程上下文)、streaming(流式合并)、status(健康检查)是三个最容易被忽视却最影响"像不像产品"的能力。Mattermost 的流式合并配置(1500 字符 + 1000ms idle 阈值)、BlueBubbles 的 edit/unsend/reaction 等,都是在这一层实现的。


2. 插件是 Tool?是 Hook?都不是------它是能力工厂 🏭

如果你已经能写工具(Tool),为什么还要多学"插件"这个概念?

因为工具是"一个能力",插件是"一批能力的注册入口"。最直白的例子来自 Voice Call 插件,同一个 register(api) 调用,一次性往宿主注册了四种形态完全不同的东西:

typescript 复制代码
register(api) {
  api.registerService(voiceCallService);                    // 后台常驻服务
  api.registerTool(voiceCallTool);                          // Agent 可调用工具
  api.registerGatewayMethod("voicecall.initiate", handler); // 外部 RPC 方法
  api.registerCli(registerVoiceCallCli);                    // CLI 命令
}

插件的"注册面"来自 OpenClawPluginApi,远不止工具:

typescript 复制代码
const createApi = (record, params): OpenClawPluginApi => ({
  registerTool:          (tool, opts)         => registerTool(record, tool, opts),
  registerHook:          (events, handler)    => registerHook(record, events, handler, ...),
  registerHttpRoute:     (params)             => registerHttpRoute(record, params),
  registerChannel:       (registration)       => registerChannel(record, registration),
  registerGatewayMethod: (method, handler)    => registerGatewayMethod(record, method, handler),
  registerCli:           (registrar, opts)    => registerCli(record, registrar, opts),
  registerService:       (service)            => registerService(record, service),
  registerCommand:       (command)            => registerCommand(record, command),
  registerProvider:      (provider)           => registerProvider(record, provider),
  on:                    (hookName, handler)  => registerTypedHook(record, hookName, handler, opts),
});

这些注册产物最终都落入 PluginRegistry,统一被系统管理:

typescript 复制代码
export type PluginRegistry = {
  tools:           PluginToolRegistration[];
  hooks:           PluginHookRegistration[];
  channels:        PluginChannelRegistration[];
  services:        PluginServiceRegistration[];
  httpRoutes:      PluginHttpRouteRegistration[];
  gatewayHandlers: GatewayRequestHandlers;
  cliRegistrars:   PluginCliRegistration[];
  commands:        PluginCommandRegistration[];
  diagnostics:     PluginDiagnostic[];
};

选哪种注册方式?一张决策表:

你想做的事 用什么
接入聊天平台 / 电话 / 长连接入站事件流 registerChannel
后台常驻(定时任务 / 队列 / watcher) registerService
Agent 主动调用的动作(查询 / 发送 / 创建) registerTool
每条消息都必须过的固定逻辑(审计 / 路由 / 脱敏) registerHook / api.on
接受外部系统的 webhook 或 RPC 调用 registerHttpRoute / registerGatewayMethod

3. 有 MCP 了,还需要 Plugin 干什么 🤔

既然 MCP 能标准化工具调用,OpenClaw 的 Plugin 在补什么洞?

这是整篇文章最核心的架构问题,答案藏在两个词里:触发方向

MCP 是 Pull 世界 ↙️:模型判断"我需要这个工具",主动发起调用,拿到结果,结束。它擅长把外部能力(数据库、GitHub、内部 API)变成模型可用的接口,协议层也是为此设计的------JSON-RPC,请求-响应,跨进程隔离,跨语言复用。

Plugin 是 Push 世界 ↗️:外部事件不断涌入(新消息、账号 webhook、Relay 订阅),系统必须持续在线、稳定承接。Teams 的 Bot Framework 回调、Matrix 的 room sync、电话的实时音频流------这些都不是"模型决定要不要处理",而是"必须有人一直在那儿接着"。

更重要的是,Plugin 解决的不只是"能不能接住",还有入站之后的一系列入口工程问题:

  • 账号生命周期(startAccount → 连接 → 重连 → 优雅退出)
  • 线程上下文(threadTs / channelId 在多轮对话里的一致性)
  • 限流与合并(避免把群聊刷爆)
  • 健康检查(知道它活着没)

这些是 MCP 工具协议天然覆盖不到的。OpenClaw 本身也选择了这条路:MCP client 被做成插件形态(extensions/mcp-client)接进来,而不是把整个系统建立在 MCP 之上。

一句话判断规则

  • 🟠 事件主动推进来,必须稳定接住 → Plugin(Channel / Service)
  • 🔵 模型自主决策要不要用 → MCP 工具(或 registerTool
  • 🟢 告诉模型怎么用这些工具 → Skills

这不是二选一,成熟的 Agent 系统通常是:Plugin 做入口,MCP 做工具供应,Skills 做流程固化。

flowchart LR subgraph PULL["🔵 MCP / Pull"] direction LR M["🧠 Model"] -->|"自主决定调用"| T["🧰 Tool"] end subgraph PUSH["🟠 Plugin / Push"] direction LR W1["💬 Webhook"] --> S["⚙️ System"] W2["📞 Phone"] --> S W3["📨 Chat"] --> S end

4. 装上插件,它怎么被发现?怎么被启用? 🔍

为什么有时候"装了插件",系统就像没看见?

因为从"文件在磁盘上"到"插件真正运行",中间有两道门:发现(Discovery)启用(Enable)

4.1 Discovery:四层来源,固定优先级

discoverOpenClawPlugins() 扫描四个位置,按优先级从高到低:

typescript 复制代码
// 1. config 指定路径(最高优先级,适合临时覆盖/本地开发)
for (const extraPath of params.extraPaths ?? []) {
  discoverFromPath({ rawPath: extraPath, origin: "config", ... });
}

// 2. 工作区插件(适合团队协作/版本控制)
discoverInDirectory({ dir: path.join(workspaceRoot, ".openclaw", "extensions"), origin: "workspace", ... });

// 3. 全局插件(用户个人安装)
discoverInDirectory({ dir: path.join(resolveConfigDir(), "extensions"), origin: "global", ... });

// 4. 内置插件(最低优先级)
discoverInDirectory({ dir: bundledDir, origin: "bundled", ... });

同一个插件 ID 在多处出现时,先被发现的胜出,后来的记录为 overridden by ${existingOrigin}。这是可预测的,也是可排查的。

flowchart TD A["① config 指定路径
最高优先级"] --> B["② workspace
.openclaw/extensions"] B --> C["③ global
~/.openclaw/extensions"] C --> D["④ bundled
内置插件(最低优先级)"] D --> E{{"同 ID 已存在?"}} E -->|否| F["✅ 加入候选列表"] E -->|是| G["⚠️ 标记为 overridden,跳过"]

📌 开发期推荐放置路径<workspace>/.openclaw/extensions/<your-plugin>/。优先级高、可版本控制、团队一致。

4.2 Enable:确定性判定链

找到了不等于会跑。resolveEnableState() 按顺序执行一条判定链:

typescript 复制代码
// 1. 全局总开关
if (!config.enabled) return { enabled: false, reason: "plugins disabled" };

// 2. 黑名单拦截
if (config.deny.includes(id)) return { enabled: false, reason: "blocked by denylist" };

// 3. 白名单过滤(设置了白名单则只加载名单内的)
if (config.allow.length > 0 && !config.allow.includes(id))
  return { enabled: false, reason: "not in allowlist" };

// 4. Memory slot 优先匹配
if (config.slots.memory === id) return { enabled: true };

// 5. 单插件级别开关
const entry = config.entries[id];
if (entry?.enabled === true)  return { enabled: true };
if (entry?.enabled === false) return { enabled: false, reason: "disabled in config" };

// 6. bundled 插件默认禁用(除非在默认启用白名单里)
if (origin === "bundled" && !BUNDLED_ENABLED_BY_DEFAULT.has(id))
  return { enabled: false, reason: "bundled (disabled by default)" };

// 7. 其他情况:默认启用
return { enabled: true };

这条链里有一个 Agent 特有的设计值得单独说------memory slot 单选机制

记忆类插件(kind: "memory")只能同时激活一个:

typescript 复制代码
export function resolveMemorySlotDecision({ id, kind, slot, selectedId }) {
  if (kind !== "memory") return { enabled: true };

  if (slot === null)   return { enabled: false, reason: "memory slot disabled" };
  if (typeof slot === "string")
    return slot === id
      ? { enabled: true, selected: true }
      : { enabled: false, reason: `memory slot set to "${slot}"` };

  // 无显式 slot:先到先得
  if (selectedId && selectedId !== id)
    return { enabled: false, reason: `memory slot already filled by "${selectedId}"` };
  return { enabled: true, selected: true };
}

多个记忆后端同时写入,会造成语义混乱。这里直接在架构层把冲突掐掉。

Notesplugins.allow 一旦设置,就变成了白名单------没在里面的全部不加载,包括你以为"默认启用"的插件。开发阶段如果不确定,不设置 allow,只用 entries[id].enabled = true 更安全。


5. TypeScript 插件为什么能即写即跑------Jiti + alias ⚡

Node 不跑 .ts,插件却是 TypeScript 写的。OpenClaw 怎么做到"装上就能运行"?

loader 用 Jiti 做即时转译,核心配置如下:

typescript 复制代码
const jiti = createJiti(import.meta.url, {
  interopDefault: true,    // 自动展开 { default: ... } 包装,兼容 CJS/ESM 混用
  extensions: [".ts", ".tsx", ".mts", ".cts", ".js", ".mjs", ".cjs", ".json"],
  ...(pluginSdkAlias
    ? { alias: { "openclaw/plugin-sdk": pluginSdkAlias } }
    : {}),
});

重点在 alias。插件里写 import { ... } from "openclaw/plugin-sdk",但插件安装在 ~/.openclaw/extensions/ 里,并不存在 openclaw 这个 npm 包。alias 把这个路径映射到核心 SDK 的真实文件。

flowchart LR A["import 'openclaw/plugin-sdk'"] -->|"Jiti alias
(load time)"| B["解析策略
preferDist?"] B -->|"prod / test"| C["dist/plugin-sdk/index.js"] B -->|"dev"| D["src/plugin-sdk/index.ts"]

映射目标由 resolvePluginSdkAlias() 动态决定------它从 loader 文件所在位置向上最多遍历 6 层目录 ,优先寻找 dist/plugin-sdk/index.js(生产/测试环境)或 src/plugin-sdk/index.ts(开发环境):

typescript 复制代码
const preferDist = process.env.VITEST || process.env.NODE_ENV === "test" || isDistRuntime;

for (let i = 0; i < 6; i++) {
  const srcCandidate  = path.join(cursor, "src",  "plugin-sdk", "index.ts");
  const distCandidate = path.join(cursor, "dist", "plugin-sdk", "index.js");
  const ordered = preferDist ? [distCandidate, srcCandidate] : [srcCandidate, distCandidate];
  for (const candidate of ordered) {
    if (fs.existsSync(candidate)) return candidate;
  }
  cursor = path.dirname(cursor);
}

这套设计的收益:插件作者 TS 即写即用,不需要构建步骤;宿主进程不被全局 module loader 污染(每次 jiti(path) 是独立调用);生产环境优先走已编译的 dist,减少转译开销。

loader 还兼容多种导出格式,无论你怎么写都能被识别:

typescript 复制代码
function resolvePluginModuleExport(moduleExport) {
  const resolved = moduleExport?.default ?? moduleExport;

  // 裸函数 → 直接视为 register
  if (typeof resolved === "function") return { register: resolved };

  // 对象 → 取 register,兼容旧字段名 activate
  if (resolved && typeof resolved === "object") {
    const register = resolved.register ?? resolved.activate;
    return { definition: resolved, register };
  }

  return {};
}

6. 为什么强制要 configSchema? 🛡️

就写个简单插件,为什么还要搞 openclaw.plugin.json + schema?

因为 Agent 系统最常见的线上故障,不是模型答错了,而是配置错了------密钥写错、URL 多了个斜杠、回调路径改了没同步。强制 schema 的意义是把这类事故从"运行期发现"提前到"启动期就拒绝"。

插件根目录必须 提供 openclaw.plugin.json,且两个字段不可缺:

typescript 复制代码
// loader 里的检查逻辑
const id = typeof raw.id === "string" ? raw.id.trim() : "";
if (!id) return { ok: false, error: "plugin manifest requires id" };

const configSchema = isRecord(raw.configSchema) ? raw.configSchema : null;
if (!configSchema) return { ok: false, error: "plugin manifest requires configSchema" };

configSchema 即使插件不需要任何配置,也要声明为空对象 {}。这不是形式主义,而是为了让 AJV 校验链路走完整------loader 会在调用 register() 之前,用 schema 验证 pluginConfig,校验失败直接报错,不进入注册阶段。

schema 编译结果会被缓存(schemaCache),缓存 key 由 manifestPath + mtime 组成,manifest 文件一旦变动,缓存自动失效。

manifest 里还可以声明 uiHintschannelsskills 目录等,这些信息供宿主做可视化 onboarding 和配置向导使用------从代码里把"配置"这件事变成可声明、可渲染的系统能力,而不是让用户去读 README。


7. 为什么 register 必须同步? ⏱️

我在 register()await 一下初始化不行吗?

不行。loader 里写得很直接:

typescript 复制代码
const result = register(api);
if (result && typeof result.then === "function") {
  registry.diagnostics.push({
    level: "warn",
    pluginId: record.id,
    message: "plugin register returned a promise; async registration is ignored",
  });
}

async register 返回 Promise,系统不会 await,只发一条 warn,然后继续。

为什么要这样设计?

register() 本质上是声明式注册------告诉系统"我有什么",不应该在这里"做事"。同步遍历所有 candidate 的好处是:加载顺序确定、错误可精确定位到哪个插件、启动不被任何一个插件的 I/O 卡住。

真正需要 I/O 的初始化,有两个正确的归宿:

  1. 渠道类 📡:放到 gateway.startAccount() 里------Gateway 启动后,createChannelManager() 会遍历 registry.channels,对每个已启用渠道调用 startAccount({ accountId, cfg, runtime, abortSignal }),这才是渠道真正"活"起来的时刻(长连接建立、webhook 监听开始、入站消息开始进路由)。

  2. 后台服务类 ⚙️:放到 registerService() 注册的 service 里------service 的启动逻辑在 Gateway 起来之后才会执行,并且受 abortSignal 管控。

这个分工让"启动期"和"运行期"边界清晰:register 是地图,startAccount/service 是真正出发。

timeline title OpenClaw 插件生命周期 插件加载阶段 : 插件被发现 : Enable 判定通过 : Jiti 转译执行 声明阶段 : register() 同步调用 : tool / channel / service 登记进 Registry 运行期 : Gateway 启动 : startAccount() 建立连接 : 入站消息进入路由 : Agent 处理 → outbound 发回

8. 闭环示例:把"日程 + 重要邮件"每天推到你的聊天里 ✅

说了这么多架构,能不能给一个我明天就能用上的东西?

目标很具体:每天早上 9:00,往你最常用的聊天渠道推一条 8 行摘要:

csharp 复制代码
📅 2026-02-20 今日摘要

• 10:30 与 Alex 的设计评审(会议室 B)
• 14:00 全组周会(Zoom)
• 17:00 1:1 with PM(可线上)

✉️ 需要回复:
  - Sarah:"Q2 预算表有问题,今天能确认吗?"
  - DevOps:"生产告警,需要你确认一下"

▶️ 要我帮你:代你回第一封 / 把 17:00 改成线上 / 推迟 30 分钟?

8.1 为什么选 Service 而不是 Cron 脚本

定时推送是典型的"Push + 常驻"场景:不是模型决定"要不要推",而是系统按时自动做。这对应的注册类型是 registerService

8.2 最小文件结构

go 复制代码
daily-digest-plugin/
  package.json
  openclaw.plugin.json
  index.ts

package.json (必须声明 openclaw.extensions,否则安装时直接失败):

json 复制代码
{
  "name": "@you/daily-digest",
  "version": "0.1.0",
  "openclaw": {
    "extensions": ["index.ts"]
  },
  "dependencies": {}
}

openclaw.plugin.jsonid + configSchema 两者缺一不可):

json 复制代码
{
  "id": "daily-digest",
  "configSchema": {
    "type": "object",
    "required": ["channelId", "accountId", "target"],
    "properties": {
      "channelId":  { "type": "string", "description": "目标渠道 ID(如 slack / telegram)" },
      "accountId":  { "type": "string", "description": "用于发送的账号 ID" },
      "target":     { "type": "string", "description": "发送目标(频道/用户 ID)" },
      "cronHour":   { "type": "number", "default": 9, "description": "触发小时(本地时区)" }
    },
    "additionalProperties": false
  }
}

index.tsregister 必须同步,I/O 放到 service 里):

typescript 复制代码
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";

export default function register(api: OpenClawPluginApi) {
  const cfg = api.pluginConfig as {
    channelId: string;
    accountId: string;
    target: string;
    cronHour?: number;
  };

  // 后台定时服务(Push 场景的正确归宿)
  api.registerService({
    id: "daily-digest.scheduler",
    async start({ abortSignal }) {
      while (!abortSignal.aborted) {
        const now = new Date();
        const triggerHour = cfg.cronHour ?? 9;
        const msUntilTrigger = getMsUntilHour(now, triggerHour);

        await sleep(msUntilTrigger, abortSignal);
        if (abortSignal.aborted) break;

        // 拉取数据(可对接 MCP 工具 / 内部 API / Gmail API)
        const [agenda, emails] = await Promise.all([
          fetchTodayAgenda(),
          fetchImportantEmails(),
        ]);

        const summary = formatBrief(agenda, emails);

        // 用 runtime 的渠道发送能力投递消息
        await api.runtime.channel.reply.dispatchReplyFromConfig({
          channelId: cfg.channelId,
          accountId: cfg.accountId,
          target: cfg.target,
          text: summary,
        });
      }
    },
  });

  // 可选:让 Agent 也能主动触发(Pull 场景)
  api.registerTool({
    name: "daily_digest.send_now",
    description: "立即生成并发送今日摘要",
    inputSchema: { type: "object", properties: {}, additionalProperties: false },
    handler: async () => {
      const [agenda, emails] = await Promise.all([fetchTodayAgenda(), fetchImportantEmails()]);
      return { summary: formatBrief(agenda, emails) };
    },
  });

  api.logger.info(`daily-digest registered (trigger: ${cfg.cronHour ?? 9}:00)`);
}

8.3 这个例子在架构层面说明了什么

  • Service 承接 Push ↗️:定时触发不依赖模型"想不想做",由 service 直接驱动
  • Tool 提供 Pull ↙️:用户说"现在给我推一次",Agent 调用 daily_digest.send_now
  • register 纯声明 📋:没有任何网络请求,start() 在 Gateway 启动后才运行
  • configSchema 完整 🛡️:channelId / accountId / target 都有类型约束,配错了启动期就报错

把邮件/日历的数据拉取做成 MCP tools,插件只做"触发 + 格式化 + 投递"------这就是前面说的分层:Plugin 管入口和生命周期,MCP 管可复用的业务工具。

Notes(进阶)

  • 只推"重要邮件"的规则建议写进 configSchema(比如 minImportanceScore),这样可以在配置层控制,而不是在代码里写死。
  • 如果要对接 Gmail OAuth,refresh token 的维护逻辑放到 service 或 channel onboarding 里,不要放在 register() 里(会被忽略)。
  • 发给群组时注意信息边界------把包含隐私内容的邮件摘要发进多人频道,是很常见的踩坑点。

结尾:OpenClaw 爆火的奥秘其实很朴素 🎯

真正"得力"的私人助理,不在于它有多聪明,而在于:它稳定地出现在你已经待着的地方,把正确的信息和下一步动作送到你手上。

OpenClaw 的插件系统用一条可工程化的链路把这件事做实了:

阶段 机制 解决什么
🔍 发现 四层来源 + 优先级覆盖 插件在哪儿、谁的优先
✅ 启用 allow/deny/slots/entries/bundled 默认策略 哪些插件该跑
⚡ 加载 Jiti + alias TS 即写即用,不污染宿主
🛡️ 校验 manifest + 强制 schema + AJV 配置错误在启动期暴露
📋 注册 同步 register 启动确定性,声明与 I/O 分离
🚀 激活 startAccount / service.start 渠道真正"活"起来

这不是"方便集成的脚手架",而是一套把"私人助理"这个产品形态变成可扩展、可运维、可复制的系统能力的工程选择。

关注我,下一期继续整更硬核干货🔥🤖📌 敬请期待~✨


本文引用的所有源码均来自 OpenClaw 代码库,可按对应文件路径(src/plugins/discovery.tssrc/plugins/loader.tssrc/plugins/config-state.ts 等)直接核对。

相关推荐
本末倒置1832 小时前
我研究了OpenClaw一周,发现它不是另一个ChatGPT,而是数字员工的起点
openai·ai编程·claude
兔子零10243 小时前
Star-Office-UI-Node 实战:从 0 到 1 接入 OpenClaw 的多 Agent 看板
前端·ai编程
D4rk_3ch05 小时前
Agent架构的真相:你可能不需要那么复杂
ai编程
用户41132025144765 小时前
DeepResearch机制解读多智能体协作(二)
ai编程
科学修行的红客7 小时前
简单设置解决cursor连接远程服务器失败问题
ai编程·cursor
lcddjm7 小时前
openclaw 配置使用第三方的api
ai编程
程序员鱼皮8 小时前
微软竟然出了免费的 AI 应用开发课?!我已经学上了
人工智能·程序员·ai编程
小碗细面8 小时前
Anthropic 官方指南:构建 Skills 的秘密都在这里
aigc·ai编程