fix: allow agent workspace directories in media local roots (#17136)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 7545ef1e19
Co-authored-by: MisterGuy420 <255743668+MisterGuy420@users.noreply.github.com>
Co-authored-by: gumadeiras <5599352+gumadeiras@users.noreply.github.com>
Reviewed-by: @gumadeiras
This commit is contained in:
Mr. Guy
2026-02-15 10:53:45 -05:00
committed by GitHub
parent 0c57f5e62e
commit e927fd1e35
38 changed files with 388 additions and 35 deletions

View File

@@ -532,6 +532,7 @@ export async function runHeartbeatOnce(opts: {
to: delivery.to,
accountId: delivery.accountId,
payloads: [{ text: heartbeatOkText }],
agentId,
deps: opts.deps,
});
return true;
@@ -710,6 +711,7 @@ export async function runHeartbeatOnce(opts: {
channel: delivery.channel,
to: delivery.to,
accountId: deliveryAccountId,
agentId,
payloads: [
...reasoningPayloads,
...(shouldSkipMain

View File

@@ -1,8 +1,10 @@
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../../config/config.js";
import { signalOutbound } from "../../channels/plugins/outbound/signal.js";
import { telegramOutbound } from "../../channels/plugins/outbound/telegram.js";
import { whatsappOutbound } from "../../channels/plugins/outbound/whatsapp.js";
import { STATE_DIR } from "../../config/paths.js";
import { setActivePluginRegistry } from "../../plugins/runtime.js";
import { markdownToSignalTextChunks } from "../../signal/format.js";
import { createOutboundTestPlugin, createTestRegistry } from "../../test-utils/channel-plugins.js";
@@ -116,6 +118,31 @@ describe("deliverOutboundPayloads", () => {
);
});
it("scopes media local roots to the active agent workspace when agentId is provided", async () => {
const sendTelegram = vi.fn().mockResolvedValue({ messageId: "m1", chatId: "c1" });
const cfg: OpenClawConfig = {
channels: { telegram: { botToken: "tok-1", textChunkLimit: 2 } },
};
await deliverOutboundPayloads({
cfg,
channel: "telegram",
to: "123",
agentId: "work",
payloads: [{ text: "hi", mediaUrl: "file:///tmp/f.png" }],
deps: { sendTelegram },
});
expect(sendTelegram).toHaveBeenCalledWith(
"123",
"hi",
expect.objectContaining({
mediaUrl: "file:///tmp/f.png",
mediaLocalRoots: expect.arrayContaining([path.join(STATE_DIR, "workspace-work")]),
}),
);
});
it("uses signal media maxBytes from config", async () => {
const sendSignal = vi.fn().mockResolvedValue({ messageId: "s1", timestamp: 123 });
const cfg: OpenClawConfig = { channels: { signal: { mediaMaxMb: 2 } } };

View File

@@ -22,6 +22,7 @@ import {
appendAssistantMessageToSessionTranscript,
resolveMirroredTranscriptText,
} from "../../config/sessions.js";
import { getAgentScopedMediaLocalRoots } from "../../media/local-roots.js";
import { getGlobalHookRunner } from "../../plugins/hook-runner-global.js";
import { markdownToSignalTextChunks, type SignalTextStyleRange } from "../../signal/format.js";
import { sendMessageSignal } from "../../signal/send.js";
@@ -90,6 +91,7 @@ async function createChannelHandler(params: {
deps?: OutboundSendDeps;
gifPlayback?: boolean;
silent?: boolean;
mediaLocalRoots?: readonly string[];
}): Promise<ChannelHandler> {
const outbound = await loadChannelOutboundAdapter(params.channel);
if (!outbound?.sendText || !outbound?.sendMedia) {
@@ -107,6 +109,7 @@ async function createChannelHandler(params: {
deps: params.deps,
gifPlayback: params.gifPlayback,
silent: params.silent,
mediaLocalRoots: params.mediaLocalRoots,
});
if (!handler) {
throw new Error(`Outbound not configured for channel: ${params.channel}`);
@@ -126,6 +129,7 @@ function createPluginHandler(params: {
deps?: OutboundSendDeps;
gifPlayback?: boolean;
silent?: boolean;
mediaLocalRoots?: readonly string[];
}): ChannelHandler | null {
const outbound = params.outbound;
if (!outbound?.sendText || !outbound?.sendMedia) {
@@ -153,6 +157,7 @@ function createPluginHandler(params: {
gifPlayback: params.gifPlayback,
deps: params.deps,
silent: params.silent,
mediaLocalRoots: params.mediaLocalRoots,
payload,
})
: undefined,
@@ -168,6 +173,7 @@ function createPluginHandler(params: {
gifPlayback: params.gifPlayback,
deps: params.deps,
silent: params.silent,
mediaLocalRoots: params.mediaLocalRoots,
}),
sendMedia: async (caption, mediaUrl) =>
sendMedia({
@@ -182,6 +188,7 @@ function createPluginHandler(params: {
gifPlayback: params.gifPlayback,
deps: params.deps,
silent: params.silent,
mediaLocalRoots: params.mediaLocalRoots,
}),
};
}
@@ -203,6 +210,8 @@ type DeliverOutboundPayloadsCoreParams = {
bestEffort?: boolean;
onError?: (err: unknown, payload: NormalizedOutboundPayload) => void;
onPayload?: (payload: NormalizedOutboundPayload) => void;
/** Active agent id for media local-root scoping. */
agentId?: string;
mirror?: {
sessionKey: string;
agentId?: string;
@@ -286,6 +295,10 @@ async function deliverOutboundPayloadsCore(
const deps = params.deps;
const abortSignal = params.abortSignal;
const sendSignal = params.deps?.sendSignal ?? sendMessageSignal;
const mediaLocalRoots = getAgentScopedMediaLocalRoots(
cfg,
params.agentId ?? params.mirror?.agentId,
);
const results: OutboundDeliveryResult[] = [];
const handler = await createChannelHandler({
cfg,
@@ -298,6 +311,7 @@ async function deliverOutboundPayloadsCore(
identity: params.identity,
gifPlayback: params.gifPlayback,
silent: params.silent,
mediaLocalRoots,
});
const textLimit = handler.chunker
? resolveTextChunkLimit(cfg, channel, accountId, {
@@ -400,6 +414,7 @@ async function deliverOutboundPayloadsCore(
accountId: accountId ?? undefined,
textMode: "plain",
textStyles: formatted.styles,
mediaLocalRoots,
})),
};
};

View File

@@ -1,5 +1,6 @@
import type { OpenClawConfig } from "../config/config.js";
import type { SessionEntry, SessionMaintenanceWarning } from "../config/sessions.js";
import { resolveSessionAgentId } from "../agents/agent-scope.js";
import { isDeliverableMessageChannel, normalizeMessageChannel } from "../utils/message-channel.js";
import { resolveSessionDeliveryTarget } from "./outbound/targets.js";
import { enqueueSystemEvent } from "./system-events.js";
@@ -100,6 +101,7 @@ export async function deliverSessionMaintenanceWarning(params: WarningParams): P
accountId: target.accountId,
threadId: target.threadId,
payloads: [{ text }],
agentId: resolveSessionAgentId({ sessionKey: params.sessionKey, config: params.cfg }),
});
} catch (err) {
console.warn(`Failed to deliver session maintenance warning: ${String(err)}`);