@agent-wechat/agent-wechat 0.14.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +206 -111
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1127,6 +1127,9 @@ function getWeChatRuntime() {
1127
1127
  return runtime;
1128
1128
  }
1129
1129
 
1130
+ // src/monitor.ts
1131
+ import { createHash as createHash3 } from "node:crypto";
1132
+
1130
1133
  // ../shared/dist/client.js
1131
1134
  function normalizeUrl(base) {
1132
1135
  const url = base.startsWith("http") ? base : `http://${base}`;
@@ -1230,6 +1233,23 @@ var WeChatClient = class {
1230
1233
  async sendMessage(params) {
1231
1234
  return this.post("/api/messages/send", params);
1232
1235
  }
1236
+ async createVoiceJob(chatId, audio, idempotencyKey) {
1237
+ const form = new FormData();
1238
+ form.set("chatId", chatId);
1239
+ form.set("audio", new Blob([new Uint8Array(audio)]), "voice-audio");
1240
+ const headers = { ...this.headers, "Idempotency-Key": idempotencyKey };
1241
+ delete headers["Content-Type"];
1242
+ const res = await fetch(`${this.base}/api/messages/voice`, { method: "POST", headers, body: form });
1243
+ if (!res.ok)
1244
+ throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`);
1245
+ return res.json();
1246
+ }
1247
+ async getVoiceJob(jobId) {
1248
+ return this.get(`/api/messages/voice/${encodeURIComponent(jobId)}`);
1249
+ }
1250
+ async cancelVoiceJob(jobId) {
1251
+ return this.post(`/api/messages/voice/${encodeURIComponent(jobId)}/cancel`);
1252
+ }
1233
1253
  // ---- Debug ----
1234
1254
  async screenshot() {
1235
1255
  return this.get("/api/debug/screenshot");
@@ -5543,6 +5563,9 @@ function attachmentKindForMessageType(baseType) {
5543
5563
  if (baseType === 49) return "file";
5544
5564
  return void 0;
5545
5565
  }
5566
+ function mediaRequestQuality(baseType) {
5567
+ return baseType === 3 ? "best" : "full";
5568
+ }
5546
5569
  function attachmentKindForResult(result, fallback) {
5547
5570
  if (result.type === "image" || result.type === "emoji") return "image";
5548
5571
  if (result.type === "voice") return "audio";
@@ -5595,8 +5618,11 @@ function renderAttachmentBody(originalBody, attachment, exposeNonVisualPath = tr
5595
5618
  return appendReference(originalBody || "<media:video>", `[Video file: ${attachment.path}]`);
5596
5619
  }
5597
5620
  }
5598
- if (!originalBody) return attachment.kind === "image" ? "<media:image>" : originalBody;
5599
- return originalBody;
5621
+ const body = originalBody || (attachment.kind === "image" ? "<media:image>" : originalBody);
5622
+ if (attachment.kind === "image" && attachment.quality === "thumbnail") {
5623
+ return appendReference(body, "[Image preview only: thumbnail quality]");
5624
+ }
5625
+ return body;
5600
5626
  }
5601
5627
  function applyLiveAttachment(message, originalBody, attachment) {
5602
5628
  const attachable = attachment?.status === "ready" && attachment.path && attachment.kind !== "file";
@@ -5779,6 +5805,131 @@ async function listMessageWindow(client, chatId, previousLastSeen, firstPoll, un
5779
5805
  return messages;
5780
5806
  }
5781
5807
 
5808
+ // src/outbound-media.ts
5809
+ import { randomUUID as randomUUID2 } from "node:crypto";
5810
+ import { readFile } from "node:fs/promises";
5811
+ import { basename, extname } from "node:path";
5812
+ var EXT_MIME = {
5813
+ png: "image/png",
5814
+ jpg: "image/jpeg",
5815
+ jpeg: "image/jpeg",
5816
+ gif: "image/gif",
5817
+ webp: "image/webp",
5818
+ mp3: "audio/mpeg",
5819
+ wav: "audio/wav",
5820
+ ogg: "audio/ogg",
5821
+ opus: "audio/ogg",
5822
+ m4a: "audio/mp4",
5823
+ flac: "audio/flac",
5824
+ aac: "audio/aac"
5825
+ };
5826
+ function isTerminal(job) {
5827
+ return ["completed", "failed", "cancelled", "needs_review"].includes(job.status);
5828
+ }
5829
+ async function waitForVoiceJob(client, jobId) {
5830
+ const deadline = Date.now() + 25 * 6e4;
5831
+ let lastStatusAt = Date.now();
5832
+ while (Date.now() < deadline) {
5833
+ let job;
5834
+ try {
5835
+ job = await client.getVoiceJob(jobId);
5836
+ lastStatusAt = Date.now();
5837
+ } catch (error) {
5838
+ if (Date.now() - lastStatusAt > 3e4) {
5839
+ throw new Error(`Voice job ${jobId} status is unavailable: ${String(error)}. Inspect the job before retrying.`);
5840
+ }
5841
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
5842
+ continue;
5843
+ }
5844
+ if (isTerminal(job)) {
5845
+ if (job.status === "completed") return job;
5846
+ const sent = job.chunks.filter((chunk) => chunk.status === "verified").length;
5847
+ throw new Error(`Voice job ${jobId} ${job.status}: ${sent}/${job.chunks.length} notes verified. ${job.error ?? ""} Do not resend the whole recording.`);
5848
+ }
5849
+ await new Promise((resolve) => setTimeout(resolve, 1e3));
5850
+ }
5851
+ throw new Error(`Voice job ${jobId} exceeded the 25-minute wait. Inspect the job before retrying.`);
5852
+ }
5853
+ async function sendWeChatMedia(cfg, to, text, mediaUrl, audioAsVoice = false, idempotencyKey) {
5854
+ const account = resolveWeChatAccount(cfg);
5855
+ if (!account?.serverUrl) throw new Error("No serverUrl configured");
5856
+ const client = new WeChatClient({ baseUrl: account.serverUrl, token: account.token });
5857
+ if (!mediaUrl) {
5858
+ if (audioAsVoice) throw new Error("Voice sending requires an audio attachment");
5859
+ const result2 = await client.sendMessage({ chatId: to, text: text || void 0 });
5860
+ if (!result2.success) throw new Error(result2.error ?? "Send failed");
5861
+ return `agent-wechat:${to}:${Date.now()}`;
5862
+ }
5863
+ let bytes;
5864
+ let mimeType;
5865
+ let filename;
5866
+ if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
5867
+ const res = await fetch(mediaUrl);
5868
+ if (!res.ok) throw new Error(`Media fetch failed: ${res.status}`);
5869
+ bytes = new Uint8Array(await res.arrayBuffer());
5870
+ mimeType = res.headers.get("content-type")?.split(";")[0] ?? "application/octet-stream";
5871
+ filename = basename(new URL(mediaUrl).pathname) || "file";
5872
+ if (mimeType === "application/octet-stream") {
5873
+ mimeType = EXT_MIME[extname(filename).toLowerCase().slice(1)] ?? mimeType;
5874
+ }
5875
+ } else {
5876
+ const localPath = mediaUrl.startsWith("file://") ? new URL(mediaUrl) : mediaUrl;
5877
+ const pathText = localPath instanceof URL ? localPath.pathname : localPath;
5878
+ bytes = new Uint8Array(await readFile(localPath));
5879
+ filename = basename(pathText);
5880
+ mimeType = EXT_MIME[extname(filename).toLowerCase().slice(1)] ?? "application/octet-stream";
5881
+ }
5882
+ if (audioAsVoice) {
5883
+ if (!mimeType.startsWith("audio/")) throw new Error(`Voice media must be audio, got ${mimeType}`);
5884
+ const job = await client.createVoiceJob(to, bytes, idempotencyKey ?? randomUUID2());
5885
+ await waitForVoiceJob(client, job.jobId);
5886
+ if (text) {
5887
+ const caption = await client.sendMessage({ chatId: to, text });
5888
+ if (!caption.success) throw new Error(`Voice job ${job.jobId} completed, but caption failed: ${caption.error ?? "unknown error"}`);
5889
+ }
5890
+ return `agent-wechat:voice:${job.jobId}`;
5891
+ }
5892
+ const base64 = Buffer.from(bytes).toString("base64");
5893
+ const result = mimeType.startsWith("image/") ? await client.sendMessage({ chatId: to, text: text || void 0, image: { data: base64, mimeType } }) : await client.sendMessage({ chatId: to, text: text || void 0, file: { data: base64, filename } });
5894
+ if (!result.success) throw new Error(result.error ?? "Send media failed");
5895
+ return `agent-wechat:${to}:${Date.now()}`;
5896
+ }
5897
+
5898
+ // src/media-poll.ts
5899
+ function rejectsBestQuality(error) {
5900
+ return error instanceof Error && /^(400|422)\b/.test(error.message) && /\bbest\b/i.test(error.message);
5901
+ }
5902
+ async function pollMedia(client, chatId, localId, quality, log, maxAttempts = 15, intervalMs = 1e3) {
5903
+ let lastResult = null;
5904
+ let legacyImageServer = false;
5905
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
5906
+ let result;
5907
+ if (legacyImageServer) {
5908
+ const full = await client.getMedia(chatId, localId, "full");
5909
+ if (full.data || full.type === "unsupported") return full;
5910
+ const preview = await client.getMedia(chatId, localId, "thumbnail");
5911
+ result = preview.data ? { ...preview, quality: "thumbnail" } : full;
5912
+ } else {
5913
+ try {
5914
+ result = await client.getMedia(chatId, localId, quality);
5915
+ } catch (error) {
5916
+ if (quality !== "best" || !rejectsBestQuality(error)) throw error;
5917
+ legacyImageServer = true;
5918
+ log?.info?.("[media] Server does not support best image quality; trying full then thumbnail");
5919
+ attempt--;
5920
+ continue;
5921
+ }
5922
+ }
5923
+ lastResult = result;
5924
+ if (result.type === "unsupported" || result.data) return result;
5925
+ if (attempt < maxAttempts) {
5926
+ log?.info?.(`[media] Attempt ${attempt}/${maxAttempts} for ${chatId}:${localId} returned no data, retrying...`);
5927
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
5928
+ }
5929
+ }
5930
+ return lastResult;
5931
+ }
5932
+
5782
5933
  // src/access-control.ts
5783
5934
  import {
5784
5935
  buildChannelKeyCandidates,
@@ -5989,6 +6140,7 @@ function resolveWeChatMentionGate(params) {
5989
6140
  // src/monitor.ts
5990
6141
  var HISTORY_CONTEXT_MARKER = "[Chat messages since your last reply - for context]";
5991
6142
  var CURRENT_MESSAGE_MARKER = "[Current message - respond to this]";
6143
+ var voiceTranscriptCache = /* @__PURE__ */ new Map();
5992
6144
  function isProcessedMessage(value) {
5993
6145
  if (!value || typeof value !== "object") return false;
5994
6146
  const message = value;
@@ -6006,24 +6158,6 @@ function sleep(ms, signal) {
6006
6158
  });
6007
6159
  });
6008
6160
  }
6009
- async function pollMedia(client, chatId, localId, log, maxAttempts = 15, intervalMs = 1e3) {
6010
- let lastResult = null;
6011
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
6012
- const result = await client.getMedia(chatId, localId, "full");
6013
- lastResult = result;
6014
- if (result.type === "unsupported") {
6015
- return result;
6016
- }
6017
- if (result.data) {
6018
- return result;
6019
- }
6020
- if (attempt < maxAttempts) {
6021
- log?.info?.(`[media] Attempt ${attempt}/${maxAttempts} for ${chatId}:${localId} returned no data, retrying...`);
6022
- await new Promise((r) => setTimeout(r, intervalMs));
6023
- }
6024
- }
6025
- return lastResult;
6026
- }
6027
6161
  function enqueueWeChatSystemEvent(text, contextKey) {
6028
6162
  try {
6029
6163
  const core = getWeChatRuntime();
@@ -6040,7 +6174,7 @@ async function retrieveAttachment(client, chatId, msg, liveAccount, log, maxAtte
6040
6174
  if (!expectedKind) return void 0;
6041
6175
  const fallbackFilename = attachmentFallbackFilename(expectedKind, msg.localId, msg.content);
6042
6176
  try {
6043
- const result = await pollMedia(client, chatId, msg.localId, log, maxAttempts);
6177
+ const result = await pollMedia(client, chatId, msg.localId, mediaRequestQuality(baseType), log, maxAttempts);
6044
6178
  if (!result) {
6045
6179
  return { kind: expectedKind, status: "unavailable", filename: fallbackFilename };
6046
6180
  }
@@ -6062,9 +6196,9 @@ async function retrieveAttachment(client, chatId, msg, liveAccount, log, maxAtte
6062
6196
  saveMediaBuffer: getWeChatRuntime().channel.media.saveMediaBuffer
6063
6197
  });
6064
6198
  log?.info?.(
6065
- `[wechat:${liveAccount.accountId}] Saved ${kind} attachment for msg ${msg.localId} (${saved.size} bytes)`
6199
+ `[wechat:${liveAccount.accountId}] Saved ${kind} attachment for msg ${msg.localId} (${saved.size} bytes${result.quality ? `, ${result.quality} quality` : ""})`
6066
6200
  );
6067
- return { kind, status: "ready", filename: saved.filename, mime, path: saved.path };
6201
+ return { kind, status: "ready", filename: saved.filename, mime, path: saved.path, quality: result.quality };
6068
6202
  } catch (error) {
6069
6203
  const status = error instanceof AttachmentTooLargeError ? "too_large" : "error";
6070
6204
  log?.error?.(
@@ -6360,6 +6494,36 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6360
6494
  return "ignored";
6361
6495
  }
6362
6496
  try {
6497
+ for (const item of segment) {
6498
+ const attachment = item.attachment;
6499
+ if (item.mediaPath || attachment?.kind !== "audio" || attachment.status !== "ready" || !attachment.path) continue;
6500
+ const cacheKey = `${liveAccount.accountId}:${chatId}:${item.msg.localId}:${attachment.path}`;
6501
+ let transcript = voiceTranscriptCache.get(cacheKey);
6502
+ if (!transcript) {
6503
+ try {
6504
+ const result = await core.mediaUnderstanding.transcribeAudioFile({
6505
+ filePath: attachment.path,
6506
+ cfg,
6507
+ mime: attachment.mime
6508
+ });
6509
+ transcript = result.text?.trim();
6510
+ if (transcript) {
6511
+ if (voiceTranscriptCache.size >= 500) voiceTranscriptCache.clear();
6512
+ voiceTranscriptCache.set(cacheKey, transcript);
6513
+ }
6514
+ } catch (error) {
6515
+ log?.error?.(`[wechat:${liveAccount.accountId}] Voice transcription failed for ${item.msg.localId}: ${String(error)}`);
6516
+ }
6517
+ }
6518
+ if (transcript) {
6519
+ item.rawBody += `
6520
+ [Voice transcript: ${transcript.slice(0, 1e4)}]`;
6521
+ item.commandBody = normalizeWeChatCommandBody(item.rawBody, {
6522
+ isGroup: item.isGroup,
6523
+ wasMentioned: item.isMentioned
6524
+ });
6525
+ }
6526
+ }
6363
6527
  const route = core.channel.routing.resolveAgentRoute({
6364
6528
  cfg,
6365
6529
  channel: "agent-wechat",
@@ -6388,7 +6552,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6388
6552
  timestamp,
6389
6553
  previousTimestamp,
6390
6554
  envelope: envelopeOptions,
6391
- body: isGroup ? `${senderName}: ${rawBody}` : rawBody
6555
+ body: isGroup ? `${senderName}: ${lastMsg.rawBody}` : lastMsg.rawBody
6392
6556
  });
6393
6557
  } else {
6394
6558
  const historyMessages = segment.slice(0, -1);
@@ -6408,7 +6572,7 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6408
6572
  timestamp,
6409
6573
  previousTimestamp,
6410
6574
  envelope: envelopeOptions,
6411
- body: isGroup ? `${senderName}: ${rawBody}` : rawBody
6575
+ body: isGroup ? `${senderName}: ${lastMsg.rawBody}` : lastMsg.rawBody
6412
6576
  });
6413
6577
  body = [
6414
6578
  HISTORY_CONTEXT_MARKER,
@@ -6430,8 +6594,8 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6430
6594
  }
6431
6595
  const ctxPayload = core.channel.reply.finalizeInboundContext({
6432
6596
  Body: body,
6433
- BodyForAgent: rawBody,
6434
- RawBody: rawBody,
6597
+ BodyForAgent: lastMsg.rawBody,
6598
+ RawBody: lastMsg.rawBody,
6435
6599
  CommandBody: commandBody,
6436
6600
  InboundHistory: inboundHistory,
6437
6601
  From: isGroup ? `wechat:group:${chatId}` : `wechat:${senderId}`,
@@ -6473,12 +6637,14 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6473
6637
  accountId: liveAccount.accountId
6474
6638
  });
6475
6639
  let replyFailed = false;
6640
+ let deliverySequence = 0;
6476
6641
  await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
6477
6642
  ctx: ctxPayload,
6478
6643
  cfg,
6479
6644
  dispatcherOptions: {
6480
6645
  ...prefixOptions,
6481
6646
  deliver: async (payload) => {
6647
+ const deliveryIndex = deliverySequence++;
6482
6648
  const mediaList = payload.mediaUrls?.length ? payload.mediaUrls : payload.mediaUrl ? [payload.mediaUrl] : [];
6483
6649
  const tableMode = core.channel.text.resolveMarkdownTableMode({
6484
6650
  cfg,
@@ -6490,49 +6656,17 @@ async function dispatchSegment(segment, client, chatId, chat, liveAccount, polic
6490
6656
  tableMode
6491
6657
  );
6492
6658
  if (mediaList.length > 0) {
6493
- for (const mediaUrl of mediaList) {
6494
- try {
6495
- const fsmod = await import("fs/promises");
6496
- const pathmod = await import("path");
6497
- let base64;
6498
- let mimeType;
6499
- let filename;
6500
- if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
6501
- const res = await fetch(mediaUrl);
6502
- const buffer = await res.arrayBuffer();
6503
- base64 = Buffer.from(buffer).toString("base64");
6504
- mimeType = res.headers.get("content-type") ?? "application/octet-stream";
6505
- const urlPath = new URL(mediaUrl).pathname;
6506
- filename = pathmod.basename(urlPath) || "file";
6507
- } else {
6508
- const buf = await fsmod.readFile(mediaUrl);
6509
- base64 = buf.toString("base64");
6510
- filename = pathmod.basename(mediaUrl);
6511
- const ext = pathmod.extname(mediaUrl).toLowerCase().replace(".", "");
6512
- const extMime = {
6513
- png: "image/png",
6514
- jpg: "image/jpeg",
6515
- jpeg: "image/jpeg",
6516
- gif: "image/gif",
6517
- webp: "image/webp"
6518
- };
6519
- mimeType = extMime[ext] ?? "application/octet-stream";
6520
- }
6521
- const isImage = mimeType.startsWith("image/");
6522
- if (isImage) {
6523
- await client.sendMessage({ chatId, image: { data: base64, mimeType } });
6524
- } else {
6525
- await client.sendMessage({ chatId, file: { data: base64, filename } });
6526
- }
6527
- } catch (err) {
6528
- log?.error?.(`[wechat:${liveAccount.accountId}] Failed to send media: ${err}`);
6529
- }
6659
+ for (let index = 0; index < mediaList.length; index++) {
6660
+ const retryKey = createHash3("sha256").update(`${liveAccount.accountId}\0${chatId}\0${msg.localId}\0${deliveryIndex}\0${index}`).digest("hex");
6661
+ await sendWeChatMedia(cfg, chatId, "", mediaList[index], payload.audioAsVoice === true, retryKey);
6530
6662
  }
6531
6663
  if (text) {
6532
- await client.sendMessage({ chatId, text });
6664
+ const result = await client.sendMessage({ chatId, text });
6665
+ if (!result.success) throw new Error(result.error ?? "Text send failed");
6533
6666
  }
6534
6667
  } else if (text) {
6535
- await client.sendMessage({ chatId, text });
6668
+ const result = await client.sendMessage({ chatId, text });
6669
+ if (!result.success) throw new Error(result.error ?? "Text send failed");
6536
6670
  }
6537
6671
  },
6538
6672
  onError: (err, info) => {
@@ -7109,46 +7243,6 @@ async function sendWeChatText(cfg, to, text) {
7109
7243
  if (!result.success) throw new Error(result.error ?? "Send failed");
7110
7244
  return `agent-wechat:${to}:${Date.now()}`;
7111
7245
  }
7112
- async function sendWeChatMedia(cfg, to, text, mediaUrl) {
7113
- const account = resolveWeChatAccount(cfg);
7114
- if (!account?.serverUrl) throw new Error("No serverUrl configured");
7115
- const client = new WeChatClient({ baseUrl: account.serverUrl, token: account.token });
7116
- if (!mediaUrl) {
7117
- const result2 = await client.sendMessage({ chatId: to, text: text || void 0 });
7118
- if (!result2.success) throw new Error(result2.error ?? "Send failed");
7119
- return `agent-wechat:${to}:${Date.now()}`;
7120
- }
7121
- const fsmod = await import("fs/promises");
7122
- const pathmod = await import("path");
7123
- let base64;
7124
- let mimeType;
7125
- let filename;
7126
- if (mediaUrl.startsWith("http://") || mediaUrl.startsWith("https://")) {
7127
- const res = await fetch(mediaUrl);
7128
- const buffer = await res.arrayBuffer();
7129
- base64 = Buffer.from(buffer).toString("base64");
7130
- mimeType = res.headers.get("content-type") ?? "application/octet-stream";
7131
- const urlPath = new URL(mediaUrl).pathname;
7132
- filename = pathmod.basename(urlPath) || "file";
7133
- } else {
7134
- const buf = await fsmod.readFile(mediaUrl);
7135
- base64 = buf.toString("base64");
7136
- filename = pathmod.basename(mediaUrl);
7137
- const ext = pathmod.extname(mediaUrl).toLowerCase().replace(".", "");
7138
- const extMime = {
7139
- png: "image/png",
7140
- jpg: "image/jpeg",
7141
- jpeg: "image/jpeg",
7142
- gif: "image/gif",
7143
- webp: "image/webp"
7144
- };
7145
- mimeType = extMime[ext] ?? "application/octet-stream";
7146
- }
7147
- const isImage = mimeType.startsWith("image/");
7148
- const result = isImage ? await client.sendMessage({ chatId: to, text: text || void 0, image: { data: base64, mimeType } }) : await client.sendMessage({ chatId: to, text: text || void 0, file: { data: base64, filename } });
7149
- if (!result.success) throw new Error(result.error ?? "Send media failed");
7150
- return `agent-wechat:${to}:${Date.now()}`;
7151
- }
7152
7246
  var wechatMessageAdapter = createChannelMessageAdapterFromOutbound({
7153
7247
  id: "agent-wechat",
7154
7248
  capabilities: {
@@ -7161,9 +7255,9 @@ var wechatMessageAdapter = createChannelMessageAdapterFromOutbound({
7161
7255
  channel: "agent-wechat",
7162
7256
  messageId: await sendWeChatText(cfg, to, text)
7163
7257
  }),
7164
- sendMedia: async ({ cfg, to, text, mediaUrl }) => ({
7258
+ sendMedia: async ({ cfg, to, text, mediaUrl, audioAsVoice }) => ({
7165
7259
  channel: "agent-wechat",
7166
- messageId: await sendWeChatMedia(cfg, to, text, mediaUrl)
7260
+ messageId: await sendWeChatMedia(cfg, to, text, mediaUrl, audioAsVoice)
7167
7261
  })
7168
7262
  }
7169
7263
  });
@@ -7184,7 +7278,8 @@ var wechatPlugin = {
7184
7278
  reactions: false,
7185
7279
  threads: false,
7186
7280
  media: true,
7187
- reply: true
7281
+ reply: true,
7282
+ tts: { voice: { synthesisTarget: "voice-note", transcodesAudio: true } }
7188
7283
  },
7189
7284
  reload: { configPrefixes: ["channels.agent-wechat"] },
7190
7285
  configSchema: {
@@ -7311,9 +7406,9 @@ var wechatPlugin = {
7311
7406
  channel: "agent-wechat",
7312
7407
  messageId: await sendWeChatText(cfg, to, text)
7313
7408
  }),
7314
- sendMedia: async ({ cfg, to, text, mediaUrl }) => ({
7409
+ sendMedia: async ({ cfg, to, text, mediaUrl, audioAsVoice }) => ({
7315
7410
  channel: "agent-wechat",
7316
- messageId: await sendWeChatMedia(cfg, to, text, mediaUrl)
7411
+ messageId: await sendWeChatMedia(cfg, to, text, mediaUrl, audioAsVoice)
7317
7412
  })
7318
7413
  },
7319
7414
  message: wechatMessageAdapter,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-wechat/agent-wechat",
3
- "version": "0.14.0",
3
+ "version": "0.15.1",
4
4
  "type": "module",
5
5
  "files": [
6
6
  "dist",
@@ -11,7 +11,7 @@
11
11
  "esbuild": "^0.25.0",
12
12
  "openclaw": "^2026.8.1",
13
13
  "typescript": "^5.4.5",
14
- "@agent-wechat/shared": "0.2.0"
14
+ "@agent-wechat/shared": "0.3.1"
15
15
  },
16
16
  "openclaw": {
17
17
  "extensions": [