mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:00:42 +00:00
fix(discord): honor agent media roots in replies
This commit is contained in:
@@ -14,6 +14,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
|
|
||||||
- Docs/tool-loop detection config keys: align `docs/tools/loop-detection.md` examples and field names with the current `tools.loopDetection` schema to prevent copy-paste validation failures from outdated keys. (#33182) Thanks @Mylszd.
|
- Docs/tool-loop detection config keys: align `docs/tools/loop-detection.md` examples and field names with the current `tools.loopDetection` schema to prevent copy-paste validation failures from outdated keys. (#33182) Thanks @Mylszd.
|
||||||
- Discord/inbound debouncer: skip bot-own MESSAGE_CREATE events before they reach the debounce queue to avoid self-triggered slowdowns in busy servers. Thanks @thewilloftheshadow.
|
- Discord/inbound debouncer: skip bot-own MESSAGE_CREATE events before they reach the debounce queue to avoid self-triggered slowdowns in busy servers. Thanks @thewilloftheshadow.
|
||||||
|
- Discord/Agent-scoped media roots: pass `mediaLocalRoots` through Discord monitor reply delivery (message + component interaction paths) so local media attachments honor per-agent workspace roots instead of falling back to default global roots. Thanks @thewilloftheshadow.
|
||||||
- Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.
|
- Discord/presence defaults: send an online presence update on ready when no custom presence is configured so bots no longer appear offline by default. Thanks @thewilloftheshadow.
|
||||||
- Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.
|
- Discord/typing cleanup: stop typing indicators after silent/NO_REPLY runs by marking the run complete before dispatch idle cleanup. Thanks @thewilloftheshadow.
|
||||||
- Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.
|
- Discord/voice messages: request upload slots with JSON fetch calls so voice message uploads no longer fail with content-type errors. Thanks @thewilloftheshadow.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import type { DiscordAccountConfig } from "../../config/types.discord.js";
|
|||||||
import { logVerbose } from "../../globals.js";
|
import { logVerbose } from "../../globals.js";
|
||||||
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
import { enqueueSystemEvent } from "../../infra/system-events.js";
|
||||||
import { logDebug, logError } from "../../logger.js";
|
import { logDebug, logError } from "../../logger.js";
|
||||||
|
import { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
|
||||||
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||||
import { upsertChannelPairingRequest } from "../../pairing/pairing-store.js";
|
import { upsertChannelPairingRequest } from "../../pairing/pairing-store.js";
|
||||||
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
import { resolveAgentRoute } from "../../routing/resolve-route.js";
|
||||||
@@ -976,6 +977,7 @@ async function dispatchDiscordComponentEvent(params: {
|
|||||||
fallbackLimit: 2000,
|
fallbackLimit: 2000,
|
||||||
});
|
});
|
||||||
const token = ctx.token ?? "";
|
const token = ctx.token ?? "";
|
||||||
|
const mediaLocalRoots = getAgentScopedMediaLocalRoots(ctx.cfg, agentId);
|
||||||
const replyToMode =
|
const replyToMode =
|
||||||
ctx.discordConfig?.replyToMode ?? ctx.cfg.channels?.discord?.replyToMode ?? "off";
|
ctx.discordConfig?.replyToMode ?? ctx.cfg.channels?.discord?.replyToMode ?? "off";
|
||||||
const replyReference = createReplyReferencePlanner({
|
const replyReference = createReplyReferencePlanner({
|
||||||
@@ -1005,6 +1007,7 @@ async function dispatchDiscordComponentEvent(params: {
|
|||||||
maxLinesPerMessage: ctx.discordConfig?.maxLinesPerMessage,
|
maxLinesPerMessage: ctx.discordConfig?.maxLinesPerMessage,
|
||||||
tableMode,
|
tableMode,
|
||||||
chunkMode: resolveChunkMode(ctx.cfg, "discord", accountId),
|
chunkMode: resolveChunkMode(ctx.cfg, "discord", accountId),
|
||||||
|
mediaLocalRoots,
|
||||||
});
|
});
|
||||||
replyReference.markSent();
|
replyReference.markSent();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ import { resolveMarkdownTableMode } from "../../config/markdown-tables.js";
|
|||||||
import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
|
import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
|
||||||
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
|
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
|
||||||
import { convertMarkdownTables } from "../../markdown/tables.js";
|
import { convertMarkdownTables } from "../../markdown/tables.js";
|
||||||
|
import { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
|
||||||
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
import { buildAgentSessionKey } from "../../routing/resolve-route.js";
|
||||||
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
|
import { resolveThreadSessionKeys } from "../../routing/session-key.js";
|
||||||
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
|
import { buildUntrustedChannelMetadata } from "../../security/channel-metadata.js";
|
||||||
@@ -128,6 +129,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
accountId,
|
accountId,
|
||||||
});
|
});
|
||||||
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
const removeAckAfterReply = cfg.messages?.removeAckAfterReply ?? false;
|
||||||
|
const mediaLocalRoots = getAgentScopedMediaLocalRoots(cfg, route.agentId);
|
||||||
const shouldAckReaction = () =>
|
const shouldAckReaction = () =>
|
||||||
Boolean(
|
Boolean(
|
||||||
ackReaction &&
|
ackReaction &&
|
||||||
@@ -668,6 +670,7 @@ export async function processDiscordMessage(ctx: DiscordMessagePreflightContext)
|
|||||||
chunkMode,
|
chunkMode,
|
||||||
sessionKey: ctxPayload.SessionKey,
|
sessionKey: ctxPayload.SessionKey,
|
||||||
threadBindings,
|
threadBindings,
|
||||||
|
mediaLocalRoots,
|
||||||
});
|
});
|
||||||
replyReference.markSent();
|
replyReference.markSent();
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -135,6 +135,45 @@ describe("deliverDiscordReply", () => {
|
|||||||
expect(sendMessageDiscordMock).not.toHaveBeenCalled();
|
expect(sendMessageDiscordMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("passes mediaLocalRoots through media sends", async () => {
|
||||||
|
const mediaLocalRoots = ["/tmp/workspace-agent"] as const;
|
||||||
|
await deliverDiscordReply({
|
||||||
|
replies: [
|
||||||
|
{
|
||||||
|
text: "Media reply",
|
||||||
|
mediaUrls: ["https://example.com/first.png", "https://example.com/second.png"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
target: "channel:654",
|
||||||
|
token: "token",
|
||||||
|
runtime,
|
||||||
|
textLimit: 2000,
|
||||||
|
mediaLocalRoots,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sendMessageDiscordMock).toHaveBeenCalledTimes(2);
|
||||||
|
expect(sendMessageDiscordMock).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
"channel:654",
|
||||||
|
"Media reply",
|
||||||
|
expect.objectContaining({
|
||||||
|
token: "token",
|
||||||
|
mediaUrl: "https://example.com/first.png",
|
||||||
|
mediaLocalRoots,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
expect(sendMessageDiscordMock).toHaveBeenNthCalledWith(
|
||||||
|
2,
|
||||||
|
"channel:654",
|
||||||
|
"",
|
||||||
|
expect.objectContaining({
|
||||||
|
token: "token",
|
||||||
|
mediaUrl: "https://example.com/second.png",
|
||||||
|
mediaLocalRoots,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("uses replyToId only for the first chunk when replyToMode is first", async () => {
|
it("uses replyToId only for the first chunk when replyToMode is first", async () => {
|
||||||
await deliverDiscordReply({
|
await deliverDiscordReply({
|
||||||
replies: [
|
replies: [
|
||||||
|
|||||||
@@ -192,6 +192,7 @@ async function sendAdditionalDiscordMedia(params: {
|
|||||||
rest?: RequestClient;
|
rest?: RequestClient;
|
||||||
accountId?: string;
|
accountId?: string;
|
||||||
mediaUrls: string[];
|
mediaUrls: string[];
|
||||||
|
mediaLocalRoots?: readonly string[];
|
||||||
resolveReplyTo: () => string | undefined;
|
resolveReplyTo: () => string | undefined;
|
||||||
retryConfig: ResolvedRetryConfig;
|
retryConfig: ResolvedRetryConfig;
|
||||||
}) {
|
}) {
|
||||||
@@ -204,6 +205,7 @@ async function sendAdditionalDiscordMedia(params: {
|
|||||||
rest: params.rest,
|
rest: params.rest,
|
||||||
mediaUrl,
|
mediaUrl,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
mediaLocalRoots: params.mediaLocalRoots,
|
||||||
replyTo,
|
replyTo,
|
||||||
}),
|
}),
|
||||||
params.retryConfig,
|
params.retryConfig,
|
||||||
@@ -226,6 +228,7 @@ export async function deliverDiscordReply(params: {
|
|||||||
chunkMode?: ChunkMode;
|
chunkMode?: ChunkMode;
|
||||||
sessionKey?: string;
|
sessionKey?: string;
|
||||||
threadBindings?: DiscordThreadBindingLookup;
|
threadBindings?: DiscordThreadBindingLookup;
|
||||||
|
mediaLocalRoots?: readonly string[];
|
||||||
}) {
|
}) {
|
||||||
const chunkLimit = Math.min(params.textLimit, 2000);
|
const chunkLimit = Math.min(params.textLimit, 2000);
|
||||||
const replyTo = params.replyToId?.trim() || undefined;
|
const replyTo = params.replyToId?.trim() || undefined;
|
||||||
@@ -341,6 +344,7 @@ export async function deliverDiscordReply(params: {
|
|||||||
rest: params.rest,
|
rest: params.rest,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
mediaUrls: mediaList.slice(1),
|
mediaUrls: mediaList.slice(1),
|
||||||
|
mediaLocalRoots: params.mediaLocalRoots,
|
||||||
resolveReplyTo,
|
resolveReplyTo,
|
||||||
retryConfig,
|
retryConfig,
|
||||||
});
|
});
|
||||||
@@ -353,6 +357,7 @@ export async function deliverDiscordReply(params: {
|
|||||||
rest: params.rest,
|
rest: params.rest,
|
||||||
mediaUrl: firstMedia,
|
mediaUrl: firstMedia,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
|
mediaLocalRoots: params.mediaLocalRoots,
|
||||||
replyTo,
|
replyTo,
|
||||||
});
|
});
|
||||||
deliveredAny = true;
|
deliveredAny = true;
|
||||||
@@ -362,6 +367,7 @@ export async function deliverDiscordReply(params: {
|
|||||||
rest: params.rest,
|
rest: params.rest,
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
mediaUrls: mediaList.slice(1),
|
mediaUrls: mediaList.slice(1),
|
||||||
|
mediaLocalRoots: params.mediaLocalRoots,
|
||||||
resolveReplyTo,
|
resolveReplyTo,
|
||||||
retryConfig,
|
retryConfig,
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user