refactor: migrate bundled plugins to message lifecycle

This commit is contained in:
Peter Steinberger
2026-05-06 01:40:53 +01:00
parent 2ead1502c9
commit 05eda57b3c
223 changed files with 8568 additions and 1354 deletions

View File

@@ -33,6 +33,7 @@ import {
import { IrcChannelConfigSchema } from "./config-schema.js";
import { collectIrcMutableAllowlistWarnings } from "./doctor.js";
import { startIrcGatewayAccount } from "./gateway.js";
import { ircMessageAdapter } from "./message-adapter.js";
import {
isChannelTarget,
looksLikeIrcTargetId,
@@ -240,6 +241,7 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = createChat
hint: "<#channel|nick>",
},
},
message: ircMessageAdapter,
resolver: {
resolveTargets: async ({ inputs, kind }) => {
return inputs.map((input) => {

View File

@@ -333,9 +333,9 @@ export async function handleIrcInbound(params: {
CommandAuthorized: commandAuthorized,
});
const { dispatchInboundReplyWithBase } =
await import("openclaw/plugin-sdk/inbound-reply-dispatch");
await dispatchInboundReplyWithBase({
const { dispatchChannelMessageReplyWithBase } =
await import("openclaw/plugin-sdk/channel-message");
await dispatchChannelMessageReplyWithBase({
cfg: config as OpenClawConfig,
channel: CHANNEL_ID,
accountId: account.accountId,

View File

@@ -0,0 +1,28 @@
import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-message";
import { sendMessageIrc } from "./send.js";
import type { CoreConfig } from "./types.js";
export const ircMessageAdapter = defineChannelMessageAdapter({
id: "irc",
durableFinal: {
capabilities: {
text: true,
media: true,
replyTo: true,
},
},
send: {
text: async ({ cfg, to, text, accountId, replyToId }) =>
await sendMessageIrc(to, text, {
cfg: cfg as CoreConfig,
accountId: accountId ?? undefined,
replyTo: replyToId ?? undefined,
}),
media: async ({ cfg, to, text, mediaUrl, accountId, replyToId }) =>
await sendMessageIrc(to, mediaUrl ? `${text}\n\nAttachment: ${mediaUrl}` : text, {
cfg: cfg as CoreConfig,
accountId: accountId ?? undefined,
replyTo: replyToId ?? undefined,
}),
},
});

View File

@@ -29,7 +29,7 @@ export {
resolveEffectiveAllowFromLists,
} from "openclaw/plugin-sdk/channel-policy";
export { resolveControlCommandGate } from "openclaw/plugin-sdk/command-auth";
export { dispatchInboundReplyWithBase } from "openclaw/plugin-sdk/inbound-reply-dispatch";
export { dispatchChannelMessageReplyWithBase } from "openclaw/plugin-sdk/channel-message";
export { chunkTextForOutbound } from "openclaw/plugin-sdk/text-chunking";
export {
deliverFormattedTextWithAttachments,

View File

@@ -1,3 +1,4 @@
import { verifyChannelMessageAdapterCapabilityProofs } from "openclaw/plugin-sdk/channel-message";
import { createSendCfgThreadingRuntime } from "openclaw/plugin-sdk/channel-test-helpers";
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { IrcClient } from "./client.js";
@@ -62,6 +63,7 @@ vi.mock("openclaw/plugin-sdk/text-runtime", async () => {
};
});
import { ircMessageAdapter } from "./message-adapter.js";
import { sendMessageIrc } from "./send.js";
describe("sendMessageIrc cfg threading", () => {
@@ -106,6 +108,21 @@ describe("sendMessageIrc cfg threading", () => {
expect(result.target).toBe("#room");
expect(result.messageId).toEqual(expect.any(String));
expect(result.messageId.length).toBeGreaterThan(0);
expect(result.receipt).toMatchObject({
primaryPlatformMessageId: "irc-msg-1",
platformMessageIds: ["irc-msg-1"],
parts: [
{
platformMessageId: "irc-msg-1",
kind: "text",
raw: {
channel: "irc",
conversationId: "#room",
messageId: "irc-msg-1",
},
},
],
});
});
it("fails hard when cfg is omitted", async () => {
@@ -151,4 +168,103 @@ describe("sendMessageIrc cfg threading", () => {
expect(result.messageId).toEqual(expect.any(String));
expect(result.messageId.length).toBeGreaterThan(0);
});
it("preserves reply ids in receipts", async () => {
const providedCfg = {
channels: {
irc: {
host: "irc.example.com",
nick: "openclaw",
},
},
} as unknown as CoreConfig;
const client = {
isReady: vi.fn(() => true),
sendPrivmsg: vi.fn(),
} as unknown as IrcClient;
const result = await sendMessageIrc("#room", "hello", {
cfg: providedCfg,
client,
replyTo: "irc-parent-1",
});
expect(client.sendPrivmsg).toHaveBeenCalledWith("#room", "hello\n\n[reply:irc-parent-1]");
expect(result.receipt).toMatchObject({
replyToId: "irc-parent-1",
parts: [
{
platformMessageId: "irc-msg-1",
replyToId: "irc-parent-1",
},
],
});
});
it("declares message adapter durable text, media, and reply with receipt proofs", async () => {
const providedCfg = {
channels: {
irc: {
host: "irc.example.com",
nick: "openclaw",
},
},
} as unknown as CoreConfig;
const client = {
isReady: vi.fn(() => true),
sendPrivmsg: vi.fn(),
quit: vi.fn(),
} as unknown as IrcClient & { quit: ReturnType<typeof vi.fn> };
hoisted.connectIrcClient.mockResolvedValue(client);
await expect(
verifyChannelMessageAdapterCapabilityProofs({
adapterName: "irc",
adapter: ircMessageAdapter,
proofs: {
text: async () => {
const result = await ircMessageAdapter.send?.text?.({
cfg: providedCfg,
to: "#room",
text: "hello",
});
expect(result?.receipt.platformMessageIds).toEqual(["irc-msg-1"]);
expect(client.sendPrivmsg).toHaveBeenCalledWith("#room", "hello");
},
media: async () => {
const result = await ircMessageAdapter.send?.media?.({
cfg: providedCfg,
to: "#room",
text: "image",
mediaUrl: "https://example.com/image.png",
});
expect(result?.receipt.platformMessageIds).toEqual(["irc-msg-1"]);
expect(client.sendPrivmsg).toHaveBeenCalledWith(
"#room",
"image\n\nAttachment: https://example.com/image.png",
);
},
replyTo: async () => {
const result = await ircMessageAdapter.send?.text?.({
cfg: providedCfg,
to: "#room",
text: "threaded",
replyToId: "parent-1",
});
expect(result?.receipt.replyToId).toBe("parent-1");
expect(client.sendPrivmsg).toHaveBeenCalledWith(
"#room",
"threaded\n\n[reply:parent-1]",
);
},
},
}),
).resolves.toEqual(
expect.arrayContaining([
{ capability: "text", status: "verified" },
{ capability: "media", status: "verified" },
{ capability: "replyTo", status: "verified" },
]),
);
});
});

View File

@@ -1,3 +1,7 @@
import {
createMessageReceiptFromOutboundResults,
type MessageReceipt,
} from "openclaw/plugin-sdk/channel-message";
import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime";
import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime";
import { convertMarkdownTables } from "openclaw/plugin-sdk/text-runtime";
@@ -21,6 +25,7 @@ type SendIrcOptions = {
type SendIrcResult = {
messageId: string;
target: string;
receipt: MessageReceipt;
};
function recordIrcOutboundActivity(accountId: string): void {
@@ -94,8 +99,20 @@ export async function sendMessageIrc(
recordIrcOutboundActivity(account.accountId);
const messageId = makeIrcMessageId();
return {
messageId: makeIrcMessageId(),
messageId,
target,
receipt: createMessageReceiptFromOutboundResults({
results: [
{
channel: "irc",
messageId,
conversationId: target,
},
],
kind: "text",
...(opts.replyTo ? { replyToId: opts.replyTo } : {}),
}),
};
}