fix(agents): preserve trusted tool media provenance

This commit is contained in:
Vincent Koc
2026-05-16 18:28:59 +08:00
parent 856076079e
commit 310c8530eb
7 changed files with 28 additions and 1 deletions
+1
View File
@@ -28,6 +28,7 @@ Docs: https://docs.openclaw.ai
- Telegram/Gateway: route targeted Telegram `/stop@bot` messages onto the control lane without cached bot metadata and match gateway stop requests across raw/canonical session aliases. (#82298) Thanks @VACInc.
- MS Teams/media: sniff inline `data:image/*` attachment bytes before staging them, skipping payloads that are not actually images.
- WebChat/media: require trusted local-media provenance before preserving local audio reply paths for display, so untrusted audio-looking paths go through normal staging and read-policy checks.
- Agents/tool media: preserve trusted local-media provenance when merging generated tool attachments into final reply payloads, so trusted audio/media survives outbound display normalization.
- Update: let package-swap `doctor --fix` persist core config repairs while plugin schemas are still converging, preventing update failures on externalized channel configs.
- Update: carry plugin-validation bypasses into config mutation pre-write reads, so package update doctor repairs can finish while externalized plugin schemas are converging.
- Update/doctor: keep plugin-validation bypasses on the top-level `$include` config write path, so package repair can update included plugin config files without flattening them into the root config.
+1
View File
@@ -2516,6 +2516,7 @@ export async function runEmbeddedPiAgent(
payloads,
toolMediaUrls: attempt.toolMediaUrls,
toolAudioAsVoice: attempt.toolAudioAsVoice,
toolTrustedLocalMedia: attempt.toolTrustedLocalMedia,
});
const timedOutDuringPrompt =
timedOut && !timedOutDuringCompaction && !timedOutDuringToolExecution;
@@ -4277,6 +4277,7 @@ export async function runEmbeddedAttempt(
heartbeatToolResponse: getHeartbeatToolResponse(),
toolMediaUrls: pendingToolMediaReply?.mediaUrls,
toolAudioAsVoice: pendingToolMediaReply?.audioAsVoice,
toolTrustedLocalMedia: pendingToolMediaReply?.trustedLocalMedia,
successfulCronAdds: getSuccessfulCronAdds(),
cloudCodeAssistFormatError: Boolean(
lastAssistant?.errorMessage && isCloudCodeAssistFormatError(lastAssistant.errorMessage),
@@ -70,4 +70,23 @@ describe("mergeAttemptToolMediaPayloads", () => {
deliverDespiteSourceReplySuppression: true,
});
});
it("preserves trusted local media provenance when merging tool media", () => {
expect(
mergeAttemptToolMediaPayloads({
payloads: [{ text: "done" }],
toolMediaUrls: ["/tmp/reply.opus"],
toolAudioAsVoice: true,
toolTrustedLocalMedia: true,
}),
).toEqual([
{
text: "done",
mediaUrls: ["/tmp/reply.opus"],
mediaUrl: "/tmp/reply.opus",
audioAsVoice: true,
trustedLocalMedia: true,
},
]);
});
});
@@ -7,11 +7,12 @@ export function mergeAttemptToolMediaPayloads(params: {
payloads?: EmbeddedRunPayload[];
toolMediaUrls?: string[];
toolAudioAsVoice?: boolean;
toolTrustedLocalMedia?: boolean;
}): EmbeddedRunPayload[] | undefined {
const mediaUrls = Array.from(
new Set(params.toolMediaUrls?.map((url) => url.trim()).filter(Boolean) ?? []),
);
if (mediaUrls.length === 0 && !params.toolAudioAsVoice) {
if (mediaUrls.length === 0 && !params.toolAudioAsVoice && !params.toolTrustedLocalMedia) {
return params.payloads;
}
@@ -25,6 +26,7 @@ export function mergeAttemptToolMediaPayloads(params: {
mediaUrls: mergedMediaUrls.length ? mergedMediaUrls : undefined,
mediaUrl: payload.mediaUrl ?? mergedMediaUrls[0],
audioAsVoice: payload.audioAsVoice || params.toolAudioAsVoice || undefined,
trustedLocalMedia: payload.trustedLocalMedia || params.toolTrustedLocalMedia || undefined,
});
return payloads;
}
@@ -35,6 +37,7 @@ export function mergeAttemptToolMediaPayloads(params: {
mediaUrls: mediaUrls.length ? mediaUrls : undefined,
mediaUrl: mediaUrls[0],
audioAsVoice: params.toolAudioAsVoice || undefined,
trustedLocalMedia: params.toolTrustedLocalMedia || undefined,
},
];
}
@@ -122,6 +122,7 @@ export type EmbeddedRunAttemptResult = {
heartbeatToolResponse?: HeartbeatToolResponse;
toolMediaUrls?: string[];
toolAudioAsVoice?: boolean;
toolTrustedLocalMedia?: boolean;
successfulCronAdds?: number;
cloudCodeAssistFormatError: boolean;
attemptUsage?: NormalizedUsage;
+1
View File
@@ -173,6 +173,7 @@ export type EmbeddedPiRunResult = {
isError?: boolean;
isReasoning?: boolean;
audioAsVoice?: boolean;
trustedLocalMedia?: boolean;
channelData?: Record<string, unknown>;
}>;
meta: EmbeddedPiRunMeta;