fix(plugin-sdk): align Discord component edit facade types (#91679)

* fix(plugin-sdk): align Discord component edit facade types

* test(plugin-sdk): satisfy Discord facade type lint

* test(upgrade): seed migrated survivor sessions

---------

Co-authored-by: openclaw-clownfish[bot] <280122609+openclaw-clownfish[bot]@users.noreply.github.com>
This commit is contained in:
Vincent Koc
2026-06-09 19:58:10 +09:00
committed by GitHub
parent a36e05050a
commit 73ce4fdcbb
3 changed files with 116 additions and 8 deletions
+44 -3
View File
@@ -1,10 +1,34 @@
import { describe, expect, expectTypeOf, it, vi } from "vitest";
import type { MessageReceipt } from "./channel-outbound.js";
/**
* Tests Discord SDK helpers and Discord-facing compatibility behavior.
*/
import { describe, expect, it, vi } from "vitest";
import type {
DiscordComponentSendOpts,
DiscordComponentSendResult,
OpenClawConfig,
} from "./discord.js";
const mocks = vi.hoisted(() => {
const runtimeConfig = { channels: { discord: { token: "token" } } };
const componentEditResult = {
channelId: "channel",
messageId: "message",
receipt: {
parts: [
{
index: 0,
kind: "card",
platformMessageId: "message",
raw: { channel: "discord", channelId: "channel", messageId: "message" },
},
],
platformMessageIds: ["message"],
primaryPlatformMessageId: "message",
raw: [{ channel: "discord", channelId: "channel", messageId: "message" }],
sentAt: 0,
},
};
const apiModule = {
buildDiscordComponentMessage: vi.fn((params: { spec: { text?: string } }) => ({
components: [],
@@ -40,7 +64,7 @@ const mocks = vi.hoisted(() => {
cfg: params.cfg,
})),
collectDiscordAuditChannelIds: vi.fn(() => ({ channelIds: [], unresolvedChannels: [] })),
editDiscordComponentMessage: vi.fn(async () => ({ id: "message" })),
editDiscordComponentMessage: vi.fn(async () => componentEditResult),
listThreadBindingsBySessionKey: vi.fn(() => []),
registerBuiltDiscordComponentMessage: vi.fn(),
unbindThreadBindingsBySessionKey: vi.fn(() => []),
@@ -48,6 +72,7 @@ const mocks = vi.hoisted(() => {
return {
apiModule,
componentEditResult,
runtimeModule,
runtimeConfig,
loadBundledPluginPublicSurfaceModuleSync: vi.fn((params: { artifactBasename: string }) => {
@@ -128,7 +153,7 @@ describe("discord plugin-sdk facade", () => {
} = await import("./discord.js");
const built = buildDiscordComponentMessage({ spec: { text: "hello" } });
await editDiscordComponentMessage(
const editResult = await editDiscordComponentMessage(
"channel",
"message",
{ text: "edited" },
@@ -148,12 +173,28 @@ describe("discord plugin-sdk facade", () => {
{ text: "edited" },
{ cfg: mocks.runtimeConfig },
);
expect(editResult).toEqual(mocks.componentEditResult);
expect(mocks.runtimeModule.registerBuiltDiscordComponentMessage).toHaveBeenCalledWith({
buildResult: built,
messageId: "message",
});
});
it("types Discord component edit options and normalized result", () => {
type IsCfgOptional = object extends Pick<DiscordComponentSendOpts, "cfg"> ? true : false;
expectTypeOf<IsCfgOptional>().toEqualTypeOf<false>();
expectTypeOf<DiscordComponentSendOpts["cfg"]>().toEqualTypeOf<OpenClawConfig>();
expectTypeOf<DiscordComponentSendResult>().toEqualTypeOf<{
messageId: string;
channelId: string;
receipt: MessageReceipt;
}>();
expectTypeOf<keyof DiscordComponentSendResult>().toEqualTypeOf<
"messageId" | "channelId" | "receipt"
>();
});
it("fills runtime config for Discord subagent auto-bind calls without cfg", async () => {
const { autoBindSpawnedDiscordSubagent } = await import("./discord.js");
+6 -5
View File
@@ -9,6 +9,7 @@ import type {
ChannelStatusIssue,
} from "./channel-contract.js";
import type { ChannelPlugin } from "./channel-core.js";
import type { MessageReceipt } from "./channel-outbound.js";
import type { OpenClawConfig } from "./config-types.js";
import {
createLazyFacadeObjectValue,
@@ -67,7 +68,7 @@ export type DiscordComponentBuildResult = {
/** Send/edit options for Discord component messages. */
export type DiscordComponentSendOpts = {
cfg?: OpenClawConfig;
cfg: OpenClawConfig;
accountId?: string;
replyTo?: string;
files?: unknown;
@@ -80,11 +81,11 @@ export type DiscordComponentSendOpts = {
[key: string]: unknown;
};
/** Minimal Discord API message result returned by component send/edit helpers. */
/** Normalized Discord message result returned by component send/edit helpers. */
export type DiscordComponentSendResult = {
id?: string;
channel_id?: string;
[key: string]: unknown;
messageId: string;
channelId: string;
receipt: MessageReceipt;
};
/** Resolved Discord account with token source metadata for status and runtime checks. */
@@ -3,6 +3,7 @@ import { execFileSync } from "node:child_process";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { DatabaseSync } from "node:sqlite";
import { describe, expect, it } from "vitest";
const ASSERTIONS_PATH = "scripts/e2e/lib/upgrade-survivor/assertions.mjs";
@@ -11,6 +12,70 @@ function writeJson(path: string, value: unknown): void {
writeFileSync(path, `${JSON.stringify(value, null, 2)}\n`);
}
function writeMigratedSessionState(stateDir: string): void {
const agentSessionsDir = join(stateDir, "agents", "main", "sessions");
const agentDbDir = join(stateDir, "agents", "main", "agent");
const mainSessionFile = join(agentSessionsDir, "upgrade-main-session.jsonl");
const directSessionFile = join(agentSessionsDir, "upgrade-direct-session.jsonl");
const groupSessionFile = join(agentSessionsDir, "upgrade-group-session.jsonl");
mkdirSync(agentSessionsDir, { recursive: true });
mkdirSync(agentDbDir, { recursive: true });
writeFileSync(mainSessionFile, '{"type":"main"}\n');
writeFileSync(directSessionFile, '{"type":"direct"}\n');
writeFileSync(groupSessionFile, '{"type":"group"}\n');
const db = new DatabaseSync(join(agentDbDir, "openclaw-agent.sqlite"));
try {
db.exec(`
CREATE TABLE IF NOT EXISTS cache_entries (
scope TEXT NOT NULL,
key TEXT NOT NULL,
value_json TEXT,
blob BLOB,
expires_at INTEGER,
updated_at INTEGER NOT NULL,
PRIMARY KEY (scope, key)
);
`);
const insert = db.prepare(`
INSERT INTO cache_entries (scope, key, value_json, updated_at)
VALUES (?, ?, ?, ?)
`);
insert.run(
"session_entries",
"agent:main:main",
JSON.stringify({
sessionFile: mainSessionFile,
sessionId: "upgrade-main-session",
skillsSnapshot: {
prompt: "legacy prompt survives as metadata",
},
}),
1710000000000,
);
insert.run(
"session_entries",
"agent:main:+15551234567",
JSON.stringify({
sessionFile: directSessionFile,
sessionId: "upgrade-direct-session",
}),
1710000000100,
);
insert.run(
"session_entries",
"agent:main:slack:channel:cupgrade",
JSON.stringify({
sessionFile: groupSessionFile,
sessionId: "upgrade-group-session",
}),
1710000000200,
);
} finally {
db.close();
}
}
function assertConfiguredPluginState(params: { installPath?: string } = {}): void {
const root = mkdtempSync(join(tmpdir(), "openclaw-upgrade-survivor-"));
try {
@@ -25,6 +90,7 @@ function assertConfiguredPluginState(params: { installPath?: string } = {}): voi
writeJson(join(stateDir, "agents", "main", "sessions", "legacy-session.json"), {
id: "legacy-session",
});
writeMigratedSessionState(stateDir);
writeJson(join(matrixInstallDir, "package.json"), {
name: "@openclaw/matrix",
});