clawgram 2.7.0 → 2.7.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.
package/dist/channel.js CHANGED
@@ -781,7 +781,16 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
781
781
  // A suppressed silent reply legitimately delivers nothing, so
782
782
  // this fallback fires right after it. Without the same check
783
783
  // the token would be read back from the transcript and sent.
784
- const visibleFallbackText = fallbackText ? (0, helpers_1.stripSilentReplyToken)(fallbackText) : "";
784
+ //
785
+ // TTS markup needs the same treatment for the same reason:
786
+ // core strips it on the normal reply path, but this text comes
787
+ // straight out of the transcript. On 2026-08-08 a group got
788
+ // `[[tts:text]]Привет, Вася!…[[/tts:text]]` verbatim. The
789
+ // spoken words are kept — a synthesis that did not happen
790
+ // should degrade to readable text, not to markup.
791
+ const visibleFallbackText = fallbackText
792
+ ? (0, helpers_1.stripTtsDirectives)((0, helpers_1.stripSilentReplyToken)(fallbackText))
793
+ : "";
785
794
  if (fallbackText && !visibleFallbackText) {
786
795
  log?.info?.("clawgram skipping silent transcript fallback", {
787
796
  accountId,
@@ -1634,25 +1643,32 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
1634
1643
  }
1635
1644
  actionLog.info("clawgram outbound sendMedia", {
1636
1645
  accountId: ctx.accountId,
1637
- to: ctx.to,
1646
+ rawTo: ctx.to,
1638
1647
  replyToId: ctx.replyToId ?? null,
1639
1648
  threadId: ctx.threadId ?? null,
1640
1649
  filePath: ctx.filePath ?? null,
1641
1650
  mediaUrl: ctx.mediaUrl ?? null,
1642
1651
  hasText: Boolean(ctx.text),
1643
1652
  hasCaption: Boolean(ctx.caption),
1653
+ asVoice: ctx.audioAsVoice === true,
1644
1654
  });
1645
1655
  const file = ctx.filePath ?? ctx.mediaUrl;
1646
1656
  if (!file) {
1647
1657
  throw new Error("clawgram: sendMedia requires filePath or mediaUrl");
1648
1658
  }
1649
1659
  const messageThreadId = parseOptionalThreadId(ctx.threadId);
1660
+ // Same normalization `sendText` does two functions up. Without it the
1661
+ // channel prefix reaches peer resolution and the send throws — which is
1662
+ // exactly how a synthesized group reply died on 2026-08-08, silently
1663
+ // enough that the transcript fallback posted it as raw text instead.
1664
+ const target = (0, helpers_1.normalizeOutboundTarget)(ctx.to);
1650
1665
  const sent = await gram.sendMedia({
1651
- target: ctx.to,
1666
+ target,
1652
1667
  file,
1653
1668
  caption: ctx.caption ?? ctx.text,
1654
1669
  replyToMessageId: (0, helpers_1.resolveReplyToMessageIdForTarget)(ctx.to, ctx.replyToId),
1655
1670
  messageThreadId,
1671
+ asVoice: ctx.audioAsVoice === true,
1656
1672
  });
1657
1673
  actionLog.info("clawgram outbound sendMedia completed", {
1658
1674
  accountId: ctx.accountId,
package/dist/helpers.js CHANGED
@@ -16,6 +16,7 @@ exports.resolveActionTarget = resolveActionTarget;
16
16
  exports.resolveReplyToMessageIdForTarget = resolveReplyToMessageIdForTarget;
17
17
  exports.readMessageText = readMessageText;
18
18
  exports.readVoiceNoteFlag = readVoiceNoteFlag;
19
+ exports.stripTtsDirectives = stripTtsDirectives;
19
20
  exports.resolveAllowFrom = resolveAllowFrom;
20
21
  exports.resolveGroupPolicy = resolveGroupPolicy;
21
22
  exports.resolveGroups = resolveGroups;
@@ -194,6 +195,28 @@ function resolveReplyToMessageIdForTarget(rawTarget, replyToId) {
194
195
  }
195
196
  return undefined;
196
197
  }
198
+ /**
199
+ * Разметка синтеза речи, которая не должна доехать до человека.
200
+ *
201
+ * Core вырезает `[[tts:...]]` из видимого текста сам, но только на штатном
202
+ * пути ответа. Аварийный путь (`readLatestAssistantFallbackFromTranscript`)
203
+ * читает сырой текст из стенограммы, поэтому 2026-08-08 в групповой чат
204
+ * ушло `[[tts:text]]Привет, Вася!…[[/tts:text]]` как есть.
205
+ *
206
+ * Блок `[[tts:text]]…[[/tts:text]]` РАЗВОРАЧИВАЕТСЯ, а не удаляется: внутри
207
+ * лежит то, что агент собирался сказать. Если синтез не состоялся, человек
208
+ * должен получить эти слова текстом — деградация в читаемое, а не в мусор
209
+ * и не в пустоту.
210
+ */
211
+ const TTS_TEXT_BLOCK = /\[\[tts:text\]\]([\s\S]*?)\[\[\/tts:text\]\]/gi;
212
+ const TTS_DIRECTIVE = /\[\[\s*\/?\s*(?:tts:[^\]]*|audio_as_voice)\s*\]\]/gi;
213
+ function stripTtsDirectives(text) {
214
+ return text
215
+ .replace(TTS_TEXT_BLOCK, (_match, spoken) => spoken)
216
+ .replace(TTS_DIRECTIVE, "")
217
+ .replace(/[ \t]{2,}/g, " ")
218
+ .trim();
219
+ }
197
220
  /**
198
221
  * Whether an outbound file should become a Telegram voice message.
199
222
  *
@@ -2,7 +2,7 @@
2
2
  "id": "clawgram",
3
3
  "name": "Clawgram",
4
4
  "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
- "version": "2.7.0",
5
+ "version": "2.7.1",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawgram",
3
- "version": "2.7.0",
3
+ "version": "2.7.1",
4
4
  "description": "Clawgram — personal Telegram (MTProto userbot) channel for OpenClaw. Your AI assistant reads and responds as you.",
5
5
  "main": "./dist/index.js",
6
6
  "scripts": {