


























@@ -444,6 +444,117 @@ describe("runMessageAction plugin dispatch", () => {
444444});
445445});
446446447+it("preserves no-context owner Discord admin actions through the shared runner", async () => {
448+const handleDiscordAction = vi.fn(async (ctx: ChannelMessageActionContext) => {
449+const currentProvider = ctx.toolContext?.currentChannelProvider?.trim().toLowerCase();
450+if (ctx.action === "channel-delete" && currentProvider && currentProvider !== "discord") {
451+throw new Error("Discord guild admin actions require a trusted Discord sender identity.");
452+}
453+if (ctx.action === "channel-delete" && !currentProvider && ctx.senderIsOwner !== true) {
454+throw new Error("Discord guild admin actions require a trusted Discord sender identity.");
455+}
456+return jsonResult({ ok: true, action: ctx.action });
457+});
458+const discordPlugin: ChannelPlugin = {
459+id: "discord",
460+meta: {
461+id: "discord",
462+label: "Discord",
463+selectionLabel: "Discord",
464+docsPath: "/channels/discord",
465+blurb: "Discord action dispatch test plugin.",
466+},
467+capabilities: { chatTypes: ["direct", "channel"] },
468+config: createAlwaysConfiguredPluginConfig(),
469+messaging: {
470+targetResolver: {
471+looksLikeId: () => true,
472+},
473+},
474+actions: {
475+describeMessageTool: () => ({ actions: ["channel-delete", "channel-info"] }),
476+supportsAction: ({ action }) => action === "channel-delete" || action === "channel-info",
477+requiresTrustedRequesterSender: ({ action, toolContext }) =>
478+Boolean(toolContext) && action === "channel-delete",
479+handleAction: handleDiscordAction,
480+},
481+};
482+const cfg = {
483+channels: {
484+discord: {
485+enabled: true,
486+},
487+},
488+} as OpenClawConfig;
489+490+setActivePluginRegistry(
491+createTestRegistry([{ pluginId: "discord", source: "test", plugin: discordPlugin }]),
492+);
493+494+await runMessageAction({
495+ cfg,
496+action: "channel-delete",
497+params: {
498+channel: "discord",
499+channelId: "channel-1",
500+},
501+senderIsOwner: true,
502+dryRun: false,
503+});
504+505+expectRecordFields(
506+readFirstPluginCall(handleDiscordAction),
507+{
508+action: "channel-delete",
509+senderIsOwner: true,
510+},
511+"owner action call",
512+);
513+514+handleDiscordAction.mockClear();
515+await expect(
516+runMessageAction({
517+ cfg,
518+action: "channel-delete",
519+params: {
520+channel: "discord",
521+channelId: "channel-1",
522+},
523+toolContext: { currentChannelProvider: "telegram" },
524+dryRun: false,
525+}),
526+).rejects.toThrow("Trusted sender identity is required for discord:channel-delete");
527+expect(handleDiscordAction).not.toHaveBeenCalled();
528+529+await expect(
530+runMessageAction({
531+ cfg,
532+action: "channel-delete",
533+params: {
534+channel: "discord",
535+channelId: "channel-1",
536+},
537+requesterSenderId: "telegram-user",
538+toolContext: { currentChannelProvider: "telegram" },
539+dryRun: false,
540+}),
541+).rejects.toThrow("trusted Discord sender identity");
542+expect(handleDiscordAction).toHaveBeenCalledOnce();
543+544+handleDiscordAction.mockClear();
545+await runMessageAction({
546+ cfg,
547+action: "channel-info",
548+params: {
549+channel: "discord",
550+channelId: "channel-1",
551+},
552+toolContext: { currentChannelProvider: "telegram" },
553+dryRun: false,
554+});
555+expect(handleDiscordAction).toHaveBeenCalledOnce();
556+});
557+447558it("routes gateway-executed plugin actions through gateway RPC instead of local dispatch", async () => {
448559const handleActionEntry = vi.fn(async () =>
449560jsonResult({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。