Hooks: pass inbound attachment arrays to plugins (#55452)

Merged via squash.

Prepared head SHA: 062f8d0513
Co-authored-by: huntharo <5617868+huntharo@users.noreply.github.com>
Reviewed-by: @huntharo
This commit is contained in:
Harold Hunt 2026-03-27 17:23:24 -04:00 committed by GitHub
parent 9134dbd252
commit 7d18799bbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 46 additions and 2 deletions

View File

@ -92,6 +92,7 @@ Docs: https://docs.openclaw.ai
- Agents/Codex fallback: classify Codex `server_error` payloads as failoverable, sanitize `Codex error:` payloads before they reach chat, preserve context-overflow guidance for prefixed `invalid_request_error` payloads, and omit provider `request_id` values from user-facing UI copy. (#42892) Thanks @xaeon2026.
- Memory/search: share memory embedding provider registrations across split plugin runtimes so memory search no longer fails with unknown provider errors after memory-core registers built-in adapters. (#55945) Thanks @glitch418x.
- Discord/Carbon beta: update `@buape/carbon` to the latest beta and pass the new `RateLimitError` request argument so Discord stays compatible with the upstream beta constructor change. (#55980) Thanks @ngutman.
- Plugins/inbound claims: pass full inbound attachment arrays through `inbound_claim` hook metadata while keeping the legacy singular media attachment fields for compatibility. (#55452) Thanks @huntharo.
## 2026.3.24

View File

@ -4,6 +4,7 @@ import type { OpenClawConfig } from "../config/config.js";
import {
buildCanonicalSentMessageHookContext,
deriveInboundMessageHookContext,
toPluginInboundClaimEvent,
toPluginInboundClaimContext,
toInternalMessagePreprocessedContext,
toInternalMessageReceivedContext,
@ -67,6 +68,32 @@ describe("message hook mappers", () => {
expect(canonical.messageId).toBe("override-msg");
});
it("preserves multi-attachment arrays for inbound claim metadata", () => {
const canonical = deriveInboundMessageHookContext(
makeInboundCtx({
MediaPath: undefined,
MediaType: undefined,
MediaPaths: ["/tmp/tree.jpg", "/tmp/ramp.jpg"],
MediaTypes: ["image/jpeg", "image/jpeg"],
}),
);
expect(canonical.mediaPath).toBe("/tmp/tree.jpg");
expect(canonical.mediaType).toBe("image/jpeg");
expect(canonical.mediaPaths).toEqual(["/tmp/tree.jpg", "/tmp/ramp.jpg"]);
expect(canonical.mediaTypes).toEqual(["image/jpeg", "image/jpeg"]);
expect(toPluginInboundClaimEvent(canonical)).toEqual(
expect.objectContaining({
metadata: expect.objectContaining({
mediaPath: "/tmp/tree.jpg",
mediaType: "image/jpeg",
mediaPaths: ["/tmp/tree.jpg", "/tmp/ramp.jpg"],
mediaTypes: ["image/jpeg", "image/jpeg"],
}),
}),
);
});
it("maps canonical inbound context to plugin/internal received payloads", () => {
const canonical = deriveInboundMessageHookContext(makeInboundCtx());

View File

@ -35,6 +35,8 @@ export type CanonicalInboundMessageHookContext = {
threadId?: string | number;
mediaPath?: string;
mediaType?: string;
mediaPaths?: string[];
mediaTypes?: string[];
originatingChannel?: string;
originatingTo?: string;
guildId?: string;
@ -75,6 +77,16 @@ export function deriveInboundMessageHookContext(
const channelId = (ctx.OriginatingChannel ?? ctx.Surface ?? ctx.Provider ?? "").toLowerCase();
const conversationId = ctx.OriginatingTo ?? ctx.To ?? ctx.From ?? undefined;
const isGroup = Boolean(ctx.GroupSubject || ctx.GroupChannel);
const mediaPaths = Array.isArray(ctx.MediaPaths)
? ctx.MediaPaths.filter(
(value): value is string => typeof value === "string" && value.length > 0,
)
: undefined;
const mediaTypes = Array.isArray(ctx.MediaTypes)
? ctx.MediaTypes.filter(
(value): value is string => typeof value === "string" && value.length > 0,
)
: undefined;
return {
from: ctx.From ?? "",
to: ctx.To,
@ -102,8 +114,10 @@ export function deriveInboundMessageHookContext(
provider: ctx.Provider,
surface: ctx.Surface,
threadId: ctx.MessageThreadId,
mediaPath: ctx.MediaPath,
mediaType: ctx.MediaType,
mediaPath: ctx.MediaPath ?? mediaPaths?.[0],
mediaType: ctx.MediaType ?? mediaTypes?.[0],
mediaPaths,
mediaTypes,
originatingChannel: ctx.OriginatingChannel,
originatingTo: ctx.OriginatingTo,
guildId: ctx.GroupSpace,
@ -272,6 +286,8 @@ export function toPluginInboundClaimEvent(
senderE164: canonical.senderE164,
mediaPath: canonical.mediaPath,
mediaType: canonical.mediaType,
mediaPaths: canonical.mediaPaths,
mediaTypes: canonical.mediaTypes,
guildId: canonical.guildId,
channelName: canonical.channelName,
groupId: canonical.groupId,