clawgram 2.7.0 → 2.8.0
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 +33 -3
- package/dist/helpers.js +23 -0
- package/dist/reactions.js +40 -0
- package/openclaw.plugin.json +11 -1
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -233,6 +233,20 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
233
233
|
},
|
|
234
234
|
capabilities: CHANNEL_CAPABILITIES,
|
|
235
235
|
agentPrompt: {
|
|
236
|
+
// Core injects its `## Reactions` section only when this returns a
|
|
237
|
+
// level. Without it the prompt never mentions reactions, and the agent
|
|
238
|
+
// treats the `react` action as one more entry in a 106-property schema:
|
|
239
|
+
// she used it when asked outright in a DM and never once on her own.
|
|
240
|
+
reactionGuidance: ({ cfg, accountId }) => {
|
|
241
|
+
const resolvedAccountId = (0, helpers_1.resolveConfiguredAccountId)(cfg, accountId);
|
|
242
|
+
const account = resolvedAccountId
|
|
243
|
+
? cfg?.channels?.[constants_1.CHANNEL_ID]?.accounts?.[resolvedAccountId]
|
|
244
|
+
: undefined;
|
|
245
|
+
const level = (0, reactions_1.resolveAgentReactionGuidance)(account?.reactionLevel);
|
|
246
|
+
// "clawgram" is our plugin id; the section reads "Reactions are
|
|
247
|
+
// enabled for <label>", and the label is the platform people see.
|
|
248
|
+
return level ? { level, channelLabel: "Telegram" } : undefined;
|
|
249
|
+
},
|
|
236
250
|
messageToolHints: () => [
|
|
237
251
|
"Use clawgram to send Telegram replies from the connected personal account.",
|
|
238
252
|
"When replying in the current Telegram chat, omit `to`/`target` and clawgram will send to the current conversation automatically.",
|
|
@@ -781,7 +795,16 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
781
795
|
// A suppressed silent reply legitimately delivers nothing, so
|
|
782
796
|
// this fallback fires right after it. Without the same check
|
|
783
797
|
// the token would be read back from the transcript and sent.
|
|
784
|
-
|
|
798
|
+
//
|
|
799
|
+
// TTS markup needs the same treatment for the same reason:
|
|
800
|
+
// core strips it on the normal reply path, but this text comes
|
|
801
|
+
// straight out of the transcript. On 2026-08-08 a group got
|
|
802
|
+
// `[[tts:text]]Привет, Вася!…[[/tts:text]]` verbatim. The
|
|
803
|
+
// spoken words are kept — a synthesis that did not happen
|
|
804
|
+
// should degrade to readable text, not to markup.
|
|
805
|
+
const visibleFallbackText = fallbackText
|
|
806
|
+
? (0, helpers_1.stripTtsDirectives)((0, helpers_1.stripSilentReplyToken)(fallbackText))
|
|
807
|
+
: "";
|
|
785
808
|
if (fallbackText && !visibleFallbackText) {
|
|
786
809
|
log?.info?.("clawgram skipping silent transcript fallback", {
|
|
787
810
|
accountId,
|
|
@@ -1634,25 +1657,32 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1634
1657
|
}
|
|
1635
1658
|
actionLog.info("clawgram outbound sendMedia", {
|
|
1636
1659
|
accountId: ctx.accountId,
|
|
1637
|
-
|
|
1660
|
+
rawTo: ctx.to,
|
|
1638
1661
|
replyToId: ctx.replyToId ?? null,
|
|
1639
1662
|
threadId: ctx.threadId ?? null,
|
|
1640
1663
|
filePath: ctx.filePath ?? null,
|
|
1641
1664
|
mediaUrl: ctx.mediaUrl ?? null,
|
|
1642
1665
|
hasText: Boolean(ctx.text),
|
|
1643
1666
|
hasCaption: Boolean(ctx.caption),
|
|
1667
|
+
asVoice: ctx.audioAsVoice === true,
|
|
1644
1668
|
});
|
|
1645
1669
|
const file = ctx.filePath ?? ctx.mediaUrl;
|
|
1646
1670
|
if (!file) {
|
|
1647
1671
|
throw new Error("clawgram: sendMedia requires filePath or mediaUrl");
|
|
1648
1672
|
}
|
|
1649
1673
|
const messageThreadId = parseOptionalThreadId(ctx.threadId);
|
|
1674
|
+
// Same normalization `sendText` does two functions up. Without it the
|
|
1675
|
+
// channel prefix reaches peer resolution and the send throws — which is
|
|
1676
|
+
// exactly how a synthesized group reply died on 2026-08-08, silently
|
|
1677
|
+
// enough that the transcript fallback posted it as raw text instead.
|
|
1678
|
+
const target = (0, helpers_1.normalizeOutboundTarget)(ctx.to);
|
|
1650
1679
|
const sent = await gram.sendMedia({
|
|
1651
|
-
target
|
|
1680
|
+
target,
|
|
1652
1681
|
file,
|
|
1653
1682
|
caption: ctx.caption ?? ctx.text,
|
|
1654
1683
|
replyToMessageId: (0, helpers_1.resolveReplyToMessageIdForTarget)(ctx.to, ctx.replyToId),
|
|
1655
1684
|
messageThreadId,
|
|
1685
|
+
asVoice: ctx.audioAsVoice === true,
|
|
1656
1686
|
});
|
|
1657
1687
|
actionLog.info("clawgram outbound sendMedia completed", {
|
|
1658
1688
|
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
|
*
|
package/dist/reactions.js
CHANGED
|
@@ -11,7 +11,47 @@
|
|
|
11
11
|
* tested without a Telegram connection.
|
|
12
12
|
*/
|
|
13
13
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.resolveAgentReactionGuidance = resolveAgentReactionGuidance;
|
|
14
15
|
exports.parseReactionParams = parseReactionParams;
|
|
16
|
+
/**
|
|
17
|
+
* How chatty the agent may be with reactions, as core understands it.
|
|
18
|
+
*
|
|
19
|
+
* Core injects a `## Reactions` section into the system prompt only when a
|
|
20
|
+
* channel returns a level from `agentPrompt.reactionGuidance`. Return nothing
|
|
21
|
+
* and the prompt never mentions reactions at all — which is what happened
|
|
22
|
+
* here until 2.8.0: the `react` action existed, the agent had it, and no line
|
|
23
|
+
* of the prompt suggested using it.
|
|
24
|
+
*
|
|
25
|
+
* Levels and fallbacks mirror the bundled Telegram channel so the two behave
|
|
26
|
+
* the same for the same config:
|
|
27
|
+
*
|
|
28
|
+
* - `off`, `ack` — no agent reactions (`ack` is the "seen it" emoji core sends
|
|
29
|
+
* by itself, which is a different feature);
|
|
30
|
+
* - `minimal` — react sparingly; the default when nothing is configured;
|
|
31
|
+
* - `extensive` — react whenever it feels natural.
|
|
32
|
+
*
|
|
33
|
+
* An invalid value yields no guidance rather than a guess: a typo turning a
|
|
34
|
+
* work chat chatty is worse than a typo turning it quiet.
|
|
35
|
+
*/
|
|
36
|
+
function resolveAgentReactionGuidance(value) {
|
|
37
|
+
if (value === undefined || value === null) {
|
|
38
|
+
return "minimal";
|
|
39
|
+
}
|
|
40
|
+
if (typeof value !== "string") {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const level = value.trim();
|
|
44
|
+
if (!level) {
|
|
45
|
+
return "minimal";
|
|
46
|
+
}
|
|
47
|
+
if (level === "minimal" || level === "extensive") {
|
|
48
|
+
return level;
|
|
49
|
+
}
|
|
50
|
+
if (level === "off" || level === "ack") {
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
15
55
|
/** Accepts the boolean and the string a JSON-ish caller may send for it. */
|
|
16
56
|
function readBooleanFlag(value) {
|
|
17
57
|
return value === true || value === "true";
|
package/openclaw.plugin.json
CHANGED
|
@@ -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.
|
|
5
|
+
"version": "2.8.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
|
@@ -326,6 +326,16 @@
|
|
|
326
326
|
"type": "string",
|
|
327
327
|
"description": "Where joins observed for this account are journalled. Defaults to a per-account file under the OpenClaw state directory."
|
|
328
328
|
},
|
|
329
|
+
"reactionLevel": {
|
|
330
|
+
"type": "string",
|
|
331
|
+
"enum": [
|
|
332
|
+
"off",
|
|
333
|
+
"ack",
|
|
334
|
+
"minimal",
|
|
335
|
+
"extensive"
|
|
336
|
+
],
|
|
337
|
+
"description": "How freely the agent may leave emoji reactions (2.8.0). Core injects its `## Reactions` prompt section only for `minimal` and `extensive`; `off` and `ack` leave the prompt silent about reactions, and the agent then effectively never reacts on its own. Absent means `minimal`. Mirrors the bundled Telegram channel's reactionLevel."
|
|
338
|
+
},
|
|
329
339
|
"replyParseMode": {
|
|
330
340
|
"type": "string",
|
|
331
341
|
"enum": [
|
package/package.json
CHANGED