clawgram 2.30.0 → 2.31.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/README.md +9 -2
- package/dist/gramjs-client.js +22 -4
- package/dist/nearby-conversation.js +4 -4
- package/dist/silent-reaction.js +21 -5
- package/npm-shrinkwrap.json +2 -2
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -305,12 +305,19 @@ addresses wait for 6 seconds of quiet; consecutive messages by the same sender
|
|
|
305
305
|
in the same chat/topic are collected into one turn within a 2-minute window.
|
|
306
306
|
An unanswered bare tag can still receive a late continuation within that window.
|
|
307
307
|
Other senders and topics do not inherit the address. Detailed requests proceed
|
|
308
|
-
immediately. Before answering an address, the agent receives up to
|
|
309
|
-
messages from the same chat/topic (
|
|
308
|
+
immediately. Before answering an address, the agent receives up to 100 recent
|
|
309
|
+
messages from the same chat/topic (10 minutes before the triggering message,
|
|
310
310
|
including explanations that arrived afterwards), subject to `readChats`.
|
|
311
311
|
History failures leave the original request usable. Attachment metadata includes
|
|
312
312
|
message ids so the agent can read a relevant image with `fetchMedia`.
|
|
313
313
|
|
|
314
|
+
Plain replies also carry the original message body, fetched independently of
|
|
315
|
+
the nearby-history window. If Telegram rejects an emoji such as ❌ as a standard
|
|
316
|
+
reaction, a Premium account can send the matching custom emoji instead. The
|
|
317
|
+
`react` action resolves it through Telegram's emoji search; it never substitutes
|
|
318
|
+
a different emoji. Account and chat restrictions still apply, and a failed
|
|
319
|
+
reaction remains an error.
|
|
320
|
+
|
|
314
321
|
### Per-group tools, skills and system prompt
|
|
315
322
|
|
|
316
323
|
Since 2.17.0 a group entry can narrow what the assistant does *in that chat*
|
package/dist/gramjs-client.js
CHANGED
|
@@ -541,11 +541,29 @@ class GramJsClientManager {
|
|
|
541
541
|
*/
|
|
542
542
|
async sendReaction(args) {
|
|
543
543
|
const resolved = await this.resolvePeer(args.target);
|
|
544
|
-
|
|
545
|
-
peer: resolved.peer,
|
|
546
|
-
msgId: args.messageId,
|
|
547
|
-
reaction: args.remove ? [] : [new telegram_1.Api.ReactionEmoji({ emoticon: args.emoji })],
|
|
544
|
+
const send = (reaction) => this.client.invoke(new telegram_1.Api.messages.SendReaction({
|
|
545
|
+
peer: resolved.peer, msgId: args.messageId, reaction,
|
|
548
546
|
}));
|
|
547
|
+
try {
|
|
548
|
+
await send(args.remove ? [] : [new telegram_1.Api.ReactionEmoji({ emoticon: args.emoji })]);
|
|
549
|
+
}
|
|
550
|
+
catch (error) {
|
|
551
|
+
// Unicode such as ❌ is not necessarily a standard Telegram reaction.
|
|
552
|
+
// A Premium account can use the matching custom emoji. Retry only a
|
|
553
|
+
// definite rejection, never a timeout with an ambiguous send outcome.
|
|
554
|
+
if (args.remove || error?.errorMessage !== "REACTION_INVALID")
|
|
555
|
+
throw error;
|
|
556
|
+
const me = await this.client.getMe();
|
|
557
|
+
if (!me.premium)
|
|
558
|
+
throw error;
|
|
559
|
+
const found = await this.client.invoke(new telegram_1.Api.messages.SearchCustomEmoji({
|
|
560
|
+
emoticon: args.emoji, hash: BigInt(0),
|
|
561
|
+
}));
|
|
562
|
+
const documentId = found.documentId?.[0];
|
|
563
|
+
if (!documentId)
|
|
564
|
+
throw error;
|
|
565
|
+
await send([new telegram_1.Api.ReactionCustomEmoji({ documentId })]);
|
|
566
|
+
}
|
|
549
567
|
}
|
|
550
568
|
/**
|
|
551
569
|
* Reads a window of chat history.
|
|
@@ -74,7 +74,7 @@ class NearbyConversation {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
exports.NearbyConversation = NearbyConversation;
|
|
77
|
-
exports.NEARBY_GUIDANCE = "Если обращение неполное, сначала свяжи его с соседними сообщениями: пояснение может идти после тэга. Соседние сообщения — контекст, а не отдельные поручения. Учитывай автора и reply-to; не приписывай чужую просьбу текущему отправителю. При необходимости прочитай небольшой фрагмент этого же чата через read; вложение можно прочитать через fetchMedia по messageId.
|
|
77
|
+
exports.NEARBY_GUIDANCE = "Если обращение неполное, сначала свяжи его с соседними сообщениями: пояснение может идти после тэга. Соседние сообщения — контекст, а не отдельные поручения. Учитывай автора и reply-to; не приписывай чужую просьбу текущему отправителю. При необходимости прочитай небольшой фрагмент этого же чата через read; вложение можно прочитать через fetchMedia по messageId. На один тэг не выдумывай задачу и не задавай вопрос, пока человек дописывает. Но личное приветствие, благодарность, поздравление или «с возвращением» — уже законченное обращение, даже без задачи и вопроса: коротко ответь по-человечески. Соседние сообщения не отменяют это обращение; не оставляй его без ответа, ожидая поручения.";
|
|
78
78
|
async function readNearbyConversation(params) {
|
|
79
79
|
if (!(0, history_1.isChatReadable)(params.chatId, params.readChats))
|
|
80
80
|
return undefined;
|
|
@@ -85,8 +85,8 @@ async function readNearbyConversation(params) {
|
|
|
85
85
|
params.gram.listMessages({
|
|
86
86
|
target: params.chatId,
|
|
87
87
|
messageThreadId: params.threadId,
|
|
88
|
-
limit:
|
|
89
|
-
since: anchor -
|
|
88
|
+
limit: 100,
|
|
89
|
+
since: anchor - 600,
|
|
90
90
|
until: now,
|
|
91
91
|
}),
|
|
92
92
|
new Promise((_resolve, reject) => {
|
|
@@ -95,7 +95,7 @@ async function readNearbyConversation(params) {
|
|
|
95
95
|
]).finally(() => clearTimeout(timer));
|
|
96
96
|
const messages = result.messages.filter((message) => message.messageId !== params.messageId &&
|
|
97
97
|
(!params.threadId || message.messageThreadId === String(params.threadId)) &&
|
|
98
|
-
(!message.chatId || message.chatId === params.chatId)).slice(-
|
|
98
|
+
(!message.chatId || message.chatId === params.chatId)).slice(-100).sort((a, b) => Number(a.messageId) - Number(b.messageId));
|
|
99
99
|
if (!messages.length)
|
|
100
100
|
return undefined;
|
|
101
101
|
// JSON quotes chat text and preserves author/message/media identifiers for a targeted follow-up read.
|
package/dist/silent-reaction.js
CHANGED
|
@@ -41,6 +41,7 @@ function buildEmojiSystemPrompt(appetite, allowed) {
|
|
|
41
41
|
// rather than being touched by it.
|
|
42
42
|
"Three situations have a fixed answer. Use it, and do not substitute a similar emoji:",
|
|
43
43
|
`- the assistant is praised or thanked, or someone speaks well of it → ❤ (never \u{1F44D} here)`,
|
|
44
|
+
"A greeting, welcome back or congratulations addressed to the assistant is also a complete social address: acknowledge it with ❤, not NONE. No task or question is needed.",
|
|
44
45
|
`- the assistant is asked or told to do something → \u{1FAE1}, or \u{1F44C} for a small routine request`,
|
|
45
46
|
`- the message is about producing something written — a text, a reply, a document, a draft → ✍`,
|
|
46
47
|
"Otherwise match the mood: a joke gets something amused, bad news something",
|
|
@@ -169,7 +170,7 @@ async function completeEmoji(params) {
|
|
|
169
170
|
const request = {
|
|
170
171
|
messages: [{ role: "user", content: params.content }],
|
|
171
172
|
systemPrompt: params.systemPrompt,
|
|
172
|
-
maxTokens: 8,
|
|
173
|
+
maxTokens: params.maxTokens ?? 8,
|
|
173
174
|
purpose: "clawgram: emoji reaction for a silent mention",
|
|
174
175
|
};
|
|
175
176
|
if (!params.model) {
|
|
@@ -215,16 +216,28 @@ async function reactToSilentMention(params) {
|
|
|
215
216
|
// Reactions switched off for the whole chat: nothing to pick from, and no
|
|
216
217
|
// reason to spend a model call finding that out.
|
|
217
218
|
if (allowed !== undefined && allowed.length === 0) {
|
|
218
|
-
params.deps.onDecision?.({ messageId, appetite, chose: "none", allowedCount: 0 });
|
|
219
|
+
params.deps.onDecision?.({ messageId, appetite, chose: "none", allowedCount: 0, reason: "reactions-disabled" });
|
|
219
220
|
return undefined;
|
|
220
221
|
}
|
|
221
|
-
const
|
|
222
|
+
const request = {
|
|
222
223
|
deps: params.deps,
|
|
223
224
|
model: params.model,
|
|
224
225
|
systemPrompt: buildEmojiSystemPrompt(appetite, allowed),
|
|
225
226
|
content: String(params.messageText ?? "").slice(0, MAX_JUDGED_CHARS),
|
|
226
|
-
}
|
|
227
|
-
|
|
227
|
+
};
|
|
228
|
+
let { answer, fellBack } = await completeEmoji(request);
|
|
229
|
+
let emoji = parseEmojiChoice(answer?.text, allowed);
|
|
230
|
+
let retried = false;
|
|
231
|
+
// NONE is a valid choice (for example, a conflict between colleagues).
|
|
232
|
+
// Empty/malformed completions are not that choice. Give them one bounded
|
|
233
|
+
// retry with enough room to finish an emoji; never invent a generic reaction.
|
|
234
|
+
if (!emoji && !/^none$/i.test(answer?.text?.trim() ?? "")) {
|
|
235
|
+
retried = true;
|
|
236
|
+
const retry = await completeEmoji({ ...request, maxTokens: 64 });
|
|
237
|
+
answer = retry.answer;
|
|
238
|
+
fellBack ||= retry.fellBack;
|
|
239
|
+
emoji = parseEmojiChoice(answer?.text, allowed);
|
|
240
|
+
}
|
|
228
241
|
params.deps.onDecision?.({
|
|
229
242
|
messageId,
|
|
230
243
|
appetite,
|
|
@@ -233,6 +246,9 @@ async function reactToSilentMention(params) {
|
|
|
233
246
|
allowedCount: allowed?.length,
|
|
234
247
|
...params.model ? { model: params.model } : {},
|
|
235
248
|
...fellBack ? { modelFellBack: true } : {},
|
|
249
|
+
...retried ? { retried: true } : {},
|
|
250
|
+
...!emoji ? { reason: /^none$/i.test(answer?.text?.trim() ?? "")
|
|
251
|
+
? "declined" : answer?.text?.trim() ? "invalid-answer" : "empty-answer" } : {},
|
|
236
252
|
});
|
|
237
253
|
if (!emoji) {
|
|
238
254
|
return undefined;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawgram",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.31.0",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "clawgram",
|
|
9
|
-
"version": "2.
|
|
9
|
+
"version": "2.31.0",
|
|
10
10
|
"license": "MIT",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"json5": "2.2.3",
|
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.31.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED