mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:50:43 +00:00
fix(signal): classify filename-only voice notes
This commit is contained in:
@@ -72,6 +72,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Signal: preserve sender attachment filenames and resolve missing MIME types from those filenames, so Linux `signal-cli` voice notes without `contentType` still enter audio transcription. Fixes #48614. Thanks @mindfury.
|
||||
- Dashboard/security: avoid writing tokenized Control UI URLs or SSH hints to runtime logs, keeping gateway bearer fragments out of console-captured logs readable through `logs.tail`. (#70029) Thanks @Ziy1-Tan.
|
||||
- Providers/OpenRouter: treat DeepSeek refs as cache-TTL eligible without injecting Anthropic cache-control markers, aligning context pruning with OpenRouter-managed prompt caching. (#51983) Thanks @QuinnH496.
|
||||
- Discord/cron: deliver text-only isolated cron and heartbeat announce output from the canonical final assistant text once, avoiding duplicate Discord posts when streamed block payloads and the final answer contain the same content. Fixes #71406. Thanks @alexgross21.
|
||||
|
||||
@@ -208,6 +208,7 @@ Groups:
|
||||
- Outbound text is chunked to `channels.signal.textChunkLimit` (default 4000).
|
||||
- Optional newline chunking: set `channels.signal.chunkMode="newline"` to split on blank lines (paragraph boundaries) before length chunking.
|
||||
- Attachments supported (base64 fetched from `signal-cli`).
|
||||
- Voice-note attachments use the `signal-cli` filename as a MIME fallback when `contentType` is missing, so audio transcription can still classify AAC voice memos.
|
||||
- Default media cap: `channels.signal.mediaMaxMb` (default 8).
|
||||
- Use `channels.signal.ignoreAttachments` to skip downloading media.
|
||||
- Group history context uses `channels.signal.historyLimit` (or `channels.signal.accounts.*.historyLimit`), falling back to `messages.groupChat.historyLimit`. Set `0` to disable (default 50).
|
||||
|
||||
@@ -7,7 +7,11 @@ import {
|
||||
warnMissingProviderGroupPolicyFallbackOnce,
|
||||
} from "openclaw/plugin-sdk/config-runtime";
|
||||
import { waitForTransportReady } from "openclaw/plugin-sdk/infra-runtime";
|
||||
import { estimateBase64DecodedBytes, saveMediaBuffer } from "openclaw/plugin-sdk/media-runtime";
|
||||
import {
|
||||
detectMime,
|
||||
estimateBase64DecodedBytes,
|
||||
saveMediaBuffer,
|
||||
} from "openclaw/plugin-sdk/media-runtime";
|
||||
import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "openclaw/plugin-sdk/reply-history";
|
||||
import {
|
||||
deliverTextOrMediaReply,
|
||||
@@ -294,11 +298,16 @@ async function fetchAttachment(params: {
|
||||
);
|
||||
}
|
||||
const buffer = Buffer.from(result.data, "base64");
|
||||
const originalFilename = normalizeOptionalString(attachment.filename ?? undefined);
|
||||
const contentType =
|
||||
normalizeOptionalString(attachment.contentType ?? undefined) ??
|
||||
(await detectMime({ buffer, filePath: originalFilename }));
|
||||
const saved = await saveMediaBuffer(
|
||||
buffer,
|
||||
attachment.contentType ?? undefined,
|
||||
contentType,
|
||||
"inbound",
|
||||
params.maxBytes,
|
||||
originalFilename,
|
||||
);
|
||||
return { path: saved.path, contentType: saved.contentType };
|
||||
}
|
||||
|
||||
@@ -295,6 +295,37 @@ describe("signal createSignalEventHandler inbound context", () => {
|
||||
expect(capture.ctx?.MediaTypes).toEqual(["image/jpeg", "application/octet-stream"]);
|
||||
});
|
||||
|
||||
it("threads resolved audio contentType for Signal voice attachments", async () => {
|
||||
const handler = createSignalEventHandler(
|
||||
createBaseSignalEventHandlerDeps({
|
||||
cfg: {
|
||||
messages: { inbound: { debounceMs: 0 } },
|
||||
channels: { signal: { dmPolicy: "open", allowFrom: ["*"] } },
|
||||
},
|
||||
ignoreAttachments: false,
|
||||
fetchAttachment: async ({ attachment }) => ({
|
||||
path: `/tmp/${String(attachment.id)}.aac`,
|
||||
contentType: "audio/aac",
|
||||
}),
|
||||
historyLimit: 0,
|
||||
}),
|
||||
);
|
||||
|
||||
await handler(
|
||||
createSignalReceiveEvent({
|
||||
dataMessage: {
|
||||
message: "",
|
||||
attachments: [{ id: "voice1", contentType: undefined, filename: "voice.aac" }],
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(capture.ctx).toBeTruthy();
|
||||
expect(capture.ctx?.MediaPath).toBe("/tmp/voice1.aac");
|
||||
expect(capture.ctx?.MediaType).toBe("audio/aac");
|
||||
expect(capture.ctx?.MediaTypes).toEqual(["audio/aac"]);
|
||||
});
|
||||
|
||||
it("drops own UUID inbound messages when only accountUuid is configured", async () => {
|
||||
const ownUuid = "123e4567-e89b-12d3-a456-426614174000";
|
||||
const handler = createSignalEventHandler(
|
||||
|
||||
@@ -110,6 +110,11 @@ describe("mime detection", () => {
|
||||
const mime = await detectMime({ filePath: "/tmp/style.css" });
|
||||
expect(mime).toBe("text/css");
|
||||
});
|
||||
|
||||
it("detects AAC from a bare filename when buffer sniffing is inconclusive", async () => {
|
||||
const mime = await detectMime({ buffer: Buffer.alloc(16), filePath: "voice.aac" });
|
||||
expect(mime).toBe("audio/aac");
|
||||
});
|
||||
});
|
||||
|
||||
describe("extensionForMime", () => {
|
||||
|
||||
Reference in New Issue
Block a user