clawgram 2.24.0 → 2.25.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 +2 -1
- package/dist/channel.js +11 -5
- package/dist/chunk.js +43 -0
- package/dist/gramjs-client.js +24 -1
- package/dist/inbound-pipeline.js +91 -26
- package/dist/outbound.js +13 -0
- package/dist/update-config.js +0 -8
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -256,10 +256,11 @@ loud where it does occur.
|
|
|
256
256
|
| `apiHash` | string | required | Telegram API hash |
|
|
257
257
|
| `sessionString` | string | `""` | Authenticated StringSession |
|
|
258
258
|
| `allowFrom` | string[] | `["*"]` | Allowed sender IDs/usernames for direct messages only. Three states: absent means everyone, `[]` denies everyone (a warning is logged at account start), a list allows those senders |
|
|
259
|
-
| `operatorIds` | string[] | — | Who receives core's operational telemetry in a DM (tool-failure warnings, fallback notices). They quote shell commands and secret-store paths, so they go only to these ids. Absent
|
|
259
|
+
| `operatorIds` | string[] | — | Who receives core's operational telemetry in a DM (tool-failure warnings, fallback notices). They quote shell commands and secret-store paths, so they go **only** to these ids. Absent, empty or containing `*` means no operator is identified and the notices are dropped everywhere — they stay in the run diagnostics, the job's `lastError` and the gateway log. `allowFrom` is **not** a fallback (2.25.0; it was until then, which made every allowed sender an operator) |
|
|
260
260
|
| `groups` | object | `{}` | Allowed groups map keyed by explicit group id or `*` |
|
|
261
261
|
| `proxy` | object | unset | Optional SOCKS4/SOCKS5 proxy for this account — see [Proxy (SOCKS4/SOCKS5)](#proxy-socks4socks5) |
|
|
262
262
|
| `manageChats` | string[] | unset | Chats the assistant may **manage** — see [Chat management](#chat-management). Absent or empty = management off; `["*"]` = every chat |
|
|
263
|
+
| `sendChats` | string[] | unset | Chats the assistant may **send to** — `send`, `upload-file`, `react` and core's own delivery path (`--deliver`, sub-agent announcements). Absent = every chat; `[]` = none. Phone-number targets are refused regardless (2.18.0; core delivery covered since 2.25.0) |
|
|
263
264
|
| `replyParseMode` | `"html"` \| `"markdown"` \| `"none"` | unset | Outbound format for replies, core-delivered text, captions and `send` calls that omit `parseMode` — see [Message formatting](#message-formatting) |
|
|
264
265
|
| `twoFaPassword` | string \| SecretRef | unset | The account's Telegram 2FA password; read only by `transferOwnership` |
|
|
265
266
|
| `reactionModel` | string | unset | Model ref or alias for the emoji pick on a silent mention. Unset = the agent's own model. Needs `plugins.entries.clawgram.llm.allowModelOverride: true` in the gateway config; without it the override is refused and the pick quietly falls back to the default model |
|
package/dist/channel.js
CHANGED
|
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.createChannelPlugin = exports.canonicalAction = exports.CORE_ACTION_SYNONYMS = void 0;
|
|
7
|
+
exports.resolveAccountOperatorIds = resolveAccountOperatorIds;
|
|
7
8
|
const core_1 = require("openclaw/plugin-sdk/core");
|
|
8
9
|
const node_os_1 = __importDefault(require("node:os"));
|
|
9
10
|
const node_path_1 = __importDefault(require("node:path"));
|
|
@@ -127,8 +128,10 @@ function resolveAccountSendChats(cfg, accountId) {
|
|
|
127
128
|
}
|
|
128
129
|
/** One refusal for every outbound action, so the three read the same. */
|
|
129
130
|
function refuseOutboundOutsideScope(action, accountId, target) {
|
|
130
|
-
const
|
|
131
|
-
|
|
131
|
+
const phone = (0, send_scope_1.isPhoneNumberTarget)(target);
|
|
132
|
+
const reason = phone ? "phone-number target" : "chat outside send scope";
|
|
133
|
+
// Телефонный номер — персональные данные: в журнал идёт вид цели, не значение (B5-09).
|
|
134
|
+
actionLog.warn(`clawgram ${action} refused: ${reason}`, { accountId, ...(phone ? { targetKind: "phone" } : { target }) });
|
|
132
135
|
throw new Error(`clawgram: not-allowed-chat ${target}`);
|
|
133
136
|
}
|
|
134
137
|
/**
|
|
@@ -149,8 +152,11 @@ function refuseOutboundOutsideScope(action, accountId, target) {
|
|
|
149
152
|
*/
|
|
150
153
|
function resolveAccountOperatorIds(cfg, accountId) {
|
|
151
154
|
const account = cfg?.channels?.["clawgram"]?.accounts?.[accountId];
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
// Только явный список. Умолчание «operatorIds = allowFrom» делало
|
|
156
|
+
// оператором каждого допущенного собеседника — и телеметрию с путями
|
|
157
|
+
// secret-store получал любой из них (D2-03, A5-11). Не назван — не
|
|
158
|
+
// назван: уведомления подавляются везде.
|
|
159
|
+
const raw = account?.operatorIds;
|
|
154
160
|
if (raw === undefined || raw === null)
|
|
155
161
|
return [];
|
|
156
162
|
const entries = Array.isArray(raw) ? raw : [raw];
|
|
@@ -253,7 +259,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
253
259
|
messageToolHints: () => [
|
|
254
260
|
"Use clawgram to send Telegram replies from the connected personal account.",
|
|
255
261
|
"When replying in the current Telegram chat, omit `to`/`target` and clawgram will send to the current conversation automatically.",
|
|
256
|
-
"Explicit targets may be @username, numeric Telegram user id,
|
|
262
|
+
"Explicit targets may be @username, numeric Telegram user id, group chat ids, or clawgram:<target>.",
|
|
257
263
|
"For Telegram forum topics, send to the group chat id and pass the topic id separately as `threadId`.",
|
|
258
264
|
"Use the `react` action to acknowledge a message with an emoji instead of sending a reply; pass an empty `emoji` (or `remove: true`) to take the reaction back.",
|
|
259
265
|
"Use the `channel-info` action to learn what a chat is — title, type, member count, description, pinned message — instead of guessing from its id. Name the chat with `chatId` and do not pass `target`: core refuses it for this action, and the descriptive spelling `chatInfo` is not callable from this tool at all.",
|
package/dist/chunk.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TELEGRAM_CAPTION_LIMIT = exports.TELEGRAM_TEXT_LIMIT = void 0;
|
|
4
|
+
exports.chunkTelegramText = chunkTelegramText;
|
|
5
|
+
/**
|
|
6
|
+
* Telegram limits: 4096 characters per message, 1024 per media caption.
|
|
7
|
+
*
|
|
8
|
+
* Core chunks only the replies it dispatches itself; a `send` issued through
|
|
9
|
+
* the message tool — the way the guard scripts deliver reports — reaches
|
|
10
|
+
* GramJS whole, and GramJS does not split either, so a long report failed
|
|
11
|
+
* with MESSAGE_TOO_LONG and the agent saw "✉️ Message failed" while the
|
|
12
|
+
* human saw nothing (audit B5-03).
|
|
13
|
+
*
|
|
14
|
+
* Splitting prefers paragraph breaks, then line breaks, then a hard cut at
|
|
15
|
+
* the limit. It runs on the text as the agent wrote it — before HTML
|
|
16
|
+
* rendering — so a chunk boundary can only fall between the agent's own
|
|
17
|
+
* lines, never inside an entity GramJS produced.
|
|
18
|
+
*/
|
|
19
|
+
exports.TELEGRAM_TEXT_LIMIT = 4096;
|
|
20
|
+
exports.TELEGRAM_CAPTION_LIMIT = 1024;
|
|
21
|
+
function chunkTelegramText(text, limit = exports.TELEGRAM_TEXT_LIMIT) {
|
|
22
|
+
const chars = Array.from(text);
|
|
23
|
+
if (chars.length <= limit)
|
|
24
|
+
return [text];
|
|
25
|
+
const chunks = [];
|
|
26
|
+
let rest = text;
|
|
27
|
+
while (Array.from(rest).length > limit) {
|
|
28
|
+
const window = Array.from(rest).slice(0, limit).join("");
|
|
29
|
+
let cut = window.lastIndexOf("\n\n");
|
|
30
|
+
if (cut < limit / 2)
|
|
31
|
+
cut = window.lastIndexOf("\n");
|
|
32
|
+
if (cut < limit / 2)
|
|
33
|
+
cut = window.lastIndexOf(" ");
|
|
34
|
+
if (cut < limit / 2)
|
|
35
|
+
cut = window.length;
|
|
36
|
+
const piece = rest.slice(0, cut).replace(/\s+$/u, "");
|
|
37
|
+
chunks.push(piece);
|
|
38
|
+
rest = rest.slice(cut).replace(/^\s+/u, "");
|
|
39
|
+
}
|
|
40
|
+
if (rest)
|
|
41
|
+
chunks.push(rest);
|
|
42
|
+
return chunks;
|
|
43
|
+
}
|
package/dist/gramjs-client.js
CHANGED
|
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.GramJsClientManager = void 0;
|
|
4
4
|
exports.buildParticipantsQuery = buildParticipantsQuery;
|
|
5
5
|
exports.buildVoiceNoteParams = buildVoiceNoteParams;
|
|
6
|
+
const chunk_1 = require("./chunk");
|
|
6
7
|
const telegram_1 = require("telegram");
|
|
7
8
|
const sessions_1 = require("telegram/sessions");
|
|
8
9
|
// Deep import, but the documented one: GramJS ships its SRP helper here and
|
|
@@ -313,7 +314,8 @@ class GramJsClientManager {
|
|
|
313
314
|
// но теперь виден в логе: если он в логе частый, значит кэш не спасает и
|
|
314
315
|
// адресация идёт не тем ключом (A6-14).
|
|
315
316
|
peerLog.info("clawgram peer resolve falling back to dialog scan", {
|
|
316
|
-
|
|
317
|
+
// Цель может быть телефоном или @handle человека — в журнал идёт только её форма (B5-09).
|
|
318
|
+
targetShape: String(raw).startsWith("+") ? "phone" : String(raw).startsWith("@") ? "handle" : /^-?\d+/.test(String(raw)) ? "id" : "other",
|
|
317
319
|
kind: kind ?? null,
|
|
318
320
|
});
|
|
319
321
|
const dialogs = await this.client.getDialogs({ limit: 200 }).catch(() => []);
|
|
@@ -418,6 +420,17 @@ class GramJsClientManager {
|
|
|
418
420
|
const resolved = await this.resolvePeer(args.target, { kind: args.targetKind });
|
|
419
421
|
const messageThreadId = args.messageThreadId ?? resolved.messageThreadId;
|
|
420
422
|
const replyParams = buildForumReplyParams(messageThreadId, args.replyToMessageId);
|
|
423
|
+
// Длиннее лимита Telegram — несколько сообщений подряд, а не
|
|
424
|
+
// MESSAGE_TOO_LONG (B5-03). Нарезка — по тексту агента, до рендера.
|
|
425
|
+
const chunks = (0, chunk_1.chunkTelegramText)(args.text, chunk_1.TELEGRAM_TEXT_LIMIT);
|
|
426
|
+
if (chunks.length > 1) {
|
|
427
|
+
let last;
|
|
428
|
+
for (const [index, chunk] of chunks.entries()) {
|
|
429
|
+
last = await this.sendText({ ...args, text: chunk,
|
|
430
|
+
...(index > 0 ? { replyToMessageId: undefined } : {}) });
|
|
431
|
+
}
|
|
432
|
+
return last;
|
|
433
|
+
}
|
|
421
434
|
return this.client.sendMessage(resolved.peer, {
|
|
422
435
|
// In html mode the text is rendered first: the agent writes markdown,
|
|
423
436
|
// Telegram HTML, or both, and GramJS's HTML parser alone would ship
|
|
@@ -757,6 +770,16 @@ class GramJsClientManager {
|
|
|
757
770
|
const resolved = await this.resolvePeer(args.target);
|
|
758
771
|
const messageThreadId = args.messageThreadId ?? resolved.messageThreadId;
|
|
759
772
|
const replyParams = buildForumReplyParams(messageThreadId, args.replyToMessageId);
|
|
773
|
+
// Подпись длиннее 1024 — файл с первой частью, остальное текстом следом
|
|
774
|
+
// (B5-03); раньше вся подпись уезжала целиком и падала на лимите.
|
|
775
|
+
const captionChunks = args.caption ? (0, chunk_1.chunkTelegramText)(args.caption, chunk_1.TELEGRAM_CAPTION_LIMIT) : [];
|
|
776
|
+
if (captionChunks.length > 1) {
|
|
777
|
+
const sent = await this.sendMedia({ ...args, caption: captionChunks[0] });
|
|
778
|
+
for (const chunk of captionChunks.slice(1)) {
|
|
779
|
+
await this.sendText({ target: args.target, text: chunk, parseMode: args.parseMode, messageThreadId });
|
|
780
|
+
}
|
|
781
|
+
return sent;
|
|
782
|
+
}
|
|
760
783
|
return this.client.sendFile(resolved.peer, {
|
|
761
784
|
file: args.file,
|
|
762
785
|
// Captions are agent prose too — the outbound path sends `caption ??
|
package/dist/inbound-pipeline.js
CHANGED
|
@@ -101,30 +101,6 @@ async function handleInboundEvent(event, ctx) {
|
|
|
101
101
|
}
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
|
-
const directReplyTarget = normalized.chatType === "direct"
|
|
105
|
-
? undefined
|
|
106
|
-
: await (0, helpers_1.resolveReplyTarget)(rawMessage);
|
|
107
|
-
const senderProfile = normalized.chatType === "direct"
|
|
108
|
-
? await (0, helpers_1.resolveSenderProfileWithTimeout)(rawMessage, {
|
|
109
|
-
senderId: normalized.senderId,
|
|
110
|
-
client,
|
|
111
|
-
}, 1500)
|
|
112
|
-
: await (0, helpers_1.resolveSenderProfile)(rawMessage, {
|
|
113
|
-
senderId: normalized.senderId,
|
|
114
|
-
client,
|
|
115
|
-
});
|
|
116
|
-
const replyTarget = normalized.chatType === "direct"
|
|
117
|
-
? normalized.chatId
|
|
118
|
-
: await (0, helpers_1.resolveChatTarget)(rawMessage);
|
|
119
|
-
if (replyTarget) {
|
|
120
|
-
normalized.replyTarget = replyTarget;
|
|
121
|
-
}
|
|
122
|
-
if (!normalized.senderUsername && senderProfile.username) {
|
|
123
|
-
normalized.senderUsername = senderProfile.username;
|
|
124
|
-
}
|
|
125
|
-
if (!normalized.senderDisplay && senderProfile.display) {
|
|
126
|
-
normalized.senderDisplay = senderProfile.display;
|
|
127
|
-
}
|
|
128
104
|
if (normalized.isOutgoing) {
|
|
129
105
|
if (normalized.chatType === "direct") {
|
|
130
106
|
log?.info?.("clawgram skipping outgoing direct event", {
|
|
@@ -145,6 +121,61 @@ async function handleInboundEvent(event, ctx) {
|
|
|
145
121
|
});
|
|
146
122
|
return;
|
|
147
123
|
}
|
|
124
|
+
// Ворота — ДО сети. До 2.25.0 на каждое сообщение из любой группы,
|
|
125
|
+
// где сидит аккаунт, — включая группы вне `groups` — плагин делал
|
|
126
|
+
// до семи запросов к Telegram (профиль отправителя, адрес ответа,
|
|
127
|
+
// цель чата) и только потом отбрасывал сообщение как чужое.
|
|
128
|
+
// Посторонний, флудящий в такой группе, тратил соединение и
|
|
129
|
+
// rate-limit аккаунта (B5-04, остаток A5-06). Группа вне конфига
|
|
130
|
+
// и выключенная группа заканчиваются здесь, без единого вызова.
|
|
131
|
+
const earlyScopes = (0, helpers_1.resolveAccountScopes)(cfg, accountId);
|
|
132
|
+
const earlyGroupConfig = normalized.chatType === "group"
|
|
133
|
+
? (0, helpers_1.resolveGroupConfig)(earlyScopes.groups, normalized.chatId)
|
|
134
|
+
: undefined;
|
|
135
|
+
if (normalized.chatType === "group") {
|
|
136
|
+
if (!earlyGroupConfig) {
|
|
137
|
+
log?.info?.("clawgram skipping group not present in groups config", {
|
|
138
|
+
accountId,
|
|
139
|
+
chatId: normalized.chatId,
|
|
140
|
+
messageId: normalized.messageId,
|
|
141
|
+
});
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
if (earlyGroupConfig.enabled === false) {
|
|
145
|
+
log?.info?.("clawgram skipping disabled group", {
|
|
146
|
+
accountId,
|
|
147
|
+
chatId: normalized.chatId,
|
|
148
|
+
messageId: normalized.messageId,
|
|
149
|
+
});
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// Профиль отправителя нужен воротам только когда allowFrom
|
|
154
|
+
// называет кого-то по @handle, а сообщение handle не принесло;
|
|
155
|
+
// числовые id сверяются без сети.
|
|
156
|
+
const gateAllowFrom = normalized.chatType === "group"
|
|
157
|
+
? earlyGroupConfig?.allowFrom
|
|
158
|
+
: earlyScopes.allowFrom;
|
|
159
|
+
const allowFromNeedsHandle = Array.isArray(gateAllowFrom)
|
|
160
|
+
&& gateAllowFrom.some((entry) => String(entry).trim().startsWith("@"));
|
|
161
|
+
const needsProfile = !normalized.senderUsername && allowFromNeedsHandle;
|
|
162
|
+
const senderProfile = needsProfile
|
|
163
|
+
? (normalized.chatType === "direct"
|
|
164
|
+
? await (0, helpers_1.resolveSenderProfileWithTimeout)(rawMessage, {
|
|
165
|
+
senderId: normalized.senderId,
|
|
166
|
+
client,
|
|
167
|
+
}, 1500)
|
|
168
|
+
: await (0, helpers_1.resolveSenderProfile)(rawMessage, {
|
|
169
|
+
senderId: normalized.senderId,
|
|
170
|
+
client,
|
|
171
|
+
}))
|
|
172
|
+
: {};
|
|
173
|
+
if (!normalized.senderUsername && senderProfile.username) {
|
|
174
|
+
normalized.senderUsername = senderProfile.username;
|
|
175
|
+
}
|
|
176
|
+
if (!normalized.senderDisplay && senderProfile.display) {
|
|
177
|
+
normalized.senderDisplay = senderProfile.display;
|
|
178
|
+
}
|
|
148
179
|
let text = normalized.text?.trim();
|
|
149
180
|
// Whether this sender may reach the agent at all — decided before
|
|
150
181
|
// the attachment is fetched.
|
|
@@ -180,6 +211,16 @@ async function handleInboundEvent(event, ctx) {
|
|
|
180
211
|
// voice note and a screenshot alike, the attachment *is* the
|
|
181
212
|
// message. A caption is kept and the reading appended, because
|
|
182
213
|
// "look at this" plus the picture is one thought, not two.
|
|
214
|
+
// Адрес ответа и цель чата — сеть, и нужны только тому, кому
|
|
215
|
+
// отвечают: считаются после ворот (B5-04).
|
|
216
|
+
const directReplyTarget = normalized.chatType === "direct" ? undefined
|
|
217
|
+
: senderMayReachAgent ? await (0, helpers_1.resolveReplyTarget)(rawMessage) : undefined;
|
|
218
|
+
const replyTarget = normalized.chatType === "direct"
|
|
219
|
+
? normalized.chatId
|
|
220
|
+
: senderMayReachAgent ? await (0, helpers_1.resolveChatTarget)(rawMessage) : undefined;
|
|
221
|
+
if (replyTarget) {
|
|
222
|
+
normalized.replyTarget = replyTarget;
|
|
223
|
+
}
|
|
183
224
|
const attachment = senderMayReachAgent ? await (0, attachments_1.readInboundAttachment)({
|
|
184
225
|
gram,
|
|
185
226
|
event,
|
|
@@ -315,7 +356,10 @@ async function handleInboundEvent(event, ctx) {
|
|
|
315
356
|
messageId: normalized.messageId,
|
|
316
357
|
senderId,
|
|
317
358
|
username: normalized.senderUsername,
|
|
318
|
-
|
|
359
|
+
// Сам список — id владельца и допущенных — в журнал не идёт:
|
|
360
|
+
// посторонний управлял бы числом его копий в journald (B5-09).
|
|
361
|
+
allowFromCount: Array.isArray(groupConfig.allowFrom) ? groupConfig.allowFrom.length : 0,
|
|
362
|
+
allowFromHasWildcard: Array.isArray(groupConfig.allowFrom) && groupConfig.allowFrom.some((e) => String(e).trim() === "*"),
|
|
319
363
|
});
|
|
320
364
|
return;
|
|
321
365
|
}
|
|
@@ -706,7 +750,8 @@ async function handleInboundEvent(event, ctx) {
|
|
|
706
750
|
accountId,
|
|
707
751
|
senderId,
|
|
708
752
|
senderUsername: normalized.senderUsername,
|
|
709
|
-
|
|
753
|
+
allowFromCount: Array.isArray(directAllowFrom) ? directAllowFrom.length : 0,
|
|
754
|
+
allowFromHasWildcard: Array.isArray(directAllowFrom) && directAllowFrom.some((e) => String(e).trim() === "*"),
|
|
710
755
|
});
|
|
711
756
|
return;
|
|
712
757
|
}
|
|
@@ -820,6 +865,26 @@ async function handleInboundEvent(event, ctx) {
|
|
|
820
865
|
});
|
|
821
866
|
return;
|
|
822
867
|
}
|
|
868
|
+
// Тот же фильтр, что у группового ответа и у outbound.sendText:
|
|
869
|
+
// личный ответ идёт третьим путём, и закрытие A5-11 его не
|
|
870
|
+
// покрывало — «⚠️ 🛠️ Bash failed: cat /opt/openclaw-secrets/…»
|
|
871
|
+
// уходил любому из allowFrom, чей ход уронил инструмент (B5-01).
|
|
872
|
+
const directNotice = (0, system_notice_1.shouldSuppressGroupSystemNotice)({
|
|
873
|
+
targetKind: "user",
|
|
874
|
+
text: visibleText,
|
|
875
|
+
to: normalized.chatId,
|
|
876
|
+
operatorIds: (0, system_notice_1.operatorIdsFor)(accountId),
|
|
877
|
+
});
|
|
878
|
+
if (directNotice) {
|
|
879
|
+
log?.warn?.("clawgram suppressing system notice in direct reply", {
|
|
880
|
+
accountId,
|
|
881
|
+
chatId: normalized.chatId,
|
|
882
|
+
messageId: normalized.messageId,
|
|
883
|
+
noticeKind: directNotice,
|
|
884
|
+
textLength: visibleText.length,
|
|
885
|
+
});
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
823
888
|
await sendTextToConversation({
|
|
824
889
|
text: visibleText,
|
|
825
890
|
replyToMessageId: payload.replyToId ? Number(payload.replyToId) : undefined,
|
package/dist/outbound.js
CHANGED
|
@@ -89,6 +89,19 @@ function createOutbound(runtimes) {
|
|
|
89
89
|
});
|
|
90
90
|
return { skipped: "silent" };
|
|
91
91
|
}
|
|
92
|
+
// Область отправки — и здесь. Путь доставки ядра (`--deliver`,
|
|
93
|
+
// анонсы субагентов) зовёт sendText напрямую, минуя resolveTarget и
|
|
94
|
+
// handleAction, где барьер уже стоял: третий из трёх исходящих путей
|
|
95
|
+
// был открыт для любого адресата и телефонного номера (D2-01, A5-12).
|
|
96
|
+
const scopedTarget = (0, helpers_1.normalizeOutboundTarget)(ctx.to);
|
|
97
|
+
if (!(0, send_scope_1.isChatSendable)(scopedTarget, (0, send_scope_1.sendScopeFor)(ctx.accountId))) {
|
|
98
|
+
actionLog.warn("clawgram outbound sendText refused", {
|
|
99
|
+
accountId: ctx.accountId,
|
|
100
|
+
target: scopedTarget,
|
|
101
|
+
reason: (0, send_scope_1.isPhoneNumberTarget)(scopedTarget) ? "phone-number target" : "chat outside send scope",
|
|
102
|
+
});
|
|
103
|
+
return { skipped: "not-allowed" };
|
|
104
|
+
}
|
|
92
105
|
// Core's operational chatter (tool-error warnings, fallback notices)
|
|
93
106
|
// stays out of group chats: it is telemetry for the operator, not a
|
|
94
107
|
// reply to the room, and it has already been seen carrying shell
|
package/dist/update-config.js
CHANGED
|
@@ -36,14 +36,6 @@ function buildAccountPayload(auth) {
|
|
|
36
36
|
sessionString: auth.sessionString,
|
|
37
37
|
};
|
|
38
38
|
}
|
|
39
|
-
/** Первый конфиг закрыт: см. одноимённую функцию в cli-core.ts. */
|
|
40
|
-
function buildAccountConfigFragment(auth) {
|
|
41
|
-
return {
|
|
42
|
-
...buildAccountPayload(auth),
|
|
43
|
-
allowFrom: auth.selfId ? [auth.selfId] : [],
|
|
44
|
-
readChats: [],
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
39
|
/**
|
|
48
40
|
* Credentials already moved into the secret store must survive re-auth.
|
|
49
41
|
*
|
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.25.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"additionalProperties": false,
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clawgram",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.25.0",
|
|
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": {
|
|
7
|
+
"verify:proxy": "node scripts/verify/gramjs-wiring.cjs && node scripts/verify/auth-preserves-proxy.cjs",
|
|
7
8
|
"build": "tsc -p tsconfig.json",
|
|
8
9
|
"build:test": "tsc -p tsconfig.test.json",
|
|
9
10
|
"test": "npm run build:test && node test/ensure-compiled.mjs && node --test \"dist-test/test/*.test.js\"",
|