From b5c38b109580f44580669b28372d7c8ebebb1df5 Mon Sep 17 00:00:00 2001 From: Gustavo Madeira Santana Date: Wed, 18 Mar 2026 02:07:51 +0000 Subject: [PATCH] Docs: point message runtime docs and tests at plugin-owned code --- docs/pi.md | 13 ++++-- docs/tools/plugin.md | 7 ++++ src/channels/plugins/actions/actions.test.ts | 6 +-- src/commands/message.test.ts | 41 ++++++++++++------- .../outbound/cfg-threading.guard.test.ts | 2 +- 5 files changed, 47 insertions(+), 22 deletions(-) diff --git a/docs/pi.md b/docs/pi.md index 2689b480963..f12c687906c 100644 --- a/docs/pi.md +++ b/docs/pi.md @@ -119,19 +119,24 @@ src/agents/ │ ├── browser-tool.ts │ ├── canvas-tool.ts │ ├── cron-tool.ts -│ ├── discord-actions*.ts │ ├── gateway-tool.ts │ ├── image-tool.ts │ ├── message-tool.ts │ ├── nodes-tool.ts │ ├── session*.ts -│ ├── slack-actions.ts -│ ├── telegram-actions.ts │ ├── web-*.ts -│ └── whatsapp-actions.ts +│ └── ... └── ... ``` +Channel-specific message action runtimes now live in the plugin-owned extension +directories instead of under `src/agents/tools`, for example: + +- `extensions/discord/src/actions/runtime*.ts` +- `extensions/slack/src/action-runtime.ts` +- `extensions/telegram/src/action-runtime.ts` +- `extensions/whatsapp/src/action-runtime.ts` + ## Core Integration Flow ### 1. Running an Embedded Agent diff --git a/docs/tools/plugin.md b/docs/tools/plugin.md index af4cd1bf6ac..4dc95ae4fe6 100644 --- a/docs/tools/plugin.md +++ b/docs/tools/plugin.md @@ -228,6 +228,13 @@ responsible for forwarding the current chat/session identity into the plugin discovery boundary so the shared `message` tool exposes the right channel-owned surface for the current turn. +For channel-owned execution helpers, bundled plugins should keep the execution +runtime inside their own extension modules. Core no longer owns the Discord, +Slack, Telegram, or WhatsApp message-action runtimes under `src/agents/tools`. +`agent-runtime` still re-exports the Discord and Telegram helpers for backward +compatibility, but we do not publish separate `plugin-sdk/*-action-runtime` +subpaths and new plugins should import their own local runtime code directly. + ## Capability ownership model OpenClaw treats a native plugin as the ownership boundary for a **company** or a diff --git a/src/channels/plugins/actions/actions.test.ts b/src/channels/plugins/actions/actions.test.ts index 1692e0f0754..f1ff9c36dfd 100644 --- a/src/channels/plugins/actions/actions.test.ts +++ b/src/channels/plugins/actions/actions.test.ts @@ -7,11 +7,11 @@ const sendReactionSignal = vi.fn(async (..._args: unknown[]) => ({ ok: true })); const removeReactionSignal = vi.fn(async (..._args: unknown[]) => ({ ok: true })); const handleSlackAction = vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })); -vi.mock("../../../agents/tools/discord-actions.js", () => ({ +vi.mock("../../../../extensions/discord/src/actions/runtime.js", () => ({ handleDiscordAction, })); -vi.mock("../../../agents/tools/telegram-actions.js", () => ({ +vi.mock("../../../../extensions/telegram/src/action-runtime.js", () => ({ handleTelegramAction, })); @@ -20,7 +20,7 @@ vi.mock("../../../../extensions/signal/src/send-reactions.js", () => ({ removeReactionSignal, })); -vi.mock("../../../agents/tools/slack-actions.js", () => ({ +vi.mock("../../../../extensions/slack/runtime-api.js", () => ({ handleSlackAction, })); diff --git a/src/commands/message.test.ts b/src/commands/message.test.ts index 182946ba7ad..806dc2655d1 100644 --- a/src/commands/message.test.ts +++ b/src/commands/message.test.ts @@ -18,43 +18,54 @@ vi.mock("../config/config.js", async (importOriginal) => { }; }); -const resolveCommandSecretRefsViaGateway = vi.fn(async ({ config }: { config: unknown }) => ({ - resolvedConfig: config, - diagnostics: [] as string[], +const { resolveCommandSecretRefsViaGateway, callGatewayMock } = vi.hoisted(() => ({ + resolveCommandSecretRefsViaGateway: vi.fn(async ({ config }: { config: unknown }) => ({ + resolvedConfig: config, + diagnostics: [] as string[], + })), + callGatewayMock: vi.fn(), })); + vi.mock("../cli/command-secret-gateway.js", () => ({ resolveCommandSecretRefsViaGateway, })); -const callGatewayMock = vi.fn(); vi.mock("../gateway/call.js", () => ({ callGateway: callGatewayMock, callGatewayLeastPrivilege: callGatewayMock, randomIdempotencyKey: () => "idem-1", })); -const webAuthExists = vi.fn(async () => false); +const webAuthExists = vi.hoisted(() => vi.fn(async () => false)); vi.mock("../../extensions/whatsapp/src/session.js", () => ({ webAuthExists, })); -const handleDiscordAction = vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })); -vi.mock("../agents/tools/discord-actions.js", () => ({ +const handleDiscordAction = vi.hoisted(() => + vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })), +); +vi.mock("../../extensions/discord/src/actions/runtime.js", () => ({ handleDiscordAction, })); -const handleSlackAction = vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })); -vi.mock("../agents/tools/slack-actions.js", () => ({ +const handleSlackAction = vi.hoisted(() => + vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })), +); +vi.mock("../../extensions/slack/runtime-api.js", () => ({ handleSlackAction, })); -const handleTelegramAction = vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })); -vi.mock("../agents/tools/telegram-actions.js", () => ({ +const handleTelegramAction = vi.hoisted(() => + vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })), +); +vi.mock("../../extensions/telegram/src/action-runtime.js", () => ({ handleTelegramAction, })); -const handleWhatsAppAction = vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })); -vi.mock("../agents/tools/whatsapp-actions.js", () => ({ +const handleWhatsAppAction = vi.hoisted(() => + vi.fn(async (..._args: unknown[]) => ({ details: { ok: true } })), +); +vi.mock("../../extensions/whatsapp/runtime-api.js", () => ({ handleWhatsAppAction, })); @@ -66,10 +77,12 @@ const setRegistry = async (registry: ReturnType) => { }; beforeEach(async () => { + vi.resetModules(); envSnapshot = captureEnv(["TELEGRAM_BOT_TOKEN", "DISCORD_BOT_TOKEN"]); process.env.TELEGRAM_BOT_TOKEN = ""; process.env.DISCORD_BOT_TOKEN = ""; testConfig = {}; + ({ messageCommand } = await import("./message.js")); await setRegistry(createTestRegistry([])); callGatewayMock.mockClear(); webAuthExists.mockClear().mockResolvedValue(false); @@ -184,7 +197,7 @@ const createTelegramPollPluginRegistration = () => ({ }), }); -const { messageCommand } = await import("./message.js"); +let messageCommand: typeof import("./message.js").messageCommand; function createTelegramSecretRawConfig() { return { diff --git a/src/infra/outbound/cfg-threading.guard.test.ts b/src/infra/outbound/cfg-threading.guard.test.ts index 3fdbb68e10b..cfdbc892db4 100644 --- a/src/infra/outbound/cfg-threading.guard.test.ts +++ b/src/infra/outbound/cfg-threading.guard.test.ts @@ -61,7 +61,7 @@ function listExtensionFiles(): { function listHighRiskRuntimeCfgFiles(): string[] { return [ - "src/agents/tools/telegram-actions.ts", + "extensions/telegram/src/action-runtime.ts", "extensions/discord/src/monitor/reply-delivery.ts", "extensions/discord/src/monitor/thread-bindings.discord-api.ts", "extensions/discord/src/monitor/thread-bindings.manager.ts",