@inline-openclaw/inline 0.0.18 → 0.0.20
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/index.d.ts.map +1 -1
- package/dist/index.js +37245 -22
- package/dist/index.js.map +150 -1
- package/dist/inline/monitor.d.ts.map +1 -1
- package/dist/inline/profile-tool.d.ts +6 -0
- package/dist/inline/profile-tool.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +2 -2
- package/dist/inline/accounts.js +0 -84
- package/dist/inline/accounts.js.map +0 -1
- package/dist/inline/actions.js +0 -1364
- package/dist/inline/actions.js.map +0 -1
- package/dist/inline/channel.js +0 -867
- package/dist/inline/channel.js.map +0 -1
- package/dist/inline/config-schema.js +0 -75
- package/dist/inline/config-schema.js.map +0 -1
- package/dist/inline/media.js +0 -213
- package/dist/inline/media.js.map +0 -1
- package/dist/inline/members-tool.js +0 -135
- package/dist/inline/members-tool.js.map +0 -1
- package/dist/inline/message-content.js +0 -305
- package/dist/inline/message-content.js.map +0 -1
- package/dist/inline/message-tools.js +0 -395
- package/dist/inline/message-tools.js.map +0 -1
- package/dist/inline/monitor.js +0 -1098
- package/dist/inline/monitor.js.map +0 -1
- package/dist/inline/normalize.js +0 -25
- package/dist/inline/normalize.js.map +0 -1
- package/dist/inline/policy.js +0 -107
- package/dist/inline/policy.js.map +0 -1
- package/dist/inline/space-members.js +0 -57
- package/dist/inline/space-members.js.map +0 -1
- package/dist/runtime.js +0 -11
- package/dist/runtime.js.map +0 -1
package/dist/inline/monitor.js
DELETED
|
@@ -1,1098 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import { createReplyPrefixOptions, createTypingCallbacks, logInboundDrop, resolveChannelMediaMaxBytes, resolveControlCommandGate, resolveMentionGatingWithBypass, } from "openclaw/plugin-sdk";
|
|
4
|
-
import { InlineSdkClient, JsonFileStateStore, Method } from "@inline-chat/realtime-sdk";
|
|
5
|
-
import { resolveInlineToken } from "./accounts.js";
|
|
6
|
-
import { resolveInlineGroupRequireMention } from "./policy.js";
|
|
7
|
-
import { getInlineRuntime } from "../runtime.js";
|
|
8
|
-
import { uploadInlineMediaFromUrl } from "./media.js";
|
|
9
|
-
import { summarizeInlineMessageContent } from "./message-content.js";
|
|
10
|
-
const CHANNEL_ID = "inline";
|
|
11
|
-
const DEFAULT_GROUP_HISTORY_LIMIT = 12;
|
|
12
|
-
const DEFAULT_DM_HISTORY_LIMIT = 6;
|
|
13
|
-
const HISTORY_LINE_MAX_CHARS = 280;
|
|
14
|
-
const BOT_MESSAGE_CACHE_LIMIT = 500;
|
|
15
|
-
const REACTION_TARGET_LOOKUP_LIMIT = 8;
|
|
16
|
-
const REPLY_TARGET_LOOKUP_LIMIT = 8;
|
|
17
|
-
const ATTACHMENT_CONTEXT_LIMIT = 6;
|
|
18
|
-
const DEFAULT_INLINE_MEDIA_MAX_BYTES = 300 * 1024 * 1024;
|
|
19
|
-
const INLINE_FORMATTING_NOTE = "Inline formatting note: prefer bullet lists over markdown tables. If a table is necessary, render it inside a fenced code block.";
|
|
20
|
-
const GET_MESSAGES_METHOD = typeof Method.GET_MESSAGES === "number" &&
|
|
21
|
-
Number.isInteger(Method.GET_MESSAGES) &&
|
|
22
|
-
Method.GET_MESSAGES > 0
|
|
23
|
-
? Method.GET_MESSAGES
|
|
24
|
-
: null;
|
|
25
|
-
function normalizeAllowEntry(raw) {
|
|
26
|
-
return raw.trim().replace(/^inline:/i, "").replace(/^user:/i, "");
|
|
27
|
-
}
|
|
28
|
-
function normalizeAllowlist(entries) {
|
|
29
|
-
return (entries ?? [])
|
|
30
|
-
.map((entry) => normalizeAllowEntry(String(entry)))
|
|
31
|
-
.map((entry) => entry.trim())
|
|
32
|
-
.filter(Boolean);
|
|
33
|
-
}
|
|
34
|
-
function allowlistMatch(params) {
|
|
35
|
-
if (params.allowFrom.some((entry) => entry === "*"))
|
|
36
|
-
return true;
|
|
37
|
-
return params.allowFrom.some((entry) => entry === params.senderId);
|
|
38
|
-
}
|
|
39
|
-
async function resolveChatInfo(client, cache, chatId) {
|
|
40
|
-
const existing = cache.get(chatId);
|
|
41
|
-
if (existing)
|
|
42
|
-
return existing;
|
|
43
|
-
const result = await client.getChat({ chatId });
|
|
44
|
-
const peerKind = result.peer?.type.oneofKind;
|
|
45
|
-
const kind = peerKind === "user" ? "direct" : "group";
|
|
46
|
-
const title = result.title?.trim() || null;
|
|
47
|
-
const info = { kind, title };
|
|
48
|
-
cache.set(chatId, info);
|
|
49
|
-
return info;
|
|
50
|
-
}
|
|
51
|
-
function normalizeInlineUsername(raw) {
|
|
52
|
-
const trimmed = raw?.trim();
|
|
53
|
-
if (!trimmed)
|
|
54
|
-
return undefined;
|
|
55
|
-
return trimmed.startsWith("@") ? trimmed.slice(1) : trimmed;
|
|
56
|
-
}
|
|
57
|
-
function normalizeInlineCommandBody(raw, botUsername) {
|
|
58
|
-
const normalized = raw.trim();
|
|
59
|
-
const normalizedBotUsername = botUsername?.trim().toLowerCase();
|
|
60
|
-
const mentionMatch = normalizedBotUsername ? normalized.match(/^\/([^\s@]+)@([^\s]+)(.*)$/) : null;
|
|
61
|
-
if (mentionMatch) {
|
|
62
|
-
const [, command, targetUsername, suffix] = mentionMatch;
|
|
63
|
-
if (targetUsername?.toLowerCase() === normalizedBotUsername) {
|
|
64
|
-
return `/${command}${suffix ?? ""}`;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
return normalized;
|
|
68
|
-
}
|
|
69
|
-
function buildInlineSenderName(params) {
|
|
70
|
-
const name = [params.firstName, params.lastName].filter(Boolean).join(" ").trim();
|
|
71
|
-
return name || undefined;
|
|
72
|
-
}
|
|
73
|
-
function rewriteNumericMentionsToUsernames(text, senderProfilesById) {
|
|
74
|
-
if (!text.includes("@"))
|
|
75
|
-
return text;
|
|
76
|
-
return text.replace(/(^|[^\w])@([0-9]+)\b/g, (full, prefix, userId) => {
|
|
77
|
-
const username = senderProfilesById.get(userId)?.username;
|
|
78
|
-
if (!username)
|
|
79
|
-
return full;
|
|
80
|
-
return `${prefix}@${username}`;
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
function rememberBotMessageId(cache, chatId, messageId) {
|
|
84
|
-
const key = String(chatId);
|
|
85
|
-
const list = cache.get(key) ?? [];
|
|
86
|
-
const nextId = String(messageId);
|
|
87
|
-
if (!list.includes(nextId))
|
|
88
|
-
list.push(nextId);
|
|
89
|
-
if (list.length > BOT_MESSAGE_CACHE_LIMIT) {
|
|
90
|
-
list.splice(0, list.length - BOT_MESSAGE_CACHE_LIMIT);
|
|
91
|
-
}
|
|
92
|
-
cache.set(key, list);
|
|
93
|
-
}
|
|
94
|
-
function hasBotMessageId(cache, chatId, messageId) {
|
|
95
|
-
const key = String(chatId);
|
|
96
|
-
return (cache.get(key) ?? []).includes(String(messageId));
|
|
97
|
-
}
|
|
98
|
-
function rememberBotMessagesFromList(params) {
|
|
99
|
-
for (const item of params.messages) {
|
|
100
|
-
if (item.fromId === params.meId) {
|
|
101
|
-
rememberBotMessageId(params.botMessageIdsByChat, params.chatId, item.id);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
function buildChatPeer(chatId) {
|
|
106
|
-
return {
|
|
107
|
-
type: {
|
|
108
|
-
oneofKind: "chat",
|
|
109
|
-
chat: { chatId },
|
|
110
|
-
},
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
async function loadChatHistoryMessages(params) {
|
|
114
|
-
const result = await params.client.invokeRaw(Method.GET_CHAT_HISTORY, {
|
|
115
|
-
oneofKind: "getChatHistory",
|
|
116
|
-
getChatHistory: {
|
|
117
|
-
peerId: buildChatPeer(params.chatId),
|
|
118
|
-
...(params.offsetId != null ? { offsetId: params.offsetId } : {}),
|
|
119
|
-
limit: params.limit,
|
|
120
|
-
},
|
|
121
|
-
});
|
|
122
|
-
if (result.oneofKind !== "getChatHistory") {
|
|
123
|
-
return null;
|
|
124
|
-
}
|
|
125
|
-
return result.getChatHistory.messages ?? [];
|
|
126
|
-
}
|
|
127
|
-
async function findChatMessageById(params) {
|
|
128
|
-
const directResult = GET_MESSAGES_METHOD == null
|
|
129
|
-
? null
|
|
130
|
-
: await params.client
|
|
131
|
-
.invokeRaw(GET_MESSAGES_METHOD, {
|
|
132
|
-
oneofKind: "getMessages",
|
|
133
|
-
getMessages: {
|
|
134
|
-
peerId: buildChatPeer(params.chatId),
|
|
135
|
-
messageIds: [params.messageId],
|
|
136
|
-
},
|
|
137
|
-
})
|
|
138
|
-
.catch(() => null);
|
|
139
|
-
if (directResult?.oneofKind === "getMessages") {
|
|
140
|
-
const directMessages = directResult.getMessages.messages ?? [];
|
|
141
|
-
rememberBotMessagesFromList({
|
|
142
|
-
messages: directMessages,
|
|
143
|
-
meId: params.meId,
|
|
144
|
-
chatId: params.chatId,
|
|
145
|
-
botMessageIdsByChat: params.botMessageIdsByChat,
|
|
146
|
-
});
|
|
147
|
-
const directTarget = directMessages.find((item) => item.id === params.messageId) ?? null;
|
|
148
|
-
if (directTarget) {
|
|
149
|
-
return directTarget;
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
// Compatibility fallback for older servers without GET_MESSAGES.
|
|
153
|
-
const historyMessages = await loadChatHistoryMessages({
|
|
154
|
-
client: params.client,
|
|
155
|
-
chatId: params.chatId,
|
|
156
|
-
offsetId: params.messageId + 1n,
|
|
157
|
-
limit: params.limit,
|
|
158
|
-
});
|
|
159
|
-
if (!historyMessages) {
|
|
160
|
-
return null;
|
|
161
|
-
}
|
|
162
|
-
rememberBotMessagesFromList({
|
|
163
|
-
messages: historyMessages,
|
|
164
|
-
meId: params.meId,
|
|
165
|
-
chatId: params.chatId,
|
|
166
|
-
botMessageIdsByChat: params.botMessageIdsByChat,
|
|
167
|
-
});
|
|
168
|
-
return historyMessages.find((item) => item.id === params.messageId) ?? null;
|
|
169
|
-
}
|
|
170
|
-
async function isReactionTargetBotMessage(params) {
|
|
171
|
-
const target = await findChatMessageById({
|
|
172
|
-
client: params.client,
|
|
173
|
-
chatId: params.chatId,
|
|
174
|
-
messageId: params.messageId,
|
|
175
|
-
limit: REACTION_TARGET_LOOKUP_LIMIT,
|
|
176
|
-
meId: params.meId,
|
|
177
|
-
botMessageIdsByChat: params.botMessageIdsByChat,
|
|
178
|
-
});
|
|
179
|
-
if (!target) {
|
|
180
|
-
return hasBotMessageId(params.botMessageIdsByChat, params.chatId, params.messageId);
|
|
181
|
-
}
|
|
182
|
-
return target.fromId === params.meId;
|
|
183
|
-
}
|
|
184
|
-
function normalizeHistoryText(raw) {
|
|
185
|
-
const compact = (raw ?? "").replace(/\s+/g, " ").trim();
|
|
186
|
-
if (!compact)
|
|
187
|
-
return "";
|
|
188
|
-
if (compact.length <= HISTORY_LINE_MAX_CHARS)
|
|
189
|
-
return compact;
|
|
190
|
-
return `${compact.slice(0, HISTORY_LINE_MAX_CHARS - 1)}…`;
|
|
191
|
-
}
|
|
192
|
-
function drainCompleteParagraphs(buffer) {
|
|
193
|
-
const paragraphs = [];
|
|
194
|
-
let rest = buffer;
|
|
195
|
-
while (rest.length > 0) {
|
|
196
|
-
const breakIndex = rest.indexOf("\n\n");
|
|
197
|
-
if (breakIndex < 0)
|
|
198
|
-
break;
|
|
199
|
-
const paragraph = rest.slice(0, breakIndex).trim();
|
|
200
|
-
if (paragraph) {
|
|
201
|
-
paragraphs.push(paragraph);
|
|
202
|
-
}
|
|
203
|
-
rest = rest.slice(breakIndex).replace(/^\n+/, "");
|
|
204
|
-
}
|
|
205
|
-
return { paragraphs, rest };
|
|
206
|
-
}
|
|
207
|
-
function appendParagraphText(existing, paragraph) {
|
|
208
|
-
const trimmed = paragraph.trim();
|
|
209
|
-
if (!trimmed)
|
|
210
|
-
return existing;
|
|
211
|
-
return existing ? `${existing}\n\n${trimmed}` : trimmed;
|
|
212
|
-
}
|
|
213
|
-
function extractCompleteParagraphText(text) {
|
|
214
|
-
const drained = drainCompleteParagraphs(text);
|
|
215
|
-
return drained.paragraphs.reduce((acc, paragraph) => appendParagraphText(acc, paragraph), "").trim();
|
|
216
|
-
}
|
|
217
|
-
function resolveHistorySenderLabel(params) {
|
|
218
|
-
if (params.senderId === params.meId)
|
|
219
|
-
return "assistant";
|
|
220
|
-
const senderId = String(params.senderId);
|
|
221
|
-
const profile = params.senderProfilesById.get(senderId);
|
|
222
|
-
if (profile?.username)
|
|
223
|
-
return `@${profile.username}`;
|
|
224
|
-
if (profile?.name)
|
|
225
|
-
return profile.name;
|
|
226
|
-
return `user:${senderId}`;
|
|
227
|
-
}
|
|
228
|
-
function resolveHistoryLimit(params) {
|
|
229
|
-
if (params.isGroup) {
|
|
230
|
-
return params.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT;
|
|
231
|
-
}
|
|
232
|
-
return params.dmHistoryLimit ?? params.historyLimit ?? DEFAULT_DM_HISTORY_LIMIT;
|
|
233
|
-
}
|
|
234
|
-
function resolveInlineMediaMaxBytes(params) {
|
|
235
|
-
return (resolveChannelMediaMaxBytes({
|
|
236
|
-
cfg: params.cfg,
|
|
237
|
-
accountId: params.account.accountId,
|
|
238
|
-
resolveChannelLimitMb: ({ accountId }) => {
|
|
239
|
-
if (accountId != null && accountId !== params.account.accountId)
|
|
240
|
-
return undefined;
|
|
241
|
-
return params.account.config.mediaMaxMb;
|
|
242
|
-
},
|
|
243
|
-
}) ?? DEFAULT_INLINE_MEDIA_MAX_BYTES);
|
|
244
|
-
}
|
|
245
|
-
function buildInlineInboundMediaPayload(media) {
|
|
246
|
-
const first = media[0];
|
|
247
|
-
const mediaPaths = media.map((item) => item.path);
|
|
248
|
-
const firstMediaType = first?.contentType?.trim();
|
|
249
|
-
const mediaTypes = media
|
|
250
|
-
.map((item) => item.contentType?.trim())
|
|
251
|
-
.filter((item) => Boolean(item));
|
|
252
|
-
return {
|
|
253
|
-
...(first?.path ? { MediaPath: first.path, MediaUrl: first.path } : {}),
|
|
254
|
-
...(firstMediaType ? { MediaType: firstMediaType } : {}),
|
|
255
|
-
...(mediaPaths.length > 0 ? { MediaPaths: mediaPaths, MediaUrls: mediaPaths } : {}),
|
|
256
|
-
...(mediaTypes.length > 0 ? { MediaTypes: mediaTypes } : {}),
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
function buildInlineAttachmentPlaceholder(content) {
|
|
260
|
-
const media = content.media;
|
|
261
|
-
if (!media)
|
|
262
|
-
return "";
|
|
263
|
-
switch (media.kind) {
|
|
264
|
-
case "photo":
|
|
265
|
-
return "<media:image>";
|
|
266
|
-
case "video":
|
|
267
|
-
return "<media:video>";
|
|
268
|
-
case "document":
|
|
269
|
-
return "<media:document>";
|
|
270
|
-
default:
|
|
271
|
-
return "";
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
function buildInlineInboundBodyText(content) {
|
|
275
|
-
const textWithPlaceholder = [content.rawText, buildInlineAttachmentPlaceholder(content)]
|
|
276
|
-
.filter(Boolean)
|
|
277
|
-
.join("\n")
|
|
278
|
-
.trim();
|
|
279
|
-
return textWithPlaceholder || content.text;
|
|
280
|
-
}
|
|
281
|
-
function resolveFilePathHint(params) {
|
|
282
|
-
const preferred = params.preferredName?.trim();
|
|
283
|
-
if (preferred)
|
|
284
|
-
return preferred;
|
|
285
|
-
try {
|
|
286
|
-
const pathname = new URL(params.sourceUrl).pathname;
|
|
287
|
-
const base = path.basename(pathname).trim();
|
|
288
|
-
if (base)
|
|
289
|
-
return base;
|
|
290
|
-
}
|
|
291
|
-
catch {
|
|
292
|
-
// ignore malformed urls and let the media pipeline choose a filename
|
|
293
|
-
}
|
|
294
|
-
return undefined;
|
|
295
|
-
}
|
|
296
|
-
async function resolveInlineInboundMedia(params) {
|
|
297
|
-
const content = summarizeInlineMessageContent(params.message);
|
|
298
|
-
const candidates = new Map();
|
|
299
|
-
if (content.media?.url) {
|
|
300
|
-
candidates.set(content.media.url, {
|
|
301
|
-
fileName: content.media.fileName ?? null,
|
|
302
|
-
mimeType: content.media.mimeType ?? null,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
for (const attachment of content.attachments) {
|
|
306
|
-
if (attachment.kind !== "urlPreview" || !attachment.previewImageUrl)
|
|
307
|
-
continue;
|
|
308
|
-
candidates.set(attachment.previewImageUrl, {
|
|
309
|
-
mimeType: null,
|
|
310
|
-
});
|
|
311
|
-
}
|
|
312
|
-
const out = [];
|
|
313
|
-
for (const [url, candidate] of candidates.entries()) {
|
|
314
|
-
try {
|
|
315
|
-
const filePathHint = resolveFilePathHint({ sourceUrl: url, preferredName: candidate.fileName });
|
|
316
|
-
const fetched = await params.core.channel.media.fetchRemoteMedia({
|
|
317
|
-
url,
|
|
318
|
-
maxBytes: params.maxBytes,
|
|
319
|
-
...(filePathHint ? { filePathHint } : {}),
|
|
320
|
-
});
|
|
321
|
-
const saved = await params.core.channel.media.saveMediaBuffer(fetched.buffer, fetched.contentType ?? candidate.mimeType ?? undefined, "inbound", params.maxBytes, fetched.fileName ?? candidate.fileName ?? undefined);
|
|
322
|
-
const contentType = saved.contentType ?? fetched.contentType ?? candidate.mimeType ?? undefined;
|
|
323
|
-
out.push({
|
|
324
|
-
path: saved.path,
|
|
325
|
-
...(contentType ? { contentType } : {}),
|
|
326
|
-
});
|
|
327
|
-
}
|
|
328
|
-
catch (err) {
|
|
329
|
-
params.log?.warn?.(`inline: failed to download inbound media ${url}: ${String(err)}`);
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
return out;
|
|
333
|
-
}
|
|
334
|
-
async function buildHistoryContext(params) {
|
|
335
|
-
const cachedReplyToBot = params.replyToMsgId != null &&
|
|
336
|
-
hasBotMessageId(params.botMessageIdsByChat, params.chatId, params.replyToMsgId);
|
|
337
|
-
let repliedToBot = cachedReplyToBot;
|
|
338
|
-
let replyToSenderId = null;
|
|
339
|
-
let foundReplyTargetInHistory = false;
|
|
340
|
-
const lines = [];
|
|
341
|
-
const attachmentLines = [];
|
|
342
|
-
const entityLines = [];
|
|
343
|
-
if (params.historyLimit > 0) {
|
|
344
|
-
const messages = await loadChatHistoryMessages({
|
|
345
|
-
client: params.client,
|
|
346
|
-
chatId: params.chatId,
|
|
347
|
-
offsetId: params.currentMessageId,
|
|
348
|
-
limit: params.historyLimit,
|
|
349
|
-
});
|
|
350
|
-
if (messages) {
|
|
351
|
-
for (const item of messages) {
|
|
352
|
-
if (item.fromId === params.meId) {
|
|
353
|
-
rememberBotMessageId(params.botMessageIdsByChat, params.chatId, item.id);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
const sortedMessages = messages
|
|
357
|
-
.filter((item) => item.id !== params.currentMessageId)
|
|
358
|
-
.sort((a, b) => {
|
|
359
|
-
const byDate = Number(a.date - b.date);
|
|
360
|
-
if (byDate !== 0)
|
|
361
|
-
return byDate;
|
|
362
|
-
if (a.id === b.id)
|
|
363
|
-
return 0;
|
|
364
|
-
return a.id < b.id ? -1 : 1;
|
|
365
|
-
});
|
|
366
|
-
for (const item of sortedMessages) {
|
|
367
|
-
if (params.replyToMsgId != null && item.id === params.replyToMsgId) {
|
|
368
|
-
foundReplyTargetInHistory = true;
|
|
369
|
-
replyToSenderId = String(item.fromId);
|
|
370
|
-
repliedToBot = item.fromId === params.meId;
|
|
371
|
-
}
|
|
372
|
-
const content = summarizeInlineMessageContent(item);
|
|
373
|
-
const text = normalizeHistoryText(content.text);
|
|
374
|
-
if (!text)
|
|
375
|
-
continue;
|
|
376
|
-
const label = resolveHistorySenderLabel({
|
|
377
|
-
senderId: item.fromId,
|
|
378
|
-
meId: params.meId,
|
|
379
|
-
senderProfilesById: params.senderProfilesById,
|
|
380
|
-
});
|
|
381
|
-
const replySuffix = item.replyToMsgId != null ? ` ->${String(item.replyToMsgId)}` : "";
|
|
382
|
-
lines.push(`#${String(item.id)}${replySuffix} ${label}: ${text}`);
|
|
383
|
-
const attachmentText = normalizeHistoryText(content.attachmentText);
|
|
384
|
-
if (attachmentText) {
|
|
385
|
-
attachmentLines.push(`#${String(item.id)}${replySuffix} ${label}: ${attachmentText}`);
|
|
386
|
-
}
|
|
387
|
-
const entityText = normalizeHistoryText(content.entityText);
|
|
388
|
-
if (entityText) {
|
|
389
|
-
entityLines.push(`#${String(item.id)}${replySuffix} ${label}: ${entityText}`);
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
if (params.replyToMsgId != null && !foundReplyTargetInHistory) {
|
|
395
|
-
const replyTarget = await findChatMessageById({
|
|
396
|
-
client: params.client,
|
|
397
|
-
chatId: params.chatId,
|
|
398
|
-
messageId: params.replyToMsgId,
|
|
399
|
-
limit: REPLY_TARGET_LOOKUP_LIMIT,
|
|
400
|
-
meId: params.meId,
|
|
401
|
-
botMessageIdsByChat: params.botMessageIdsByChat,
|
|
402
|
-
});
|
|
403
|
-
if (replyTarget) {
|
|
404
|
-
replyToSenderId = String(replyTarget.fromId);
|
|
405
|
-
repliedToBot = replyTarget.fromId === params.meId;
|
|
406
|
-
}
|
|
407
|
-
else if (!cachedReplyToBot) {
|
|
408
|
-
repliedToBot = false;
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
if (!lines.length) {
|
|
412
|
-
return {
|
|
413
|
-
historyText: null,
|
|
414
|
-
attachmentText: attachmentLines.length ? attachmentLines.slice(-ATTACHMENT_CONTEXT_LIMIT).join("\n") : null,
|
|
415
|
-
entityText: entityLines.length ? entityLines.slice(-ATTACHMENT_CONTEXT_LIMIT).join("\n") : null,
|
|
416
|
-
repliedToBot,
|
|
417
|
-
replyToSenderId,
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
return {
|
|
421
|
-
historyText: `Recent thread messages (oldest -> newest):\n${lines.join("\n")}`,
|
|
422
|
-
attachmentText: attachmentLines.length
|
|
423
|
-
? `Recent media/attachments:\n${attachmentLines.slice(-ATTACHMENT_CONTEXT_LIMIT).join("\n")}`
|
|
424
|
-
: null,
|
|
425
|
-
entityText: entityLines.length
|
|
426
|
-
? `Recent message entities:\n${entityLines.slice(-ATTACHMENT_CONTEXT_LIMIT).join("\n")}`
|
|
427
|
-
: null,
|
|
428
|
-
repliedToBot,
|
|
429
|
-
replyToSenderId,
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
export async function monitorInlineProvider(params) {
|
|
433
|
-
const { cfg, account, runtime, abortSignal, log, statusSink } = params;
|
|
434
|
-
const core = getInlineRuntime();
|
|
435
|
-
if (!account.configured || !account.baseUrl) {
|
|
436
|
-
throw new Error(`Inline not configured for account "${account.accountId}" (missing baseUrl or token)`);
|
|
437
|
-
}
|
|
438
|
-
const token = await resolveInlineToken(account);
|
|
439
|
-
const stateDir = core.state.resolveStateDir();
|
|
440
|
-
const statePath = path.join(stateDir, "channels", "inline", `${account.accountId}.json`);
|
|
441
|
-
await mkdir(path.dirname(statePath), { recursive: true });
|
|
442
|
-
const sdkLog = {
|
|
443
|
-
debug: (msg) => log?.debug?.(msg),
|
|
444
|
-
info: (msg) => log?.info(msg),
|
|
445
|
-
warn: (msg) => log?.warn(msg),
|
|
446
|
-
error: (msg) => log?.error(msg),
|
|
447
|
-
};
|
|
448
|
-
const client = new InlineSdkClient({
|
|
449
|
-
baseUrl: account.baseUrl,
|
|
450
|
-
token,
|
|
451
|
-
logger: sdkLog,
|
|
452
|
-
state: new JsonFileStateStore(statePath),
|
|
453
|
-
});
|
|
454
|
-
await client.connect(abortSignal);
|
|
455
|
-
const meResult = await client.invokeRaw(Method.GET_ME, {
|
|
456
|
-
oneofKind: "getMe",
|
|
457
|
-
getMe: {},
|
|
458
|
-
});
|
|
459
|
-
if (meResult.oneofKind !== "getMe" || !meResult.getMe.user) {
|
|
460
|
-
throw new Error("inline getMe: missing user");
|
|
461
|
-
}
|
|
462
|
-
const meId = meResult.getMe.user.id;
|
|
463
|
-
const botUsername = normalizeInlineUsername(meResult.getMe.user.username)?.toLowerCase();
|
|
464
|
-
log?.info(`[${account.accountId}] inline connected (me=${String(meId)})`);
|
|
465
|
-
const chatCache = new Map();
|
|
466
|
-
const senderProfilesById = new Map();
|
|
467
|
-
const botMessageIdsByChat = new Map();
|
|
468
|
-
const hydratedParticipantChats = new Set();
|
|
469
|
-
const participantFetches = new Map();
|
|
470
|
-
const inboundMediaMaxBytes = resolveInlineMediaMaxBytes({ cfg, account });
|
|
471
|
-
const hydrateChatParticipants = async (chatId) => {
|
|
472
|
-
const chatKey = String(chatId);
|
|
473
|
-
if (hydratedParticipantChats.has(chatKey))
|
|
474
|
-
return;
|
|
475
|
-
const existing = participantFetches.get(chatKey);
|
|
476
|
-
if (existing)
|
|
477
|
-
return existing;
|
|
478
|
-
const run = (async () => {
|
|
479
|
-
const result = await client.invokeRaw(Method.GET_CHAT_PARTICIPANTS, {
|
|
480
|
-
oneofKind: "getChatParticipants",
|
|
481
|
-
getChatParticipants: { chatId },
|
|
482
|
-
});
|
|
483
|
-
if (result.oneofKind !== "getChatParticipants")
|
|
484
|
-
return;
|
|
485
|
-
for (const user of result.getChatParticipants.users ?? []) {
|
|
486
|
-
const userId = String(user.id);
|
|
487
|
-
if (!userId)
|
|
488
|
-
continue;
|
|
489
|
-
const nextName = buildInlineSenderName({ firstName: user.firstName, lastName: user.lastName });
|
|
490
|
-
const nextUsername = normalizeInlineUsername(user.username);
|
|
491
|
-
const previous = senderProfilesById.get(userId);
|
|
492
|
-
const mergedName = nextName ?? previous?.name;
|
|
493
|
-
const mergedUsername = nextUsername ?? previous?.username;
|
|
494
|
-
senderProfilesById.set(userId, {
|
|
495
|
-
...(mergedName ? { name: mergedName } : {}),
|
|
496
|
-
...(mergedUsername ? { username: mergedUsername } : {}),
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
hydratedParticipantChats.add(chatKey);
|
|
500
|
-
})()
|
|
501
|
-
.catch((err) => {
|
|
502
|
-
statusSink?.({ lastError: `getChatParticipants failed: ${String(err)}` });
|
|
503
|
-
})
|
|
504
|
-
.finally(() => {
|
|
505
|
-
participantFetches.delete(chatKey);
|
|
506
|
-
});
|
|
507
|
-
participantFetches.set(chatKey, run);
|
|
508
|
-
await run;
|
|
509
|
-
};
|
|
510
|
-
const loop = (async () => {
|
|
511
|
-
try {
|
|
512
|
-
for await (const event of client.events()) {
|
|
513
|
-
if (abortSignal.aborted)
|
|
514
|
-
break;
|
|
515
|
-
let msg;
|
|
516
|
-
let rawBody = "";
|
|
517
|
-
let currentAttachmentText = null;
|
|
518
|
-
let currentEntityText = null;
|
|
519
|
-
let reactionEvent = null;
|
|
520
|
-
if (event.kind === "message.new") {
|
|
521
|
-
msg = event.message;
|
|
522
|
-
const content = summarizeInlineMessageContent(msg);
|
|
523
|
-
rawBody = buildInlineInboundBodyText(content);
|
|
524
|
-
currentAttachmentText = content.attachmentText || null;
|
|
525
|
-
currentEntityText = content.entityText || null;
|
|
526
|
-
if (!rawBody)
|
|
527
|
-
continue;
|
|
528
|
-
// Ignore echoes / our own outbound messages.
|
|
529
|
-
if (msg.out || msg.fromId === meId)
|
|
530
|
-
continue;
|
|
531
|
-
}
|
|
532
|
-
else if (event.kind === "reaction.add") {
|
|
533
|
-
if (event.reaction.userId === meId)
|
|
534
|
-
continue;
|
|
535
|
-
const onBotMessage = await isReactionTargetBotMessage({
|
|
536
|
-
client,
|
|
537
|
-
chatId: event.chatId,
|
|
538
|
-
messageId: event.reaction.messageId,
|
|
539
|
-
meId,
|
|
540
|
-
botMessageIdsByChat,
|
|
541
|
-
}).catch((err) => {
|
|
542
|
-
statusSink?.({ lastError: `getChatHistory (reaction target) failed: ${String(err)}` });
|
|
543
|
-
return false;
|
|
544
|
-
});
|
|
545
|
-
if (!onBotMessage)
|
|
546
|
-
continue;
|
|
547
|
-
reactionEvent = {
|
|
548
|
-
action: "added",
|
|
549
|
-
emoji: event.reaction.emoji,
|
|
550
|
-
targetMessageId: event.reaction.messageId,
|
|
551
|
-
};
|
|
552
|
-
msg = {
|
|
553
|
-
id: event.reaction.messageId,
|
|
554
|
-
chatId: event.chatId,
|
|
555
|
-
date: event.date,
|
|
556
|
-
fromId: event.reaction.userId,
|
|
557
|
-
message: "",
|
|
558
|
-
out: false,
|
|
559
|
-
mentioned: false,
|
|
560
|
-
replyToMsgId: event.reaction.messageId,
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
else if (event.kind === "reaction.delete") {
|
|
564
|
-
if (event.userId === meId)
|
|
565
|
-
continue;
|
|
566
|
-
const onBotMessage = await isReactionTargetBotMessage({
|
|
567
|
-
client,
|
|
568
|
-
chatId: event.chatId,
|
|
569
|
-
messageId: event.messageId,
|
|
570
|
-
meId,
|
|
571
|
-
botMessageIdsByChat,
|
|
572
|
-
}).catch((err) => {
|
|
573
|
-
statusSink?.({ lastError: `getChatHistory (reaction target) failed: ${String(err)}` });
|
|
574
|
-
return false;
|
|
575
|
-
});
|
|
576
|
-
if (!onBotMessage)
|
|
577
|
-
continue;
|
|
578
|
-
reactionEvent = {
|
|
579
|
-
action: "removed",
|
|
580
|
-
emoji: event.emoji,
|
|
581
|
-
targetMessageId: event.messageId,
|
|
582
|
-
};
|
|
583
|
-
msg = {
|
|
584
|
-
id: event.messageId,
|
|
585
|
-
chatId: event.chatId,
|
|
586
|
-
date: event.date,
|
|
587
|
-
fromId: event.userId,
|
|
588
|
-
message: "",
|
|
589
|
-
out: false,
|
|
590
|
-
mentioned: false,
|
|
591
|
-
replyToMsgId: event.messageId,
|
|
592
|
-
};
|
|
593
|
-
}
|
|
594
|
-
else {
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
const chatId = event.chatId;
|
|
598
|
-
statusSink?.({ lastInboundAt: Date.now() });
|
|
599
|
-
let chatInfo;
|
|
600
|
-
try {
|
|
601
|
-
chatInfo = await resolveChatInfo(client, chatCache, chatId);
|
|
602
|
-
}
|
|
603
|
-
catch (err) {
|
|
604
|
-
// Default conservative behavior if metadata fetch fails.
|
|
605
|
-
chatInfo = { kind: "group", title: null };
|
|
606
|
-
statusSink?.({ lastError: `getChat failed: ${String(err)}` });
|
|
607
|
-
}
|
|
608
|
-
const isGroup = chatInfo.kind !== "direct";
|
|
609
|
-
const senderId = String(msg.fromId);
|
|
610
|
-
await hydrateChatParticipants(chatId);
|
|
611
|
-
const senderProfile = senderProfilesById.get(senderId);
|
|
612
|
-
const senderUsername = senderProfile?.username;
|
|
613
|
-
const senderName = senderProfile?.name ?? (!isGroup ? chatInfo.title ?? undefined : undefined);
|
|
614
|
-
if (reactionEvent) {
|
|
615
|
-
const actor = senderUsername != null && senderUsername.length > 0
|
|
616
|
-
? `@${senderUsername}`
|
|
617
|
-
: senderName ?? `user:${senderId}`;
|
|
618
|
-
const emoji = reactionEvent.emoji.trim() || "a reaction";
|
|
619
|
-
const messageId = String(reactionEvent.targetMessageId);
|
|
620
|
-
if (reactionEvent.action === "added") {
|
|
621
|
-
rawBody = `${actor} reacted with ${emoji} to your message #${messageId}`;
|
|
622
|
-
}
|
|
623
|
-
else {
|
|
624
|
-
rawBody = `${actor} removed ${emoji} from your message #${messageId}`;
|
|
625
|
-
}
|
|
626
|
-
}
|
|
627
|
-
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
|
628
|
-
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
|
629
|
-
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
|
630
|
-
const configAllowFrom = normalizeAllowlist(account.config.allowFrom);
|
|
631
|
-
const configGroupAllowFrom = normalizeAllowlist(account.config.groupAllowFrom);
|
|
632
|
-
const storeAllowFrom = await core.channel.pairing.readAllowFromStore(CHANNEL_ID).catch(() => []);
|
|
633
|
-
const storeAllowList = normalizeAllowlist(storeAllowFrom);
|
|
634
|
-
const effectiveAllowFrom = [...configAllowFrom, ...storeAllowList].filter(Boolean);
|
|
635
|
-
const effectiveGroupAllowFrom = [
|
|
636
|
-
...(configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom),
|
|
637
|
-
...storeAllowList,
|
|
638
|
-
].filter(Boolean);
|
|
639
|
-
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
|
|
640
|
-
cfg,
|
|
641
|
-
surface: CHANNEL_ID,
|
|
642
|
-
});
|
|
643
|
-
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
|
644
|
-
const allowForCommands = isGroup ? effectiveGroupAllowFrom : effectiveAllowFrom;
|
|
645
|
-
const senderAllowedForCommands = allowlistMatch({ allowFrom: allowForCommands, senderId });
|
|
646
|
-
const hasControlCommand = core.channel.text.hasControlCommand(rawBody, cfg, botUsername ? { botUsername } : undefined);
|
|
647
|
-
const commandGate = resolveControlCommandGate({
|
|
648
|
-
useAccessGroups,
|
|
649
|
-
authorizers: [{ configured: allowForCommands.length > 0, allowed: senderAllowedForCommands }],
|
|
650
|
-
allowTextCommands,
|
|
651
|
-
hasControlCommand,
|
|
652
|
-
});
|
|
653
|
-
const commandAuthorized = commandGate.commandAuthorized;
|
|
654
|
-
if (isGroup) {
|
|
655
|
-
if (groupPolicy === "disabled") {
|
|
656
|
-
log?.info(`[${account.accountId}] inline: drop group chat=${String(chatId)} (groupPolicy=disabled)`);
|
|
657
|
-
continue;
|
|
658
|
-
}
|
|
659
|
-
if (groupPolicy === "allowlist") {
|
|
660
|
-
const allowed = allowlistMatch({ allowFrom: effectiveGroupAllowFrom, senderId });
|
|
661
|
-
if (!allowed) {
|
|
662
|
-
log?.info(`[${account.accountId}] inline: drop group sender=${senderId} (groupPolicy=allowlist)`);
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
else {
|
|
668
|
-
if (dmPolicy === "disabled") {
|
|
669
|
-
log?.info(`[${account.accountId}] inline: drop DM sender=${senderId} (dmPolicy=disabled)`);
|
|
670
|
-
continue;
|
|
671
|
-
}
|
|
672
|
-
if (dmPolicy !== "open") {
|
|
673
|
-
const allowed = allowlistMatch({ allowFrom: effectiveAllowFrom, senderId });
|
|
674
|
-
if (!allowed) {
|
|
675
|
-
if (dmPolicy === "pairing") {
|
|
676
|
-
const { code, created } = await core.channel.pairing.upsertPairingRequest({
|
|
677
|
-
channel: CHANNEL_ID,
|
|
678
|
-
id: senderId,
|
|
679
|
-
meta: {},
|
|
680
|
-
// Pass adapter explicitly to avoid relying on registry lookup for plugin channels.
|
|
681
|
-
pairingAdapter: { idLabel: "inlineUserId", normalizeAllowEntry },
|
|
682
|
-
});
|
|
683
|
-
if (created) {
|
|
684
|
-
try {
|
|
685
|
-
await client.sendMessage({
|
|
686
|
-
chatId,
|
|
687
|
-
text: core.channel.pairing.buildPairingReply({
|
|
688
|
-
channel: CHANNEL_ID,
|
|
689
|
-
idLine: `Your Inline user id: ${senderId}`,
|
|
690
|
-
code,
|
|
691
|
-
}),
|
|
692
|
-
});
|
|
693
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
694
|
-
}
|
|
695
|
-
catch (err) {
|
|
696
|
-
runtime.error?.(`inline: pairing reply failed for ${senderId}: ${String(err)}`);
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
log?.info(`[${account.accountId}] inline: drop DM sender=${senderId} (dmPolicy=${dmPolicy})`);
|
|
701
|
-
continue;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
}
|
|
705
|
-
if (commandGate.shouldBlock) {
|
|
706
|
-
logInboundDrop({
|
|
707
|
-
log: (m) => runtime.log?.(m),
|
|
708
|
-
channel: CHANNEL_ID,
|
|
709
|
-
reason: "control command (unauthorized)",
|
|
710
|
-
target: senderId,
|
|
711
|
-
});
|
|
712
|
-
continue;
|
|
713
|
-
}
|
|
714
|
-
const route = core.channel.routing.resolveAgentRoute({
|
|
715
|
-
cfg,
|
|
716
|
-
channel: CHANNEL_ID,
|
|
717
|
-
accountId: account.accountId,
|
|
718
|
-
peer: {
|
|
719
|
-
kind: isGroup ? "group" : "direct",
|
|
720
|
-
// DM sessions should be stable per sender. Group sessions should be stable per chat.
|
|
721
|
-
id: isGroup ? String(chatId) : senderId,
|
|
722
|
-
},
|
|
723
|
-
});
|
|
724
|
-
const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg, route.agentId);
|
|
725
|
-
const wasMentioned = typeof msg.mentioned === "boolean"
|
|
726
|
-
? msg.mentioned
|
|
727
|
-
: mentionRegexes.length
|
|
728
|
-
? core.channel.mentions.matchesMentionPatterns(rawBody, mentionRegexes)
|
|
729
|
-
: false;
|
|
730
|
-
const historyLimit = resolveHistoryLimit({
|
|
731
|
-
isGroup,
|
|
732
|
-
historyLimit: account.config.historyLimit,
|
|
733
|
-
dmHistoryLimit: account.config.dmHistoryLimit,
|
|
734
|
-
});
|
|
735
|
-
const historyContext = await buildHistoryContext({
|
|
736
|
-
client,
|
|
737
|
-
chatId,
|
|
738
|
-
currentMessageId: msg.id,
|
|
739
|
-
replyToMsgId: msg.replyToMsgId,
|
|
740
|
-
senderProfilesById,
|
|
741
|
-
meId,
|
|
742
|
-
historyLimit,
|
|
743
|
-
botMessageIdsByChat,
|
|
744
|
-
}).catch((err) => {
|
|
745
|
-
statusSink?.({ lastError: `getChatHistory failed: ${String(err)}` });
|
|
746
|
-
return { historyText: null, attachmentText: null, entityText: null, repliedToBot: false, replyToSenderId: null };
|
|
747
|
-
});
|
|
748
|
-
const implicitMention = (reactionEvent != null && isGroup) ||
|
|
749
|
-
(isGroup &&
|
|
750
|
-
(account.config.replyToBotWithoutMention ?? false) &&
|
|
751
|
-
msg.replyToMsgId != null &&
|
|
752
|
-
historyContext.repliedToBot);
|
|
753
|
-
const requireMention = isGroup
|
|
754
|
-
? resolveInlineGroupRequireMention({
|
|
755
|
-
cfg,
|
|
756
|
-
groupId: String(chatId),
|
|
757
|
-
accountId: account.accountId,
|
|
758
|
-
requireMentionDefault: account.config.requireMention ?? false,
|
|
759
|
-
})
|
|
760
|
-
: false;
|
|
761
|
-
const mentionGate = resolveMentionGatingWithBypass({
|
|
762
|
-
isGroup,
|
|
763
|
-
requireMention,
|
|
764
|
-
canDetectMention: typeof msg.mentioned === "boolean" || mentionRegexes.length > 0,
|
|
765
|
-
wasMentioned,
|
|
766
|
-
implicitMention,
|
|
767
|
-
allowTextCommands,
|
|
768
|
-
hasControlCommand,
|
|
769
|
-
commandAuthorized,
|
|
770
|
-
});
|
|
771
|
-
if (isGroup && mentionGate.shouldSkip) {
|
|
772
|
-
runtime.log?.(`inline: drop group chat ${String(chatId)} (no mention)`);
|
|
773
|
-
continue;
|
|
774
|
-
}
|
|
775
|
-
const inboundMedia = reactionEvent
|
|
776
|
-
? []
|
|
777
|
-
: await resolveInlineInboundMedia({
|
|
778
|
-
core,
|
|
779
|
-
message: msg,
|
|
780
|
-
maxBytes: inboundMediaMaxBytes,
|
|
781
|
-
...(log ? { log } : {}),
|
|
782
|
-
});
|
|
783
|
-
const timestamp = Number(msg.date) * 1000;
|
|
784
|
-
const fromLabel = isGroup ? `chat:${chatInfo.title ?? String(chatId)}` : `user:${senderId}`;
|
|
785
|
-
const storePath = core.channel.session.resolveStorePath(cfg.session?.store, { agentId: route.agentId });
|
|
786
|
-
const envelopeOptions = core.channel.reply.resolveEnvelopeFormatOptions(cfg);
|
|
787
|
-
const previousTimestamp = core.channel.session.readSessionUpdatedAt({ storePath, sessionKey: route.sessionKey });
|
|
788
|
-
const combinedBody = [
|
|
789
|
-
historyContext.historyText,
|
|
790
|
-
historyContext.attachmentText,
|
|
791
|
-
historyContext.entityText,
|
|
792
|
-
INLINE_FORMATTING_NOTE,
|
|
793
|
-
`Current message:\n${rawBody}`,
|
|
794
|
-
currentAttachmentText && currentAttachmentText !== rawBody
|
|
795
|
-
? `Current media/attachments:\n${currentAttachmentText}`
|
|
796
|
-
: null,
|
|
797
|
-
currentEntityText ? `Current message entities:\n${currentEntityText}` : null,
|
|
798
|
-
]
|
|
799
|
-
.filter(Boolean)
|
|
800
|
-
.join("\n\n");
|
|
801
|
-
const body = core.channel.reply.formatAgentEnvelope({
|
|
802
|
-
channel: "Inline",
|
|
803
|
-
from: fromLabel,
|
|
804
|
-
timestamp,
|
|
805
|
-
...(previousTimestamp != null ? { previousTimestamp } : {}),
|
|
806
|
-
envelope: envelopeOptions,
|
|
807
|
-
body: combinedBody || rawBody,
|
|
808
|
-
});
|
|
809
|
-
const commandBody = normalizeInlineCommandBody(rawBody, botUsername);
|
|
810
|
-
const ctxPayload = core.channel.reply.finalizeInboundContext({
|
|
811
|
-
Body: body,
|
|
812
|
-
RawBody: rawBody,
|
|
813
|
-
CommandBody: commandBody,
|
|
814
|
-
From: isGroup ? `inline:chat:${String(chatId)}` : `inline:${senderId}`,
|
|
815
|
-
To: `inline:${String(chatId)}`,
|
|
816
|
-
SessionKey: route.sessionKey,
|
|
817
|
-
AccountId: route.accountId,
|
|
818
|
-
ChatType: isGroup ? "group" : "direct",
|
|
819
|
-
ConversationLabel: fromLabel,
|
|
820
|
-
...(isGroup ? { GroupSubject: chatInfo.title ?? String(chatId) } : {}),
|
|
821
|
-
SenderId: senderId,
|
|
822
|
-
...(senderName ? { SenderName: senderName } : {}),
|
|
823
|
-
...(senderUsername ? { SenderUsername: senderUsername } : {}),
|
|
824
|
-
Provider: CHANNEL_ID,
|
|
825
|
-
Surface: CHANNEL_ID,
|
|
826
|
-
MessageSid: String(msg.id),
|
|
827
|
-
...(msg.replyToMsgId != null ? { ReplyToId: String(msg.replyToMsgId) } : {}),
|
|
828
|
-
...(historyContext.replyToSenderId != null ? { ReplyToSenderId: historyContext.replyToSenderId } : {}),
|
|
829
|
-
...(msg.replyToMsgId != null ? { ReplyToWasBot: historyContext.repliedToBot } : {}),
|
|
830
|
-
...buildInlineInboundMediaPayload(inboundMedia),
|
|
831
|
-
Timestamp: timestamp || Date.now(),
|
|
832
|
-
WasMentioned: mentionGate.effectiveWasMentioned,
|
|
833
|
-
CommandAuthorized: commandAuthorized,
|
|
834
|
-
OriginatingChannel: CHANNEL_ID,
|
|
835
|
-
OriginatingTo: `inline:${String(chatId)}`,
|
|
836
|
-
});
|
|
837
|
-
await core.channel.session.recordInboundSession({
|
|
838
|
-
storePath,
|
|
839
|
-
sessionKey: ctxPayload.SessionKey ?? route.sessionKey,
|
|
840
|
-
ctx: ctxPayload,
|
|
841
|
-
...(!isGroup
|
|
842
|
-
? {
|
|
843
|
-
updateLastRoute: {
|
|
844
|
-
sessionKey: route.mainSessionKey,
|
|
845
|
-
channel: CHANNEL_ID,
|
|
846
|
-
to: `inline:${String(chatId)}`,
|
|
847
|
-
accountId: route.accountId,
|
|
848
|
-
},
|
|
849
|
-
}
|
|
850
|
-
: {}),
|
|
851
|
-
onRecordError: (err) => runtime.error?.(`inline: failed updating session meta: ${String(err)}`),
|
|
852
|
-
});
|
|
853
|
-
const prefixConfig = (typeof createReplyPrefixOptions === "function"
|
|
854
|
-
? createReplyPrefixOptions({
|
|
855
|
-
cfg,
|
|
856
|
-
agentId: route.agentId,
|
|
857
|
-
channel: CHANNEL_ID,
|
|
858
|
-
accountId: account.accountId,
|
|
859
|
-
})
|
|
860
|
-
: {});
|
|
861
|
-
const onModelSelected = typeof prefixConfig.onModelSelected === "function"
|
|
862
|
-
? prefixConfig.onModelSelected
|
|
863
|
-
: undefined;
|
|
864
|
-
const { onModelSelected: _ignoredOnModelSelected, ...prefixOptions } = prefixConfig;
|
|
865
|
-
const typingCallbacks = typeof createTypingCallbacks === "function"
|
|
866
|
-
? createTypingCallbacks({
|
|
867
|
-
start: () => client.sendTyping({ chatId, typing: true }),
|
|
868
|
-
stop: () => client.sendTyping({ chatId, typing: false }),
|
|
869
|
-
onStartError: (err) => runtime.error?.(`inline typing start failed: ${String(err)}`),
|
|
870
|
-
onStopError: (err) => runtime.error?.(`inline typing stop failed: ${String(err)}`),
|
|
871
|
-
})
|
|
872
|
-
: {};
|
|
873
|
-
const parseMarkdown = account.config.parseMarkdown ?? true;
|
|
874
|
-
const streamViaEditMessage = account.config.streamViaEditMessage === true;
|
|
875
|
-
const defaultReplyToMsgId = isGroup && msg.replyToMsgId != null ? msg.id : undefined;
|
|
876
|
-
const disableBlockStreaming = streamViaEditMessage
|
|
877
|
-
? true
|
|
878
|
-
: typeof account.config.blockStreaming === "boolean"
|
|
879
|
-
? !account.config.blockStreaming
|
|
880
|
-
: undefined;
|
|
881
|
-
const editStreamState = {
|
|
882
|
-
messageId: null,
|
|
883
|
-
accumulatedText: "",
|
|
884
|
-
lastPartialText: "",
|
|
885
|
-
finalTextAccumulator: "",
|
|
886
|
-
failed: false,
|
|
887
|
-
opChain: Promise.resolve(),
|
|
888
|
-
};
|
|
889
|
-
const replyOptions = {
|
|
890
|
-
...(onModelSelected ? { onModelSelected } : {}),
|
|
891
|
-
blockReplyTimeoutMs: 25_000,
|
|
892
|
-
...(streamViaEditMessage
|
|
893
|
-
? {
|
|
894
|
-
onPartialReply: async (payload) => {
|
|
895
|
-
if (editStreamState.failed)
|
|
896
|
-
return;
|
|
897
|
-
if ((payload.mediaUrls?.length ?? 0) > 0)
|
|
898
|
-
return;
|
|
899
|
-
const partialText = typeof payload.text === "string" ? payload.text : "";
|
|
900
|
-
if (!partialText || partialText === editStreamState.lastPartialText)
|
|
901
|
-
return;
|
|
902
|
-
editStreamState.lastPartialText = partialText;
|
|
903
|
-
const nextText = rewriteNumericMentionsToUsernames(extractCompleteParagraphText(partialText), senderProfilesById).trim();
|
|
904
|
-
if (!nextText || nextText === editStreamState.accumulatedText)
|
|
905
|
-
return;
|
|
906
|
-
editStreamState.opChain = editStreamState.opChain.then(async () => {
|
|
907
|
-
if (editStreamState.failed)
|
|
908
|
-
return;
|
|
909
|
-
if (!nextText || nextText === editStreamState.accumulatedText)
|
|
910
|
-
return;
|
|
911
|
-
try {
|
|
912
|
-
if (editStreamState.messageId == null) {
|
|
913
|
-
const sent = await client.sendMessage({
|
|
914
|
-
chatId,
|
|
915
|
-
text: nextText,
|
|
916
|
-
...(defaultReplyToMsgId != null ? { replyToMsgId: defaultReplyToMsgId } : {}),
|
|
917
|
-
parseMarkdown,
|
|
918
|
-
});
|
|
919
|
-
if (sent.messageId == null) {
|
|
920
|
-
throw new Error("inline edit stream: sendMessage returned no messageId");
|
|
921
|
-
}
|
|
922
|
-
editStreamState.messageId = sent.messageId;
|
|
923
|
-
rememberBotMessageId(botMessageIdsByChat, chatId, sent.messageId);
|
|
924
|
-
}
|
|
925
|
-
else {
|
|
926
|
-
const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
|
|
927
|
-
oneofKind: "editMessage",
|
|
928
|
-
editMessage: {
|
|
929
|
-
messageId: editStreamState.messageId,
|
|
930
|
-
peerId: buildChatPeer(chatId),
|
|
931
|
-
text: nextText,
|
|
932
|
-
parseMarkdown,
|
|
933
|
-
},
|
|
934
|
-
});
|
|
935
|
-
if (result.oneofKind !== "editMessage") {
|
|
936
|
-
throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
editStreamState.accumulatedText = nextText;
|
|
940
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
941
|
-
}
|
|
942
|
-
catch (error) {
|
|
943
|
-
editStreamState.failed = true;
|
|
944
|
-
runtime.error?.(`inline edit stream failed: ${String(error)}`);
|
|
945
|
-
}
|
|
946
|
-
});
|
|
947
|
-
await editStreamState.opChain;
|
|
948
|
-
},
|
|
949
|
-
}
|
|
950
|
-
: {}),
|
|
951
|
-
...(typeof disableBlockStreaming === "boolean" ? { disableBlockStreaming } : {}),
|
|
952
|
-
};
|
|
953
|
-
await core.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
|
|
954
|
-
ctx: ctxPayload,
|
|
955
|
-
cfg,
|
|
956
|
-
dispatcherOptions: {
|
|
957
|
-
...prefixOptions,
|
|
958
|
-
...typingCallbacks,
|
|
959
|
-
deliver: async (payload) => {
|
|
960
|
-
const rawText = payload.text ?? "";
|
|
961
|
-
const mediaList = payload.mediaUrls?.length
|
|
962
|
-
? payload.mediaUrls
|
|
963
|
-
: payload.mediaUrl
|
|
964
|
-
? [payload.mediaUrl]
|
|
965
|
-
: [];
|
|
966
|
-
const outboundText = rewriteNumericMentionsToUsernames(rawText, senderProfilesById);
|
|
967
|
-
let replyToMsgId;
|
|
968
|
-
if (payload.replyToId != null) {
|
|
969
|
-
try {
|
|
970
|
-
replyToMsgId = BigInt(payload.replyToId);
|
|
971
|
-
}
|
|
972
|
-
catch {
|
|
973
|
-
// ignore
|
|
974
|
-
}
|
|
975
|
-
}
|
|
976
|
-
// Keep reply chains threaded when inbound is a reply in group chats.
|
|
977
|
-
if (replyToMsgId == null && isGroup && msg.replyToMsgId != null) {
|
|
978
|
-
replyToMsgId = msg.id;
|
|
979
|
-
}
|
|
980
|
-
const rememberSent = (messageId) => {
|
|
981
|
-
if (messageId != null) {
|
|
982
|
-
rememberBotMessageId(botMessageIdsByChat, chatId, messageId);
|
|
983
|
-
}
|
|
984
|
-
};
|
|
985
|
-
const sendTextFallback = async (text, includeReplyTo) => {
|
|
986
|
-
if (!text.trim())
|
|
987
|
-
return;
|
|
988
|
-
const sent = await client.sendMessage({
|
|
989
|
-
chatId,
|
|
990
|
-
text,
|
|
991
|
-
...(includeReplyTo && replyToMsgId != null ? { replyToMsgId } : {}),
|
|
992
|
-
parseMarkdown,
|
|
993
|
-
});
|
|
994
|
-
rememberSent(sent.messageId);
|
|
995
|
-
};
|
|
996
|
-
const updateStreamedMessage = async (text) => {
|
|
997
|
-
await editStreamState.opChain;
|
|
998
|
-
if (editStreamState.messageId == null)
|
|
999
|
-
return false;
|
|
1000
|
-
const nextText = text.trim();
|
|
1001
|
-
if (!nextText)
|
|
1002
|
-
return true;
|
|
1003
|
-
if (!editStreamState.failed && nextText === editStreamState.accumulatedText)
|
|
1004
|
-
return true;
|
|
1005
|
-
const result = await client.invokeRaw(Method.EDIT_MESSAGE, {
|
|
1006
|
-
oneofKind: "editMessage",
|
|
1007
|
-
editMessage: {
|
|
1008
|
-
messageId: editStreamState.messageId,
|
|
1009
|
-
peerId: buildChatPeer(chatId),
|
|
1010
|
-
text: nextText,
|
|
1011
|
-
parseMarkdown,
|
|
1012
|
-
},
|
|
1013
|
-
});
|
|
1014
|
-
if (result.oneofKind !== "editMessage") {
|
|
1015
|
-
throw new Error(`inline edit stream: expected editMessage result, got ${String(result.oneofKind)}`);
|
|
1016
|
-
}
|
|
1017
|
-
editStreamState.accumulatedText = nextText;
|
|
1018
|
-
editStreamState.lastPartialText = nextText;
|
|
1019
|
-
editStreamState.failed = false;
|
|
1020
|
-
return true;
|
|
1021
|
-
};
|
|
1022
|
-
if (mediaList.length === 0) {
|
|
1023
|
-
if (!outboundText.trim())
|
|
1024
|
-
return;
|
|
1025
|
-
if (streamViaEditMessage && editStreamState.messageId != null) {
|
|
1026
|
-
editStreamState.finalTextAccumulator += outboundText;
|
|
1027
|
-
await updateStreamedMessage(editStreamState.finalTextAccumulator);
|
|
1028
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
1029
|
-
return;
|
|
1030
|
-
}
|
|
1031
|
-
await sendTextFallback(outboundText, true);
|
|
1032
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
if (streamViaEditMessage && editStreamState.messageId != null && outboundText.trim()) {
|
|
1036
|
-
await updateStreamedMessage(outboundText);
|
|
1037
|
-
}
|
|
1038
|
-
for (let index = 0; index < mediaList.length; index++) {
|
|
1039
|
-
const mediaUrl = mediaList[index];
|
|
1040
|
-
if (!mediaUrl?.trim())
|
|
1041
|
-
continue;
|
|
1042
|
-
const isFirst = index === 0;
|
|
1043
|
-
const caption = isFirst && !(streamViaEditMessage && editStreamState.messageId != null) ? outboundText : "";
|
|
1044
|
-
try {
|
|
1045
|
-
const media = await uploadInlineMediaFromUrl({
|
|
1046
|
-
client,
|
|
1047
|
-
cfg,
|
|
1048
|
-
accountId: account.accountId,
|
|
1049
|
-
mediaUrl,
|
|
1050
|
-
});
|
|
1051
|
-
const sent = await client.sendMessage({
|
|
1052
|
-
chatId,
|
|
1053
|
-
...(caption ? { text: caption } : {}),
|
|
1054
|
-
media,
|
|
1055
|
-
...(isFirst && replyToMsgId != null ? { replyToMsgId } : {}),
|
|
1056
|
-
...(caption ? { parseMarkdown } : {}),
|
|
1057
|
-
});
|
|
1058
|
-
rememberSent(sent.messageId);
|
|
1059
|
-
}
|
|
1060
|
-
catch (error) {
|
|
1061
|
-
runtime.error?.(`inline media upload failed; falling back to url text (${String(error)})`);
|
|
1062
|
-
const fallbackText = caption
|
|
1063
|
-
? `${caption}\n\nAttachment: ${mediaUrl}`
|
|
1064
|
-
: `Attachment: ${mediaUrl}`;
|
|
1065
|
-
await sendTextFallback(fallbackText, isFirst);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
statusSink?.({ lastOutboundAt: Date.now() });
|
|
1069
|
-
},
|
|
1070
|
-
onError: (err, info) => runtime.error?.(`inline ${info.kind} reply failed: ${String(err)}`),
|
|
1071
|
-
},
|
|
1072
|
-
replyOptions,
|
|
1073
|
-
});
|
|
1074
|
-
}
|
|
1075
|
-
}
|
|
1076
|
-
catch (err) {
|
|
1077
|
-
statusSink?.({ lastError: String(err) });
|
|
1078
|
-
runtime.error?.(`inline monitor loop crashed: ${String(err)}`);
|
|
1079
|
-
}
|
|
1080
|
-
})();
|
|
1081
|
-
let stopPromise = null;
|
|
1082
|
-
const stop = async () => {
|
|
1083
|
-
if (stopPromise) {
|
|
1084
|
-
await stopPromise;
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
stopPromise = (async () => {
|
|
1088
|
-
await client.close().catch(() => { });
|
|
1089
|
-
await loop.catch(() => { });
|
|
1090
|
-
})();
|
|
1091
|
-
await stopPromise;
|
|
1092
|
-
};
|
|
1093
|
-
abortSignal.addEventListener("abort", () => {
|
|
1094
|
-
void stop();
|
|
1095
|
-
}, { once: true });
|
|
1096
|
-
return { stop, done: loop.catch(() => { }) };
|
|
1097
|
-
}
|
|
1098
|
-
//# sourceMappingURL=monitor.js.map
|