clawgram 2.23.0 → 2.24.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 +8 -873
- package/dist/helpers.js +26 -0
- package/dist/inbound-pipeline.js +872 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
package/dist/channel.js
CHANGED
|
@@ -54,22 +54,14 @@ const fetch_media_1 = require("./fetch-media");
|
|
|
54
54
|
const channel_runtime_1 = require("openclaw/plugin-sdk/channel-runtime");
|
|
55
55
|
const param_readers_1 = require("openclaw/plugin-sdk/param-readers");
|
|
56
56
|
const tool_send_1 = require("openclaw/plugin-sdk/tool-send");
|
|
57
|
-
const direct_dm_1 = require("openclaw/plugin-sdk/direct-dm");
|
|
58
|
-
const channel_inbound_1 = require("openclaw/plugin-sdk/channel-inbound");
|
|
59
|
-
const channel_reply_pipeline_1 = require("openclaw/plugin-sdk/channel-reply-pipeline");
|
|
60
|
-
const inbound_envelope_1 = require("openclaw/plugin-sdk/inbound-envelope");
|
|
61
|
-
const inbound_reply_dispatch_1 = require("openclaw/plugin-sdk/inbound-reply-dispatch");
|
|
62
57
|
const channel_pairing_1 = require("openclaw/plugin-sdk/channel-pairing");
|
|
63
58
|
const events_1 = require("telegram/events");
|
|
64
|
-
const constants_1 = require("./constants");
|
|
65
59
|
const gramjs_client_1 = require("./gramjs-client");
|
|
66
|
-
const normalize_1 = require("./normalize");
|
|
67
60
|
const history_1 = require("./history");
|
|
68
61
|
const send_scope_1 = require("./send-scope");
|
|
69
62
|
const joins_1 = require("./joins");
|
|
70
63
|
const reactions_1 = require("./reactions");
|
|
71
64
|
const manage_1 = require("./manage");
|
|
72
|
-
const silent_reaction_1 = require("./silent-reaction");
|
|
73
65
|
const system_notice_1 = require("./system-notice");
|
|
74
66
|
const state_dir_1 = require("./state-dir");
|
|
75
67
|
const chat_info_1 = require("./chat-info");
|
|
@@ -82,68 +74,8 @@ const group_reply_address_1 = require("./group-reply-address");
|
|
|
82
74
|
const group_visible_reply_guard_1 = require("./group-visible-reply-guard");
|
|
83
75
|
const helpers_1 = require("./helpers");
|
|
84
76
|
const proxy_config_1 = require("./proxy-config");
|
|
85
|
-
const
|
|
77
|
+
const constants_1 = require("./constants");
|
|
86
78
|
const actionLog = (0, core_1.createSubsystemLogger)("channels/clawgram");
|
|
87
|
-
/** Reads the configured reaction level for an account, tolerating a missing config. */
|
|
88
|
-
function readAccountReactionLevel(cfg, accountId) {
|
|
89
|
-
const resolvedAccountId = (0, helpers_1.resolveConfiguredAccountId)(cfg, accountId);
|
|
90
|
-
if (!resolvedAccountId) {
|
|
91
|
-
return undefined;
|
|
92
|
-
}
|
|
93
|
-
return cfg?.channels?.[constants_2.CHANNEL_ID]?.accounts?.[resolvedAccountId]?.reactionLevel;
|
|
94
|
-
}
|
|
95
|
-
/**
|
|
96
|
-
* Model ref for the emoji pick, when the account names one.
|
|
97
|
-
*
|
|
98
|
-
* Picking one emoji out of a fixed list of 68 is the cheapest judgement this
|
|
99
|
-
* channel makes and the only model call it makes on its own; running it on the
|
|
100
|
-
* agent's own head spends the expensive quota on a decision a small model
|
|
101
|
-
* makes just as well.
|
|
102
|
-
*/
|
|
103
|
-
function readAccountReactionModel(cfg, accountId) {
|
|
104
|
-
const resolvedAccountId = (0, helpers_1.resolveConfiguredAccountId)(cfg, accountId);
|
|
105
|
-
if (!resolvedAccountId) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
const raw = cfg?.channels?.[constants_2.CHANNEL_ID]?.accounts?.[resolvedAccountId]?.reactionModel;
|
|
109
|
-
return typeof raw === "string" && raw.trim() ? raw.trim() : undefined;
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Wires `reactToSilentMention` to this account's runtime, config and log.
|
|
113
|
-
*
|
|
114
|
-
* The decision itself lives in `silent-reaction.ts`, testable without a
|
|
115
|
-
* Telegram connection; everything here is lookup. Missing pieces — no
|
|
116
|
-
* connected client, no model access — resolve to no reaction rather than to
|
|
117
|
-
* an error, because by this point the agent has already declined to reply.
|
|
118
|
-
*/
|
|
119
|
-
async function reactToSilentMentionForAccount(params) {
|
|
120
|
-
const gram = params.gram;
|
|
121
|
-
const llm = params.pluginRuntime?.llm;
|
|
122
|
-
if (!gram || typeof llm?.complete !== "function") {
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
await (0, silent_reaction_1.reactToSilentMention)({
|
|
126
|
-
appetite: (0, reactions_1.resolveAgentReactionGuidance)(readAccountReactionLevel(params.cfg, params.accountId)),
|
|
127
|
-
model: readAccountReactionModel(params.cfg, params.accountId),
|
|
128
|
-
wasMentioned: params.wasMentioned,
|
|
129
|
-
chatId: params.chatId,
|
|
130
|
-
messageId: params.messageId,
|
|
131
|
-
messageText: params.messageText,
|
|
132
|
-
deps: {
|
|
133
|
-
// Bound rather than destructured: the SDK may implement this as a
|
|
134
|
-
// method that needs its receiver.
|
|
135
|
-
complete: (args) => llm.complete(args),
|
|
136
|
-
sendReaction: (args) => gram.sendReaction(args),
|
|
137
|
-
allowedReactions: gram.getAllowedReactions
|
|
138
|
-
? () => gram.getAllowedReactions(params.chatId)
|
|
139
|
-
: undefined,
|
|
140
|
-
onDecision: (info) => actionLog.info("clawgram silent-mention reaction", {
|
|
141
|
-
accountId: params.accountId,
|
|
142
|
-
...info,
|
|
143
|
-
}),
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
79
|
/**
|
|
148
80
|
* Read scope as configured for the account. Left `undefined` when the key is
|
|
149
81
|
* absent so `isChatReadable` can tell "not configured" from "configured empty" —
|
|
@@ -242,6 +174,7 @@ const actions_1 = require("./actions");
|
|
|
242
174
|
Object.defineProperty(exports, "CORE_ACTION_SYNONYMS", { enumerable: true, get: function () { return actions_1.CORE_ACTION_SYNONYMS; } });
|
|
243
175
|
Object.defineProperty(exports, "canonicalAction", { enumerable: true, get: function () { return actions_1.canonicalAction; } });
|
|
244
176
|
const outbound_1 = require("./outbound");
|
|
177
|
+
const inbound_pipeline_1 = require("./inbound-pipeline");
|
|
245
178
|
/**
|
|
246
179
|
* Turns an inbound attachment into text the agent can read.
|
|
247
180
|
*
|
|
@@ -454,808 +387,10 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
454
387
|
});
|
|
455
388
|
const client = gram.getClient();
|
|
456
389
|
const eventBuilder = new events_1.NewMessage({});
|
|
457
|
-
const eventHandler = async (event) => {
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
const rawPeerChatId = rawMessage?.peerId?.chatId;
|
|
462
|
-
const rawPeerChannelId = rawMessage?.peerId?.channelId;
|
|
463
|
-
const directLike = rawPeerUserId !== undefined ||
|
|
464
|
-
(typeof rawMessage?.chatId === "number" && rawMessage.chatId > 0);
|
|
465
|
-
if (directLike) {
|
|
466
|
-
log?.info?.("clawgram raw direct-like event", {
|
|
467
|
-
accountId,
|
|
468
|
-
messageId: String(rawMessage?.id ?? ""),
|
|
469
|
-
chatId: String(rawMessage?.chatId ?? ""),
|
|
470
|
-
peerUserId: String(rawPeerUserId ?? ""),
|
|
471
|
-
peerChatId: String(rawPeerChatId ?? ""),
|
|
472
|
-
peerChannelId: String(rawPeerChannelId ?? ""),
|
|
473
|
-
senderId: String(rawMessage?.senderId ?? rawMessage?.fromId?.userId ?? ""),
|
|
474
|
-
out: rawMessage?.out === true,
|
|
475
|
-
textLength: typeof rawMessage?.message === "string" ? rawMessage.message.length : typeof rawMessage?.text === "string" ? rawMessage.text.length : 0,
|
|
476
|
-
});
|
|
477
|
-
}
|
|
478
|
-
const normalized = (0, normalize_1.normalizeTelegramEvent)(event, accountId);
|
|
479
|
-
if (!normalized) {
|
|
480
|
-
if (directLike) {
|
|
481
|
-
log?.info?.("clawgram normalize returned null", {
|
|
482
|
-
accountId,
|
|
483
|
-
messageId: String(rawMessage?.id ?? ""),
|
|
484
|
-
chatId: String(rawMessage?.chatId ?? ""),
|
|
485
|
-
peerUserId: String(rawPeerUserId ?? ""),
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
const directReplyTarget = normalized.chatType === "direct"
|
|
491
|
-
? undefined
|
|
492
|
-
: await (0, helpers_1.resolveReplyTarget)(rawMessage);
|
|
493
|
-
const senderProfile = normalized.chatType === "direct"
|
|
494
|
-
? await (0, helpers_1.resolveSenderProfileWithTimeout)(rawMessage, {
|
|
495
|
-
senderId: normalized.senderId,
|
|
496
|
-
client,
|
|
497
|
-
}, 1500)
|
|
498
|
-
: await (0, helpers_1.resolveSenderProfile)(rawMessage, {
|
|
499
|
-
senderId: normalized.senderId,
|
|
500
|
-
client,
|
|
501
|
-
});
|
|
502
|
-
const replyTarget = normalized.chatType === "direct"
|
|
503
|
-
? normalized.chatId
|
|
504
|
-
: await (0, helpers_1.resolveChatTarget)(rawMessage);
|
|
505
|
-
if (replyTarget) {
|
|
506
|
-
normalized.replyTarget = replyTarget;
|
|
507
|
-
}
|
|
508
|
-
if (!normalized.senderUsername && senderProfile.username) {
|
|
509
|
-
normalized.senderUsername = senderProfile.username;
|
|
510
|
-
}
|
|
511
|
-
if (!normalized.senderDisplay && senderProfile.display) {
|
|
512
|
-
normalized.senderDisplay = senderProfile.display;
|
|
513
|
-
}
|
|
514
|
-
if (normalized.isOutgoing) {
|
|
515
|
-
if (normalized.chatType === "direct") {
|
|
516
|
-
log?.info?.("clawgram skipping outgoing direct event", {
|
|
517
|
-
accountId,
|
|
518
|
-
chatId: normalized.chatId,
|
|
519
|
-
messageId: normalized.messageId,
|
|
520
|
-
senderId: normalized.senderId,
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
if (normalized.chatType === "channel") {
|
|
526
|
-
log?.info?.("clawgram skipping channel inbound", {
|
|
527
|
-
accountId,
|
|
528
|
-
chatId: normalized.chatId,
|
|
529
|
-
chatType: normalized.chatType,
|
|
530
|
-
messageId: normalized.messageId,
|
|
531
|
-
});
|
|
532
|
-
return;
|
|
533
|
-
}
|
|
534
|
-
let text = normalized.text?.trim();
|
|
535
|
-
// Whether this sender may reach the agent at all — decided before
|
|
536
|
-
// the attachment is fetched.
|
|
537
|
-
//
|
|
538
|
-
// Reading an attachment downloads up to 25 MB and then spends a
|
|
539
|
-
// transcription or vision call on it. That used to happen for
|
|
540
|
-
// every photo and voice note from anyone in any group the account
|
|
541
|
-
// sits in, and only afterwards was the sender checked against
|
|
542
|
-
// `allowFrom`. A stranger could therefore spend the owner's model
|
|
543
|
-
// budget at will. None of these checks depend on the message text,
|
|
544
|
-
// so they cost nothing to run first.
|
|
545
|
-
const inboundSenderId = normalized.senderId ?? normalized.chatId;
|
|
546
|
-
const inboundScopes = (0, helpers_1.resolveAccountScopes)(cfg, accountId);
|
|
547
|
-
const inboundGroupConfig = normalized.chatType === "group"
|
|
548
|
-
? (0, helpers_1.resolveGroupConfig)(inboundScopes.groups, normalized.chatId)
|
|
549
|
-
: undefined;
|
|
550
|
-
const senderMayReachAgent = normalized.chatType === "group"
|
|
551
|
-
? Boolean(inboundGroupConfig
|
|
552
|
-
&& inboundGroupConfig.enabled !== false
|
|
553
|
-
&& (0, helpers_1.isSenderAllowed)({
|
|
554
|
-
allowFrom: inboundGroupConfig.allowFrom,
|
|
555
|
-
senderId: inboundSenderId,
|
|
556
|
-
senderUsername: normalized.senderUsername,
|
|
557
|
-
}))
|
|
558
|
-
: (0, helpers_1.isSenderAllowed)({
|
|
559
|
-
allowFrom: inboundScopes.allowFrom,
|
|
560
|
-
senderId: inboundSenderId,
|
|
561
|
-
senderUsername: normalized.senderUsername,
|
|
562
|
-
});
|
|
563
|
-
// An attachment carries no text of its own, and dropping it as
|
|
564
|
-
// "empty" is how the assistant used to go silent on being spoken
|
|
565
|
-
// to or shown something. Read it into the body instead: for a
|
|
566
|
-
// voice note and a screenshot alike, the attachment *is* the
|
|
567
|
-
// message. A caption is kept and the reading appended, because
|
|
568
|
-
// "look at this" plus the picture is one thought, not two.
|
|
569
|
-
const attachment = senderMayReachAgent ? await (0, attachments_1.readInboundAttachment)({
|
|
570
|
-
gram,
|
|
571
|
-
event,
|
|
572
|
-
cfg,
|
|
573
|
-
runtime: pluginRuntime,
|
|
574
|
-
log,
|
|
575
|
-
accountId,
|
|
576
|
-
chatId: normalized.chatId,
|
|
577
|
-
messageId: normalized.messageId,
|
|
578
|
-
}) : undefined;
|
|
579
|
-
if (attachment) {
|
|
580
|
-
const marker = attachment.understanding === "transcript" ? "голосовое" : "изображение";
|
|
581
|
-
const read = `[${marker}] ${attachment.text}`;
|
|
582
|
-
text = text ? `${text}\n\n${read}` : read;
|
|
583
|
-
}
|
|
584
|
-
// What the mention gate is allowed to read.
|
|
585
|
-
//
|
|
586
|
-
// A transcript is the sender's own speech, so "Тина, посмотри"
|
|
587
|
-
// said aloud addresses the agent exactly as typing it would. A
|
|
588
|
-
// description is not: it is a vision model reading somebody
|
|
589
|
-
// else's content, and a screenshot of a chat where a third party
|
|
590
|
-
// wrote "@tina_bot" is not an address to her. Feeding the whole
|
|
591
|
-
// body to the gate made every such screenshot wake her up.
|
|
592
|
-
const addressableText = (0, helpers_1.resolveAddressableText)({
|
|
593
|
-
messageText: normalized.text,
|
|
594
|
-
bodyText: text,
|
|
595
|
-
understanding: attachment?.understanding,
|
|
596
|
-
});
|
|
597
|
-
if (!text) {
|
|
598
|
-
log?.info?.("clawgram skipping empty inbound text", {
|
|
599
|
-
accountId,
|
|
600
|
-
chatId: normalized.chatId,
|
|
601
|
-
messageId: normalized.messageId,
|
|
602
|
-
});
|
|
603
|
-
return;
|
|
604
|
-
}
|
|
605
|
-
const senderId = normalized.senderId ?? normalized.chatId;
|
|
606
|
-
const isTelegramServiceDirect = normalized.chatType === "direct" &&
|
|
607
|
-
(normalized.chatId === constants_1.TELEGRAM_SERVICE_CHAT_ID || senderId === constants_1.TELEGRAM_SERVICE_CHAT_ID);
|
|
608
|
-
const isSavedMessagesDirect = normalized.chatType === "direct" &&
|
|
609
|
-
Boolean(selfId) &&
|
|
610
|
-
normalized.chatId === selfId &&
|
|
611
|
-
senderId === selfId;
|
|
612
|
-
if (isTelegramServiceDirect) {
|
|
613
|
-
log?.info?.("clawgram skipping Telegram service direct chat", {
|
|
614
|
-
accountId,
|
|
615
|
-
chatId: normalized.chatId,
|
|
616
|
-
messageId: normalized.messageId,
|
|
617
|
-
senderId,
|
|
618
|
-
});
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
621
|
-
if (isSavedMessagesDirect) {
|
|
622
|
-
log?.info?.("clawgram skipping Saved Messages direct chat", {
|
|
623
|
-
accountId,
|
|
624
|
-
chatId: normalized.chatId,
|
|
625
|
-
messageId: normalized.messageId,
|
|
626
|
-
senderId,
|
|
627
|
-
selfId,
|
|
628
|
-
});
|
|
629
|
-
return;
|
|
630
|
-
}
|
|
631
|
-
const senderUsername = normalized.senderUsername;
|
|
632
|
-
const senderLabel = normalized.senderDisplay || normalized.senderUsername || senderId;
|
|
633
|
-
const conversationTarget = normalized.chatType === "direct"
|
|
634
|
-
? normalized.chatId
|
|
635
|
-
: normalized.replyTarget ?? normalized.chatId;
|
|
636
|
-
const conversationFallbackTargets = [
|
|
637
|
-
normalized.chatType === "direct" ? directReplyTarget : undefined,
|
|
638
|
-
normalized.chatType === "direct" ? normalized.replyTarget : undefined,
|
|
639
|
-
normalized.chatType === "direct" && normalized.senderUsername ? `@${normalized.senderUsername}` : undefined,
|
|
640
|
-
normalized.chatId,
|
|
641
|
-
].filter((target, index, items) => {
|
|
642
|
-
if (!target || target === conversationTarget) {
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
645
|
-
return items.findIndex((candidate) => candidate === target) === index;
|
|
646
|
-
});
|
|
647
|
-
const sendTextToConversation = async (args) => {
|
|
648
|
-
const targets = [conversationTarget, ...conversationFallbackTargets];
|
|
649
|
-
// Replies have no per-call parseMode slot — the format is an
|
|
650
|
-
// account setting (2.3.1); absent keeps the GramJS default
|
|
651
|
-
// (its markdown parser — not plain text, see 2.15.0 notes).
|
|
652
|
-
const replyParseMode = gram.replyParseMode;
|
|
653
|
-
let lastError;
|
|
654
|
-
for (const target of targets) {
|
|
655
|
-
try {
|
|
656
|
-
return await gram.sendText({
|
|
657
|
-
target,
|
|
658
|
-
text: args.text,
|
|
659
|
-
replyToMessageId: args.replyToMessageId,
|
|
660
|
-
messageThreadId: args.messageThreadId,
|
|
661
|
-
parseMode: replyParseMode,
|
|
662
|
-
});
|
|
663
|
-
}
|
|
664
|
-
catch (error) {
|
|
665
|
-
lastError = error;
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
throw lastError;
|
|
669
|
-
};
|
|
670
|
-
// Resolved once, above, before the attachment fetch that depends on
|
|
671
|
-
// the answer — and by the same resolver `resolveAccount` uses, so the
|
|
672
|
-
// gate applied here is the one the account was started with.
|
|
673
|
-
const { allowFrom: directAllowFrom } = inboundScopes;
|
|
674
|
-
const dmPolicy = "open";
|
|
675
|
-
if (normalized.chatType === "group") {
|
|
676
|
-
const groupConfig = inboundGroupConfig;
|
|
677
|
-
if (!groupConfig) {
|
|
678
|
-
log?.info?.("clawgram skipping group not present in groups config", {
|
|
679
|
-
accountId,
|
|
680
|
-
chatId: normalized.chatId,
|
|
681
|
-
messageId: normalized.messageId,
|
|
682
|
-
});
|
|
683
|
-
return;
|
|
684
|
-
}
|
|
685
|
-
if (groupConfig.enabled === false) {
|
|
686
|
-
log?.info?.("clawgram skipping disabled group", {
|
|
687
|
-
accountId,
|
|
688
|
-
chatId: normalized.chatId,
|
|
689
|
-
messageId: normalized.messageId,
|
|
690
|
-
});
|
|
691
|
-
return;
|
|
692
|
-
}
|
|
693
|
-
if (!(0, helpers_1.isSenderAllowed)({
|
|
694
|
-
allowFrom: groupConfig.allowFrom,
|
|
695
|
-
senderId,
|
|
696
|
-
senderUsername: normalized.senderUsername,
|
|
697
|
-
})) {
|
|
698
|
-
log?.info?.("clawgram blocking inbound group sender by allowFrom", {
|
|
699
|
-
accountId,
|
|
700
|
-
chatId: normalized.chatId,
|
|
701
|
-
messageId: normalized.messageId,
|
|
702
|
-
senderId,
|
|
703
|
-
username: normalized.senderUsername,
|
|
704
|
-
allowFrom: groupConfig.allowFrom,
|
|
705
|
-
});
|
|
706
|
-
return;
|
|
707
|
-
}
|
|
708
|
-
const scopedGroupPeerId = (0, helpers_1.buildScopedGroupPeerId)(accountId, normalized.chatId);
|
|
709
|
-
const { route: inboundRoute, buildEnvelope } = (0, inbound_envelope_1.resolveInboundRouteEnvelopeBuilderWithRuntime)({
|
|
710
|
-
cfg,
|
|
711
|
-
channel: "clawgram",
|
|
712
|
-
accountId,
|
|
713
|
-
peer: {
|
|
714
|
-
kind: "group",
|
|
715
|
-
id: scopedGroupPeerId,
|
|
716
|
-
},
|
|
717
|
-
runtime: channelRuntime,
|
|
718
|
-
sessionStore: cfg?.session?.store,
|
|
719
|
-
});
|
|
720
|
-
// channelRuntime comes from the untyped ctx, so the generic route type falls
|
|
721
|
-
// back to the minimal RouteLike. The runtime value is a ResolvedAgentRoute.
|
|
722
|
-
const route = inboundRoute;
|
|
723
|
-
// Under `tag` the name is not an address: in a chat of a thousand
|
|
724
|
-
// people it occurs in conversation constantly and is aimed at her
|
|
725
|
-
// almost never. Only the `@` counts, and it is the same fact the
|
|
726
|
-
// stricter rung of the ladder is named after.
|
|
727
|
-
const wasMentioned = groupConfig.groupPolicy === "tag"
|
|
728
|
-
? (0, helpers_1.hasExplicitTelegramMention)({ selfUsername, text: addressableText, message: rawMessage })
|
|
729
|
-
: (0, helpers_1.hasTelegramMention)({
|
|
730
|
-
cfg,
|
|
731
|
-
agentId: route.agentId,
|
|
732
|
-
selfUsername,
|
|
733
|
-
text: addressableText,
|
|
734
|
-
message: rawMessage,
|
|
735
|
-
});
|
|
736
|
-
// One fetch serves two needs: the reply-to-self gate below and
|
|
737
|
-
// the parent's text for the agent (ReplyToBody), which a plain
|
|
738
|
-
// reply does not carry on its own.
|
|
739
|
-
const replyParent = await (0, helpers_1.resolveReplyParent)(rawMessage, { selfId, selfLabel });
|
|
740
|
-
const wasReplyToSelf = replyParent.isSelf;
|
|
741
|
-
const mentionDecision = (0, channel_inbound_1.resolveInboundMentionDecision)({
|
|
742
|
-
facts: {
|
|
743
|
-
canDetectMention: true,
|
|
744
|
-
wasMentioned,
|
|
745
|
-
hasAnyMention: /(^|\s)@[a-zA-Z0-9_]{5,}\b/.test(addressableText),
|
|
746
|
-
},
|
|
747
|
-
policy: {
|
|
748
|
-
isGroup: true,
|
|
749
|
-
requireMention: groupConfig.groupPolicy !== "open",
|
|
750
|
-
allowTextCommands: false,
|
|
751
|
-
hasControlCommand: false,
|
|
752
|
-
commandAuthorized: true,
|
|
753
|
-
},
|
|
754
|
-
});
|
|
755
|
-
log?.info?.("clawgram group mention gate", {
|
|
756
|
-
accountId,
|
|
757
|
-
chatId: normalized.chatId,
|
|
758
|
-
messageId: normalized.messageId,
|
|
759
|
-
selfUsername,
|
|
760
|
-
groupPolicy: groupConfig.groupPolicy,
|
|
761
|
-
mentionedFlag: rawMessage?.mentioned === true,
|
|
762
|
-
hasEntities: Array.isArray(rawMessage?.entities) ? rawMessage.entities.length : 0,
|
|
763
|
-
wasMentioned,
|
|
764
|
-
wasReplyToSelf,
|
|
765
|
-
shouldSkip: mentionDecision.shouldSkip,
|
|
766
|
-
textLength: text.length,
|
|
767
|
-
});
|
|
768
|
-
if (groupConfig.groupPolicy !== "open" && mentionDecision.shouldSkip && !wasReplyToSelf) {
|
|
769
|
-
log?.info?.("clawgram skipping group message without mention", {
|
|
770
|
-
accountId,
|
|
771
|
-
chatId: normalized.chatId,
|
|
772
|
-
messageId: normalized.messageId,
|
|
773
|
-
senderId,
|
|
774
|
-
});
|
|
775
|
-
return;
|
|
776
|
-
}
|
|
777
|
-
const { storePath, body } = buildEnvelope({
|
|
778
|
-
channel: "Telegram",
|
|
779
|
-
from: senderLabel,
|
|
780
|
-
body: text,
|
|
781
|
-
timestamp: normalized.timestamp,
|
|
782
|
-
});
|
|
783
|
-
const conversationRouteTarget = (0, helpers_1.buildConversationTarget)(normalized.chatId);
|
|
784
|
-
const ctxPayload = channelRuntime.reply.finalizeInboundContext({
|
|
785
|
-
Body: body,
|
|
786
|
-
BodyForAgent: text,
|
|
787
|
-
RawBody: text,
|
|
788
|
-
CommandBody: text,
|
|
789
|
-
From: conversationRouteTarget,
|
|
790
|
-
To: conversationRouteTarget,
|
|
791
|
-
SessionKey: route.sessionKey,
|
|
792
|
-
AccountId: route.accountId ?? accountId,
|
|
793
|
-
ChatType: "group",
|
|
794
|
-
ConversationLabel: senderLabel,
|
|
795
|
-
SenderId: senderId,
|
|
796
|
-
SenderUsername: normalized.senderUsername,
|
|
797
|
-
SenderName: normalized.senderDisplay,
|
|
798
|
-
GroupId: normalized.chatId,
|
|
799
|
-
GroupSubject: normalized.chatId,
|
|
800
|
-
WasMentioned: mentionDecision.effectiveWasMentioned || wasReplyToSelf,
|
|
801
|
-
WasReplyToSelf: wasReplyToSelf,
|
|
802
|
-
Provider: "telegram",
|
|
803
|
-
Surface: "clawgram",
|
|
804
|
-
MessageSid: normalized.messageId,
|
|
805
|
-
MessageSidFull: normalized.messageId,
|
|
806
|
-
Timestamp: normalized.timestamp,
|
|
807
|
-
ReplyToId: normalized.replyToMessageId,
|
|
808
|
-
// Core renders these itself as `[Replying to: "…"]` ahead of the
|
|
809
|
-
// user body — it keys off Provider being "telegram", which is set
|
|
810
|
-
// below. Without them a highlighted reply reaches the agent as
|
|
811
|
-
// bare text, and the fragment the person pointed at is lost.
|
|
812
|
-
ReplyToQuoteText: normalized.replyQuoteText,
|
|
813
|
-
ReplyToIsQuote: normalized.replyIsQuote,
|
|
814
|
-
// A plain reply has no highlight; core then falls back to the
|
|
815
|
-
// parent's body, which only exists if the channel fetched it.
|
|
816
|
-
ReplyToBody: replyParent.body,
|
|
817
|
-
ReplyToSender: replyParent.sender,
|
|
818
|
-
MessageThreadId: normalized.messageThreadId,
|
|
819
|
-
NativeChannelId: normalized.chatId,
|
|
820
|
-
// Trusted per-group prompt block from `groups.<id>.systemPrompt`.
|
|
821
|
-
// Core normalizes it (`normalizeTrustedTextField`) and appends
|
|
822
|
-
// it to the system prompt for this turn. Undefined = no block.
|
|
823
|
-
GroupSystemPrompt: groupConfig.systemPrompt,
|
|
824
|
-
OriginatingChannel: "clawgram",
|
|
825
|
-
OriginatingTo: conversationRouteTarget,
|
|
826
|
-
});
|
|
827
|
-
const groupReplyAddress = (0, group_reply_address_1.buildGroupReplyAddress)({
|
|
828
|
-
senderUsername: normalized.senderUsername,
|
|
829
|
-
senderDisplay: normalized.senderDisplay,
|
|
830
|
-
senderId,
|
|
831
|
-
});
|
|
832
|
-
(0, group_reply_address_1.rememberGroupReplyAddress)({
|
|
833
|
-
accountId: route.accountId ?? accountId,
|
|
834
|
-
chatId: normalized.chatId,
|
|
835
|
-
replyToId: normalized.messageId,
|
|
836
|
-
address: groupReplyAddress,
|
|
837
|
-
});
|
|
838
|
-
const messageThreadId = (0, helpers_1.parseOptionalThreadId)(normalized.messageThreadId);
|
|
839
|
-
const groupTypingTarget = normalized.chatId;
|
|
840
|
-
await gram.withTyping(groupTypingTarget, async () => {
|
|
841
|
-
log?.info?.("clawgram dispatching group reply", {
|
|
842
|
-
accountId,
|
|
843
|
-
chatId: normalized.chatId,
|
|
844
|
-
messageId: normalized.messageId,
|
|
845
|
-
routeSessionKey: route.sessionKey,
|
|
846
|
-
storePath,
|
|
847
|
-
});
|
|
848
|
-
await channelRuntime.session.recordInboundSession({
|
|
849
|
-
storePath,
|
|
850
|
-
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
851
|
-
ctx: ctxPayload,
|
|
852
|
-
updateLastRoute: {
|
|
853
|
-
sessionKey: route.sessionKey,
|
|
854
|
-
channel: constants_2.CHANNEL_ID,
|
|
855
|
-
to: conversationRouteTarget,
|
|
856
|
-
accountId: route.accountId ?? accountId,
|
|
857
|
-
},
|
|
858
|
-
onRecordError: (err) => {
|
|
859
|
-
log?.info?.("clawgram failed to update group last route", {
|
|
860
|
-
accountId,
|
|
861
|
-
chatId: normalized.chatId,
|
|
862
|
-
messageId: normalized.messageId,
|
|
863
|
-
error: String(err),
|
|
864
|
-
});
|
|
865
|
-
},
|
|
866
|
-
});
|
|
867
|
-
const dispatchBase = (0, inbound_reply_dispatch_1.buildInboundReplyDispatchBase)({
|
|
868
|
-
cfg,
|
|
869
|
-
channel: "clawgram",
|
|
870
|
-
accountId: route.accountId ?? accountId,
|
|
871
|
-
route,
|
|
872
|
-
storePath,
|
|
873
|
-
ctxPayload,
|
|
874
|
-
core: { channel: channelRuntime },
|
|
875
|
-
});
|
|
876
|
-
const { onModelSelected, ...replyPipeline } = (0, channel_reply_pipeline_1.createChannelReplyPipeline)({
|
|
877
|
-
cfg,
|
|
878
|
-
agentId: route.agentId,
|
|
879
|
-
channel: "clawgram",
|
|
880
|
-
accountId: route.accountId ?? accountId,
|
|
881
|
-
});
|
|
882
|
-
// Boundary for the transcript fallback below: only replies
|
|
883
|
-
// written after this instant may be salvaged. Same clock as
|
|
884
|
-
// the transcript writer — both live in this process.
|
|
885
|
-
const dispatchStartedAt = Date.now();
|
|
886
|
-
const dispatchResult = await dispatchBase.dispatchReplyWithBufferedBlockDispatcher({
|
|
887
|
-
ctx: ctxPayload,
|
|
888
|
-
cfg,
|
|
889
|
-
dispatcherOptions: {
|
|
890
|
-
...replyPipeline,
|
|
891
|
-
deliver: async (payload) => {
|
|
892
|
-
const outboundText = typeof payload.text === "string" ? payload.text.trim() : "";
|
|
893
|
-
log?.info?.("clawgram deliver group payload", {
|
|
894
|
-
accountId,
|
|
895
|
-
chatId: normalized.chatId,
|
|
896
|
-
messageId: normalized.messageId,
|
|
897
|
-
payloadTextLength: outboundText.length,
|
|
898
|
-
payloadReplyToId: payload.replyToId ?? null,
|
|
899
|
-
});
|
|
900
|
-
if (!outboundText) {
|
|
901
|
-
return;
|
|
902
|
-
}
|
|
903
|
-
// The agent may decline to answer by returning the shared
|
|
904
|
-
// silent token. Drop it before addressing: otherwise the
|
|
905
|
-
// reply-address prefix turns it into a visible message.
|
|
906
|
-
const visibleText = (0, helpers_1.stripSilentReplyToken)(outboundText);
|
|
907
|
-
if (!visibleText) {
|
|
908
|
-
log?.info?.("clawgram suppressing silent group reply", {
|
|
909
|
-
accountId,
|
|
910
|
-
chatId: normalized.chatId,
|
|
911
|
-
messageId: normalized.messageId,
|
|
912
|
-
});
|
|
913
|
-
return;
|
|
914
|
-
}
|
|
915
|
-
// Ядро подклеивает свою телеметрию к полезной нагрузке
|
|
916
|
-
// хода, и сюда она приходит тем же путём, что ответ.
|
|
917
|
-
// Проверка стояла только в `outbound.sendText`, то есть
|
|
918
|
-
// класс инцидента 30.08–01.09 был закрыт для рассылок и
|
|
919
|
-
// открыт для обычного ответа на упоминание (A5-10).
|
|
920
|
-
const groupNotice = (0, system_notice_1.shouldSuppressGroupSystemNotice)({
|
|
921
|
-
targetKind: "group",
|
|
922
|
-
text: visibleText,
|
|
923
|
-
});
|
|
924
|
-
if (groupNotice) {
|
|
925
|
-
log?.warn?.("clawgram suppressing system notice in group reply", {
|
|
926
|
-
accountId,
|
|
927
|
-
chatId: normalized.chatId,
|
|
928
|
-
messageId: normalized.messageId,
|
|
929
|
-
noticeKind: groupNotice,
|
|
930
|
-
textLength: visibleText.length,
|
|
931
|
-
});
|
|
932
|
-
return;
|
|
933
|
-
}
|
|
934
|
-
const replyToMessageId = payload.replyToId ? Number(payload.replyToId) : Number(normalized.messageId);
|
|
935
|
-
const rememberedAddress = (0, group_reply_address_1.consumeGroupReplyAddress)({
|
|
936
|
-
accountId: route.accountId ?? accountId,
|
|
937
|
-
chatId: normalized.chatId,
|
|
938
|
-
replyToId: payload.replyToId ?? normalized.messageId,
|
|
939
|
-
});
|
|
940
|
-
await sendTextToConversation({
|
|
941
|
-
text: (0, helpers_1.prefixReplyTextToAddress)(visibleText, rememberedAddress ?? groupReplyAddress),
|
|
942
|
-
replyToMessageId,
|
|
943
|
-
messageThreadId,
|
|
944
|
-
});
|
|
945
|
-
},
|
|
946
|
-
onError: (err, info) => {
|
|
947
|
-
log?.error?.("clawgram failed to dispatch group reply", {
|
|
948
|
-
accountId,
|
|
949
|
-
chatId: normalized.chatId,
|
|
950
|
-
messageId: normalized.messageId,
|
|
951
|
-
kind: info.kind,
|
|
952
|
-
error: String(err),
|
|
953
|
-
});
|
|
954
|
-
},
|
|
955
|
-
},
|
|
956
|
-
replyOptions: {
|
|
957
|
-
onModelSelected,
|
|
958
|
-
// `groups.<id>.skills` → core's per-turn skill allowlist.
|
|
959
|
-
// Undefined = inherit the agent's skills; [] = none here.
|
|
960
|
-
skillFilter: groupConfig.skillFilter,
|
|
961
|
-
},
|
|
962
|
-
});
|
|
963
|
-
log?.info?.("clawgram group dispatch completed", {
|
|
964
|
-
accountId,
|
|
965
|
-
chatId: normalized.chatId,
|
|
966
|
-
messageId: normalized.messageId,
|
|
967
|
-
queuedFinal: dispatchResult?.queuedFinal ?? null,
|
|
968
|
-
counts: dispatchResult?.counts ?? null,
|
|
969
|
-
});
|
|
970
|
-
const dispatchCounts = dispatchResult?.counts ?? { tool: 0, block: 0, final: 0 };
|
|
971
|
-
const nothingDelivered = dispatchResult?.queuedFinal !== true &&
|
|
972
|
-
(dispatchCounts.tool ?? 0) === 0 &&
|
|
973
|
-
(dispatchCounts.block ?? 0) === 0 &&
|
|
974
|
-
(dispatchCounts.final ?? 0) === 0;
|
|
975
|
-
if (nothingDelivered) {
|
|
976
|
-
const fallbackText = (0, helpers_1.readLatestAssistantFallbackFromTranscript)(route.sessionKey, storePath, dispatchStartedAt);
|
|
977
|
-
// A suppressed silent reply legitimately delivers nothing, so
|
|
978
|
-
// this fallback fires right after it. Without the same check
|
|
979
|
-
// the token would be read back from the transcript and sent.
|
|
980
|
-
//
|
|
981
|
-
// TTS markup needs the same treatment for the same reason:
|
|
982
|
-
// core strips it on the normal reply path, but this text comes
|
|
983
|
-
// straight out of the transcript. On 2026-08-08 a group got
|
|
984
|
-
// `[[tts:text]]Привет, Вася!…[[/tts:text]]` verbatim. The
|
|
985
|
-
// spoken words are kept — a synthesis that did not happen
|
|
986
|
-
// should degrade to readable text, not to markup.
|
|
987
|
-
const rawFallback = fallbackText
|
|
988
|
-
? (0, helpers_1.stripTtsDirectives)((0, helpers_1.stripSilentReplyToken)(fallbackText))
|
|
989
|
-
: "";
|
|
990
|
-
// Тот же фильтр и здесь: последняя реплика в стенограмме
|
|
991
|
-
// вполне может оказаться именно уведомлением об ошибке.
|
|
992
|
-
const fallbackNotice = rawFallback
|
|
993
|
-
? (0, system_notice_1.shouldSuppressGroupSystemNotice)({ targetKind: "group", text: rawFallback })
|
|
994
|
-
: undefined;
|
|
995
|
-
if (fallbackNotice) {
|
|
996
|
-
log?.warn?.("clawgram suppressing system notice in transcript fallback", {
|
|
997
|
-
accountId,
|
|
998
|
-
chatId: normalized.chatId,
|
|
999
|
-
messageId: normalized.messageId,
|
|
1000
|
-
noticeKind: fallbackNotice,
|
|
1001
|
-
textLength: rawFallback.length,
|
|
1002
|
-
});
|
|
1003
|
-
}
|
|
1004
|
-
const visibleFallbackText = fallbackNotice ? "" : rawFallback;
|
|
1005
|
-
if (!visibleFallbackText) {
|
|
1006
|
-
if (fallbackText) {
|
|
1007
|
-
log?.info?.("clawgram skipping silent transcript fallback", {
|
|
1008
|
-
accountId,
|
|
1009
|
-
chatId: normalized.chatId,
|
|
1010
|
-
messageId: normalized.messageId,
|
|
1011
|
-
routeSessionKey: route.sessionKey,
|
|
1012
|
-
});
|
|
1013
|
-
}
|
|
1014
|
-
else {
|
|
1015
|
-
log?.warn?.("clawgram transcript fallback unavailable", {
|
|
1016
|
-
accountId,
|
|
1017
|
-
chatId: normalized.chatId,
|
|
1018
|
-
messageId: normalized.messageId,
|
|
1019
|
-
routeSessionKey: route.sessionKey,
|
|
1020
|
-
});
|
|
1021
|
-
}
|
|
1022
|
-
// Named, and nothing came back: leave a reaction so the
|
|
1023
|
-
// decision is visible instead of reading as her ignoring
|
|
1024
|
-
// people. The condition is her silence, not the shape of
|
|
1025
|
-
// the transcript — a turn that wrote no entry at all is
|
|
1026
|
-
// just as silent as one that wrote the NO_REPLY token.
|
|
1027
|
-
//
|
|
1028
|
-
// Never allowed to disturb the turn: the reply is already
|
|
1029
|
-
// settled by this point, so a failure here stays silent.
|
|
1030
|
-
await reactToSilentMentionForAccount({
|
|
1031
|
-
cfg,
|
|
1032
|
-
accountId,
|
|
1033
|
-
gram: runtimes.get(accountId),
|
|
1034
|
-
pluginRuntime,
|
|
1035
|
-
chatId: normalized.chatId,
|
|
1036
|
-
messageId: normalized.messageId,
|
|
1037
|
-
messageText: normalized.text,
|
|
1038
|
-
// Same sense of "addressed" the agent was given for this
|
|
1039
|
-
// turn on line 817: a reply to her own message counts as
|
|
1040
|
-
// being spoken to, mention or not.
|
|
1041
|
-
wasMentioned: mentionDecision.effectiveWasMentioned || wasReplyToSelf,
|
|
1042
|
-
}).catch((err) => {
|
|
1043
|
-
log?.info?.("clawgram silent-mention reaction failed", {
|
|
1044
|
-
accountId,
|
|
1045
|
-
chatId: normalized.chatId,
|
|
1046
|
-
messageId: normalized.messageId,
|
|
1047
|
-
error: String(err),
|
|
1048
|
-
});
|
|
1049
|
-
});
|
|
1050
|
-
}
|
|
1051
|
-
else {
|
|
1052
|
-
log?.warn?.("clawgram using transcript fallback reply", {
|
|
1053
|
-
accountId,
|
|
1054
|
-
chatId: normalized.chatId,
|
|
1055
|
-
messageId: normalized.messageId,
|
|
1056
|
-
routeSessionKey: route.sessionKey,
|
|
1057
|
-
fallbackTextLength: visibleFallbackText.length,
|
|
1058
|
-
});
|
|
1059
|
-
await sendTextToConversation({
|
|
1060
|
-
text: (0, helpers_1.prefixReplyTextToAddress)(visibleFallbackText, groupReplyAddress),
|
|
1061
|
-
replyToMessageId: Number(normalized.messageId),
|
|
1062
|
-
messageThreadId,
|
|
1063
|
-
});
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
|
-
}, {
|
|
1067
|
-
readMessageId: Number(normalized.messageId),
|
|
1068
|
-
messageThreadId,
|
|
1069
|
-
// The indicator is a promise of an answer, and it is owed only
|
|
1070
|
-
// to someone who addressed her. Under `open` the turn runs on
|
|
1071
|
-
// every message in the chat, so without this the whole room
|
|
1072
|
-
// watches her "type" through conversations she is only reading.
|
|
1073
|
-
typing: mentionDecision.effectiveWasMentioned || wasReplyToSelf,
|
|
1074
|
-
});
|
|
1075
|
-
log?.info?.("clawgram group inbound handled", {
|
|
1076
|
-
accountId,
|
|
1077
|
-
chatId: normalized.chatId,
|
|
1078
|
-
messageId: normalized.messageId,
|
|
1079
|
-
senderId,
|
|
1080
|
-
senderLabel,
|
|
1081
|
-
wasMentioned: mentionDecision.effectiveWasMentioned,
|
|
1082
|
-
wasReplyToSelf,
|
|
1083
|
-
});
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
if (!(0, helpers_1.isSenderAllowed)({
|
|
1087
|
-
allowFrom: directAllowFrom,
|
|
1088
|
-
senderId,
|
|
1089
|
-
senderUsername: normalized.senderUsername,
|
|
1090
|
-
})) {
|
|
1091
|
-
log?.info?.("clawgram direct allowFrom mismatch", {
|
|
1092
|
-
accountId,
|
|
1093
|
-
senderId,
|
|
1094
|
-
senderUsername: normalized.senderUsername,
|
|
1095
|
-
allowFrom: directAllowFrom,
|
|
1096
|
-
});
|
|
1097
|
-
return;
|
|
1098
|
-
}
|
|
1099
|
-
const access = await (0, direct_dm_1.resolveInboundDirectDmAccessWithRuntime)({
|
|
1100
|
-
cfg,
|
|
1101
|
-
channel: "clawgram",
|
|
1102
|
-
accountId,
|
|
1103
|
-
dmPolicy,
|
|
1104
|
-
allowFrom: directAllowFrom,
|
|
1105
|
-
senderId,
|
|
1106
|
-
rawBody: text,
|
|
1107
|
-
runtime: channelRuntime.commands,
|
|
1108
|
-
isSenderAllowed: (_candidateSenderId, allowEntries) => (0, helpers_1.isSenderAllowed)({
|
|
1109
|
-
allowFrom: allowEntries,
|
|
1110
|
-
senderId,
|
|
1111
|
-
senderUsername,
|
|
1112
|
-
}),
|
|
1113
|
-
readStoreAllowFrom: pairing.readStoreForDmPolicy,
|
|
1114
|
-
});
|
|
1115
|
-
if (access.access.decision === "block") {
|
|
1116
|
-
log?.info?.("clawgram blocking inbound direct message", {
|
|
1117
|
-
accountId,
|
|
1118
|
-
chatId: normalized.chatId,
|
|
1119
|
-
messageId: normalized.messageId,
|
|
1120
|
-
senderId,
|
|
1121
|
-
reason: access.access.reason,
|
|
1122
|
-
reasonCode: access.access.reasonCode,
|
|
1123
|
-
});
|
|
1124
|
-
return;
|
|
1125
|
-
}
|
|
1126
|
-
if (access.access.decision === "pairing") {
|
|
1127
|
-
await pairing.issueChallenge({
|
|
1128
|
-
senderId,
|
|
1129
|
-
senderIdLine: `Your Telegram user id: ${senderId}`,
|
|
1130
|
-
meta: {
|
|
1131
|
-
username: normalized.senderUsername,
|
|
1132
|
-
name: normalized.senderDisplay,
|
|
1133
|
-
},
|
|
1134
|
-
sendPairingReply: async (pairingText) => {
|
|
1135
|
-
await sendTextToConversation({
|
|
1136
|
-
text: pairingText,
|
|
1137
|
-
});
|
|
1138
|
-
},
|
|
1139
|
-
onReplyError: (err) => {
|
|
1140
|
-
log?.info?.("clawgram pairing reply failed", {
|
|
1141
|
-
accountId,
|
|
1142
|
-
chatId: normalized.chatId,
|
|
1143
|
-
senderId,
|
|
1144
|
-
error: String(err),
|
|
1145
|
-
});
|
|
1146
|
-
},
|
|
1147
|
-
});
|
|
1148
|
-
log?.info?.("clawgram pairing required for inbound direct message", {
|
|
1149
|
-
accountId,
|
|
1150
|
-
chatId: normalized.chatId,
|
|
1151
|
-
messageId: normalized.messageId,
|
|
1152
|
-
senderId,
|
|
1153
|
-
});
|
|
1154
|
-
return;
|
|
1155
|
-
}
|
|
1156
|
-
// Same fetch as the group path. In a DM the parent is as often
|
|
1157
|
-
// the agent's own message as the person's — the owner answers a
|
|
1158
|
-
// notice she sent — and neither text is available any other way.
|
|
1159
|
-
const replyParent = await (0, helpers_1.resolveReplyParent)(rawMessage, { selfId, selfLabel });
|
|
1160
|
-
await gram.withTyping(conversationTarget, async () => {
|
|
1161
|
-
await (0, direct_dm_1.dispatchInboundDirectDmWithRuntime)({
|
|
1162
|
-
cfg,
|
|
1163
|
-
runtime: { channel: channelRuntime },
|
|
1164
|
-
channel: "clawgram",
|
|
1165
|
-
channelLabel: "Telegram",
|
|
1166
|
-
accountId,
|
|
1167
|
-
peer: {
|
|
1168
|
-
kind: "direct",
|
|
1169
|
-
id: senderId,
|
|
1170
|
-
},
|
|
1171
|
-
senderId,
|
|
1172
|
-
senderAddress: `telegram:${senderId}`,
|
|
1173
|
-
recipientAddress: selfId ? `telegram:${selfId}` : `telegram:${accountId}`,
|
|
1174
|
-
conversationLabel: senderLabel,
|
|
1175
|
-
rawBody: text,
|
|
1176
|
-
messageId: normalized.messageId,
|
|
1177
|
-
timestamp: normalized.timestamp,
|
|
1178
|
-
commandAuthorized: access.commandAuthorized,
|
|
1179
|
-
provider: "telegram",
|
|
1180
|
-
surface: "clawgram",
|
|
1181
|
-
originatingChannel: "clawgram",
|
|
1182
|
-
originatingTo: senderId,
|
|
1183
|
-
extraContext: {
|
|
1184
|
-
SenderUsername: normalized.senderUsername,
|
|
1185
|
-
SenderName: normalized.senderDisplay,
|
|
1186
|
-
ReplyToId: normalized.replyToMessageId,
|
|
1187
|
-
// Same reason as the group path: highlighted replies happen in
|
|
1188
|
-
// direct messages too, and the fragment is not part of the text.
|
|
1189
|
-
ReplyToQuoteText: normalized.replyQuoteText,
|
|
1190
|
-
ReplyToIsQuote: normalized.replyIsQuote,
|
|
1191
|
-
ReplyToBody: replyParent.body,
|
|
1192
|
-
ReplyToSender: replyParent.sender,
|
|
1193
|
-
NativeChannelId: normalized.chatId,
|
|
1194
|
-
},
|
|
1195
|
-
deliver: async (payload) => {
|
|
1196
|
-
const outboundText = typeof payload.text === "string" ? payload.text.trim() : "";
|
|
1197
|
-
if (!outboundText) {
|
|
1198
|
-
return;
|
|
1199
|
-
}
|
|
1200
|
-
const visibleText = (0, helpers_1.stripSilentReplyToken)(outboundText);
|
|
1201
|
-
if (!visibleText) {
|
|
1202
|
-
log?.info?.("clawgram suppressing silent direct reply", {
|
|
1203
|
-
accountId,
|
|
1204
|
-
chatId: normalized.chatId,
|
|
1205
|
-
messageId: normalized.messageId,
|
|
1206
|
-
});
|
|
1207
|
-
return;
|
|
1208
|
-
}
|
|
1209
|
-
await sendTextToConversation({
|
|
1210
|
-
text: visibleText,
|
|
1211
|
-
replyToMessageId: payload.replyToId ? Number(payload.replyToId) : undefined,
|
|
1212
|
-
});
|
|
1213
|
-
},
|
|
1214
|
-
onRecordError: (err) => {
|
|
1215
|
-
log?.info?.("clawgram failed to record inbound session", {
|
|
1216
|
-
accountId,
|
|
1217
|
-
chatId: normalized.chatId,
|
|
1218
|
-
messageId: normalized.messageId,
|
|
1219
|
-
error: String(err),
|
|
1220
|
-
});
|
|
1221
|
-
},
|
|
1222
|
-
onDispatchError: (err, info) => {
|
|
1223
|
-
log?.info?.("clawgram failed to dispatch reply", {
|
|
1224
|
-
accountId,
|
|
1225
|
-
chatId: normalized.chatId,
|
|
1226
|
-
messageId: normalized.messageId,
|
|
1227
|
-
kind: info.kind,
|
|
1228
|
-
error: String(err),
|
|
1229
|
-
});
|
|
1230
|
-
},
|
|
1231
|
-
});
|
|
1232
|
-
}, {
|
|
1233
|
-
readMessageId: Number(normalized.messageId),
|
|
1234
|
-
});
|
|
1235
|
-
log?.info?.("clawgram inbound handled", {
|
|
1236
|
-
accountId,
|
|
1237
|
-
chatId: normalized.chatId,
|
|
1238
|
-
messageId: normalized.messageId,
|
|
1239
|
-
senderId,
|
|
1240
|
-
senderLabel,
|
|
1241
|
-
});
|
|
1242
|
-
}
|
|
1243
|
-
catch (error) {
|
|
1244
|
-
const rawMessage = event?.message;
|
|
1245
|
-
log?.error?.("clawgram inbound handling failed", {
|
|
1246
|
-
accountId,
|
|
1247
|
-
chatId: String(rawMessage?.chatId ?? rawMessage?.peerId?.userId ?? rawMessage?.peerId?.chatId ?? rawMessage?.peerId?.channelId ?? ""),
|
|
1248
|
-
messageId: String(rawMessage?.id ?? ""),
|
|
1249
|
-
error: String(error),
|
|
1250
|
-
});
|
|
1251
|
-
log?.info?.("clawgram inbound preflight failed", {
|
|
1252
|
-
accountId,
|
|
1253
|
-
chatId: String(rawMessage?.chatId ?? rawMessage?.peerId?.userId ?? rawMessage?.peerId?.chatId ?? rawMessage?.peerId?.channelId ?? ""),
|
|
1254
|
-
messageId: String(rawMessage?.id ?? ""),
|
|
1255
|
-
error: String(error),
|
|
1256
|
-
});
|
|
1257
|
-
}
|
|
1258
|
-
};
|
|
390
|
+
const eventHandler = async (event) => (0, inbound_pipeline_1.handleInboundEvent)(event, {
|
|
391
|
+
accountId, cfg, channelRuntime, client, gram, log, pairing,
|
|
392
|
+
pluginRuntime, runtimes, selfId, selfLabel, selfUsername,
|
|
393
|
+
});
|
|
1259
394
|
client.addEventHandler(eventHandler, eventBuilder);
|
|
1260
395
|
// Being added to a chat arrives as a service message, which `NewMessage`
|
|
1261
396
|
// drops — so joins are observed on the raw update stream instead. Only
|
|
@@ -1304,7 +439,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1304
439
|
},
|
|
1305
440
|
},
|
|
1306
441
|
messaging: {
|
|
1307
|
-
targetPrefixes: [
|
|
442
|
+
targetPrefixes: [constants_1.CHANNEL_ID, "tguserbot", "telegram", "tg"],
|
|
1308
443
|
normalizeTarget(raw) {
|
|
1309
444
|
const normalized = (0, helpers_1.normalizeOutboundTarget)(raw);
|
|
1310
445
|
return normalized || undefined;
|
|
@@ -1376,7 +511,7 @@ const createChannelPlugin = (runtimes, pluginRuntime) => {
|
|
|
1376
511
|
return (0, core_1.buildChannelOutboundSessionRoute)({
|
|
1377
512
|
cfg: params.cfg,
|
|
1378
513
|
agentId: params.agentId,
|
|
1379
|
-
channel:
|
|
514
|
+
channel: constants_1.CHANNEL_ID,
|
|
1380
515
|
accountId,
|
|
1381
516
|
peer: {
|
|
1382
517
|
kind: (0, helpers_1.routeKindFromChatType)(chatType),
|