

























@@ -231,6 +231,7 @@ const {
231231 mockResolveBoundConversation,
232232 mockTouchBinding,
233233 mockResolveFeishuReasoningPreviewEnabled,
234+ mockTranscribeFirstAudio,
234235} = vi.hoisted(() => ({
235236mockCreateFeishuReplyDispatcher: vi.fn(() => ({
236237dispatcher: createReplyDispatcher(),
@@ -265,6 +266,7 @@ const {
265266mockResolveBoundConversation: vi.fn((_ref?: unknown) => null as BoundConversation),
266267mockTouchBinding: vi.fn(),
267268mockResolveFeishuReasoningPreviewEnabled: vi.fn(() => false),
269+mockTranscribeFirstAudio: vi.fn(),
268270}));
269271270272vi.mock("./reply-dispatcher.js", () => ({
@@ -285,6 +287,10 @@ vi.mock("./media.js", () => ({
285287downloadMessageResourceFeishu: mockDownloadMessageResourceFeishu,
286288}));
287289290+vi.mock("./audio-preflight.runtime.js", () => ({
291+transcribeFirstAudio: mockTranscribeFirstAudio,
292+}));
293+288294vi.mock("./client.js", () => ({
289295createFeishuClient: mockCreateFeishuClient,
290296}));
@@ -357,6 +363,7 @@ describe("handleFeishuMessage ACP routing", () => {
357363mockResolveBoundConversation.mockReset().mockReturnValue(null);
358364mockTouchBinding.mockReset();
359365mockResolveFeishuReasoningPreviewEnabled.mockReset().mockReturnValue(false);
366+mockTranscribeFirstAudio.mockReset().mockResolvedValue(undefined);
360367mockResolveAgentRoute.mockReset().mockReturnValue({
361368 ...buildDefaultResolveRoute(),
362369sessionKey: "agent:main:feishu:direct:ou_sender_1",
@@ -555,6 +562,7 @@ describe("handleFeishuMessage command authorization", () => {
555562mockEnsureConfiguredBindingRouteReady.mockReset().mockResolvedValue({ ok: true });
556563mockResolveBoundConversation.mockReset().mockReturnValue(null);
557564mockTouchBinding.mockReset();
565+mockTranscribeFirstAudio.mockReset().mockResolvedValue(undefined);
558566mockResolveAgentRoute.mockReturnValue(buildDefaultResolveRoute());
559567mockCreateFeishuClient.mockReturnValue({
560568contact: {
@@ -1438,6 +1446,78 @@ describe("handleFeishuMessage command authorization", () => {
14381446expect(mockDispatchReplyFromConfig).not.toHaveBeenCalled();
14391447});
144014481449+it("transcribes inbound audio before building the agent turn", async () => {
1450+mockShouldComputeCommandAuthorized.mockReturnValue(false);
1451+mockDownloadMessageResourceFeishu.mockResolvedValueOnce({
1452+buffer: Buffer.from("voice"),
1453+contentType: "audio/ogg",
1454+fileName: "voice.ogg",
1455+});
1456+mockSaveMediaBuffer.mockResolvedValueOnce({
1457+id: "inbound-voice.ogg",
1458+path: "/tmp/inbound-voice.ogg",
1459+size: Buffer.byteLength("voice"),
1460+contentType: "audio/ogg",
1461+});
1462+mockTranscribeFirstAudio.mockResolvedValueOnce("voice transcript");
1463+1464+const cfg: ClawdbotConfig = {
1465+channels: {
1466+feishu: {
1467+dmPolicy: "open",
1468+},
1469+},
1470+} as ClawdbotConfig;
1471+1472+const event: FeishuMessageEvent = {
1473+sender: {
1474+sender_id: {
1475+open_id: "ou-voice",
1476+},
1477+},
1478+message: {
1479+message_id: "msg-audio-inbound",
1480+chat_id: "oc-dm",
1481+chat_type: "p2p",
1482+message_type: "audio",
1483+content: JSON.stringify({
1484+file_key: "file_audio_payload",
1485+duration: 1200,
1486+}),
1487+},
1488+};
1489+1490+await dispatchMessage({ cfg, event });
1491+1492+expect(mockDownloadMessageResourceFeishu).toHaveBeenCalledWith(
1493+expect.objectContaining({
1494+messageId: "msg-audio-inbound",
1495+fileKey: "file_audio_payload",
1496+type: "file",
1497+}),
1498+);
1499+expect(mockTranscribeFirstAudio).toHaveBeenCalledWith({
1500+ctx: {
1501+MediaPaths: ["/tmp/inbound-voice.ogg"],
1502+MediaTypes: ["audio/ogg"],
1503+ChatType: "direct",
1504+},
1505+ cfg,
1506+});
1507+expect(mockFinalizeInboundContext).toHaveBeenCalledWith(
1508+expect.objectContaining({
1509+BodyForAgent: "[message_id: msg-audio-inbound]\nou-voice: voice transcript",
1510+RawBody: "voice transcript",
1511+CommandBody: "voice transcript",
1512+Transcript: "voice transcript",
1513+MediaPaths: ["/tmp/inbound-voice.ogg"],
1514+MediaTypes: ["audio/ogg"],
1515+}),
1516+);
1517+const finalized = mockFinalizeInboundContext.mock.calls[0]?.[0];
1518+expect(finalized.BodyForAgent).not.toContain("file_audio_payload");
1519+});
1520+14411521it("uses video file_key (not thumbnail image_key) for inbound video download", async () => {
14421522mockShouldComputeCommandAuthorized.mockReturnValue(false);
14431523此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。