@ihazz/bitrix24 1.1.7 → 1.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -10
- package/dist/src/channel.d.ts +8 -1
- package/dist/src/channel.d.ts.map +1 -1
- package/dist/src/channel.js +227 -39
- package/dist/src/channel.js.map +1 -1
- package/dist/src/commands.d.ts +7 -4
- package/dist/src/commands.d.ts.map +1 -1
- package/dist/src/commands.js +57 -75
- package/dist/src/commands.js.map +1 -1
- package/dist/src/i18n.d.ts +33 -0
- package/dist/src/i18n.d.ts.map +1 -1
- package/dist/src/i18n.js +448 -0
- package/dist/src/i18n.js.map +1 -1
- package/package.json +1 -1
- package/src/channel.ts +307 -46
- package/src/commands.ts +75 -81
- package/src/i18n.ts +512 -0
package/src/channel.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createHash } from 'node:crypto';
|
|
2
|
-
import {
|
|
1
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
+
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { basename, dirname, join } from 'node:path';
|
|
3
4
|
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
4
5
|
import { listAccountIds, resolveAccount, getConfig } from './config.js';
|
|
5
6
|
import { Bitrix24Api } from './api.js';
|
|
@@ -11,6 +12,7 @@ import type { DownloadedMedia } from './media-service.js';
|
|
|
11
12
|
import { InboundHandler } from './inbound-handler.js';
|
|
12
13
|
import type { FetchCommandContext, FetchJoinChatContext, FetchReactionContext } from './inbound-handler.js';
|
|
13
14
|
import { PollingService } from './polling-service.js';
|
|
15
|
+
import { resolvePollingStateDir } from './state-paths.js';
|
|
14
16
|
import {
|
|
15
17
|
normalizeAllowEntry,
|
|
16
18
|
normalizeAllowList,
|
|
@@ -27,7 +29,12 @@ import { DEFAULT_AVATAR_BASE64 } from './bot-avatar.js';
|
|
|
27
29
|
import { Bitrix24ApiError, createVerboseLogger, defaultLogger, CHANNEL_PREFIX_RE } from './utils.js';
|
|
28
30
|
import { getBitrix24Runtime } from './runtime.js';
|
|
29
31
|
import type { ChannelPairingAdapter } from './runtime.js';
|
|
30
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
OPENCLAW_COMMANDS,
|
|
34
|
+
buildCommandsHelpText,
|
|
35
|
+
formatModelsCommandReply,
|
|
36
|
+
getCommandRegistrationPayload,
|
|
37
|
+
} from './commands.js';
|
|
31
38
|
import {
|
|
32
39
|
accessApproved,
|
|
33
40
|
accessDenied,
|
|
@@ -35,10 +42,13 @@ import {
|
|
|
35
42
|
groupPairingPending,
|
|
36
43
|
mediaDownloadFailed,
|
|
37
44
|
groupChatUnsupported,
|
|
45
|
+
newSessionReplyTexts,
|
|
38
46
|
onboardingMessage,
|
|
47
|
+
normalizeNewSessionReply,
|
|
39
48
|
ownerAndAllowedUsersOnly,
|
|
40
49
|
personalBotOwnerOnly,
|
|
41
50
|
replyGenerationFailed,
|
|
51
|
+
welcomeKeyboardLabels,
|
|
42
52
|
watchOwnerDmNotice,
|
|
43
53
|
} from './i18n.js';
|
|
44
54
|
import { HistoryCache } from './history-cache.js';
|
|
@@ -79,6 +89,9 @@ const CROSS_CHAT_HISTORY_LIMIT = 20;
|
|
|
79
89
|
const ACCESS_DENIED_REACTION = 'crossMark';
|
|
80
90
|
const BOT_MESSAGE_WATCH_REACTION = 'eyes';
|
|
81
91
|
const FORWARDED_CONTEXT_RANGE = 5;
|
|
92
|
+
const ACTIVE_SESSION_NAMESPACE_TTL_MS = 180 * 24 * 60 * 60 * 1000;
|
|
93
|
+
const ACTIVE_SESSION_NAMESPACE_MAX_KEYS = 1000;
|
|
94
|
+
const ACTIVE_SESSION_NAMESPACE_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
82
95
|
const REGISTERED_COMMANDS = new Set(OPENCLAW_COMMANDS.map((command) => command.command));
|
|
83
96
|
|
|
84
97
|
// ─── Emoji → B24 reaction code mapping ──────────────────────────────────
|
|
@@ -181,7 +194,7 @@ function buildTopicsBbCode(topics: string[] | undefined): string | undefined {
|
|
|
181
194
|
}
|
|
182
195
|
|
|
183
196
|
function formatQuoteTimestamp(timestamp: number | undefined, language: string | undefined): string {
|
|
184
|
-
const locale = (language ?? '
|
|
197
|
+
const locale = (language ?? 'en').toLowerCase().slice(0, 2);
|
|
185
198
|
const value = timestamp ?? Date.now();
|
|
186
199
|
|
|
187
200
|
try {
|
|
@@ -193,7 +206,7 @@ function formatQuoteTimestamp(timestamp: number | undefined, language: string |
|
|
|
193
206
|
minute: '2-digit',
|
|
194
207
|
}).format(new Date(value)).replace(',', '');
|
|
195
208
|
} catch {
|
|
196
|
-
return new Intl.DateTimeFormat('
|
|
209
|
+
return new Intl.DateTimeFormat('en', {
|
|
197
210
|
year: 'numeric',
|
|
198
211
|
month: '2-digit',
|
|
199
212
|
day: '2-digit',
|
|
@@ -203,6 +216,14 @@ function formatQuoteTimestamp(timestamp: number | undefined, language: string |
|
|
|
203
216
|
}
|
|
204
217
|
}
|
|
205
218
|
|
|
219
|
+
function buildWatchQuoteAnchor(msgCtx: B24MsgContext, ownerId: string): string {
|
|
220
|
+
if (msgCtx.isGroup) {
|
|
221
|
+
return `#${msgCtx.chatId}/${msgCtx.messageId}`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return `#${msgCtx.chatId}:${ownerId}/${msgCtx.messageId}`;
|
|
225
|
+
}
|
|
226
|
+
|
|
206
227
|
function buildWatchQuoteText(params: {
|
|
207
228
|
senderName: string;
|
|
208
229
|
language?: string;
|
|
@@ -505,8 +526,116 @@ export function resolveConversationRef(params: {
|
|
|
505
526
|
export function buildConversationSessionKey(
|
|
506
527
|
routeSessionKey: string,
|
|
507
528
|
conversation: Pick<Bitrix24ConversationRef, 'address'>,
|
|
529
|
+
sessionNamespace?: string,
|
|
508
530
|
): string {
|
|
509
|
-
|
|
531
|
+
const baseKey = `${routeSessionKey}:${conversation.address}`;
|
|
532
|
+
return sessionNamespace
|
|
533
|
+
? `${baseKey}:${sessionNamespace}`
|
|
534
|
+
: baseKey;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
type ActiveSessionNamespaceEntry = {
|
|
538
|
+
namespace: string;
|
|
539
|
+
updatedAt: number;
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
function normalizeSessionNamespaceTimestamp(value: unknown, now: number, fallback: number): number {
|
|
543
|
+
let parsed: number | undefined;
|
|
544
|
+
|
|
545
|
+
if (typeof value === 'number') {
|
|
546
|
+
parsed = value;
|
|
547
|
+
} else if (typeof value === 'string') {
|
|
548
|
+
const asNumber = Number(value);
|
|
549
|
+
parsed = Number.isFinite(asNumber) ? asNumber : Date.parse(value);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (!Number.isFinite(parsed) || parsed == null || parsed <= 0) {
|
|
553
|
+
return fallback;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
return Math.min(parsed, now);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function isValidActiveSessionNamespace(value: unknown): value is string {
|
|
560
|
+
return typeof value === 'string' && ACTIVE_SESSION_NAMESPACE_RE.test(value);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export function normalizeActiveSessionNamespaceState(
|
|
564
|
+
state: unknown,
|
|
565
|
+
accountId: string,
|
|
566
|
+
now = Date.now(),
|
|
567
|
+
): Record<string, ActiveSessionNamespaceEntry> {
|
|
568
|
+
if (!state || typeof state !== 'object') {
|
|
569
|
+
return {};
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
const rawState = state as {
|
|
573
|
+
updatedAt?: unknown;
|
|
574
|
+
sessions?: Record<string, unknown>;
|
|
575
|
+
};
|
|
576
|
+
const fallbackUpdatedAt = normalizeSessionNamespaceTimestamp(rawState.updatedAt, now, now);
|
|
577
|
+
const accountKeyPrefix = `${accountId}:`;
|
|
578
|
+
const entries: Array<[string, ActiveSessionNamespaceEntry]> = [];
|
|
579
|
+
|
|
580
|
+
for (const [key, value] of Object.entries(rawState.sessions ?? {})) {
|
|
581
|
+
if (!key.startsWith(accountKeyPrefix)) {
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
let namespace: unknown;
|
|
586
|
+
let updatedAt = fallbackUpdatedAt;
|
|
587
|
+
|
|
588
|
+
if (typeof value === 'string') {
|
|
589
|
+
namespace = value;
|
|
590
|
+
} else if (value && typeof value === 'object') {
|
|
591
|
+
const rawEntry = value as {
|
|
592
|
+
namespace?: unknown;
|
|
593
|
+
updatedAt?: unknown;
|
|
594
|
+
};
|
|
595
|
+
namespace = rawEntry.namespace;
|
|
596
|
+
updatedAt = normalizeSessionNamespaceTimestamp(rawEntry.updatedAt, now, fallbackUpdatedAt);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
if (!isValidActiveSessionNamespace(namespace)) {
|
|
600
|
+
continue;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (now - updatedAt > ACTIVE_SESSION_NAMESPACE_TTL_MS) {
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
entries.push([key, { namespace, updatedAt }]);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
entries.sort((left, right) => left[1].updatedAt - right[1].updatedAt);
|
|
611
|
+
return Object.fromEntries(entries.slice(-ACTIVE_SESSION_NAMESPACE_MAX_KEYS));
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
function pruneActiveSessionNamespaceEntries(
|
|
615
|
+
entries: Map<string, ActiveSessionNamespaceEntry>,
|
|
616
|
+
now = Date.now(),
|
|
617
|
+
): void {
|
|
618
|
+
for (const [key, value] of entries) {
|
|
619
|
+
if (!isValidActiveSessionNamespace(value?.namespace)
|
|
620
|
+
|| !Number.isFinite(value?.updatedAt)
|
|
621
|
+
|| value.updatedAt <= 0
|
|
622
|
+
|| now - value.updatedAt > ACTIVE_SESSION_NAMESPACE_TTL_MS) {
|
|
623
|
+
entries.delete(key);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (entries.size <= ACTIVE_SESSION_NAMESPACE_MAX_KEYS) {
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
const overflow = entries.size - ACTIVE_SESSION_NAMESPACE_MAX_KEYS;
|
|
632
|
+
const staleFirst = [...entries.entries()]
|
|
633
|
+
.sort((left, right) => left[1].updatedAt - right[1].updatedAt)
|
|
634
|
+
.slice(0, overflow);
|
|
635
|
+
|
|
636
|
+
for (const [key] of staleFirst) {
|
|
637
|
+
entries.delete(key);
|
|
638
|
+
}
|
|
510
639
|
}
|
|
511
640
|
|
|
512
641
|
function buildHistoryBody(msgCtx: B24MsgContext): string {
|
|
@@ -737,7 +866,7 @@ function normalizeTopicText(text: string): string {
|
|
|
737
866
|
|
|
738
867
|
function tokenizeTopicText(text: string): string[] {
|
|
739
868
|
return normalizeTopicText(text)
|
|
740
|
-
.split(/[
|
|
869
|
+
.split(/[^\p{L}\p{N}]+/u)
|
|
741
870
|
.filter(Boolean);
|
|
742
871
|
}
|
|
743
872
|
|
|
@@ -922,7 +1051,20 @@ export function __setGatewayStateForTests(state: GatewayState | null): void {
|
|
|
922
1051
|
gatewayState = state;
|
|
923
1052
|
}
|
|
924
1053
|
|
|
925
|
-
// ───
|
|
1054
|
+
// ─── Keyboard layouts ────────────────────────────────────────────────────────
|
|
1055
|
+
|
|
1056
|
+
export function buildWelcomeKeyboard(language?: string): B24Keyboard {
|
|
1057
|
+
const labels = welcomeKeyboardLabels(language);
|
|
1058
|
+
|
|
1059
|
+
return [
|
|
1060
|
+
{ TEXT: labels.todayTasks, ACTION: 'SEND', ACTION_VALUE: labels.todayTasks, DISPLAY: 'LINE' },
|
|
1061
|
+
{ TEXT: labels.stalledDeals, ACTION: 'SEND', ACTION_VALUE: labels.stalledDeals, DISPLAY: 'LINE' },
|
|
1062
|
+
{ TYPE: 'NEWLINE' },
|
|
1063
|
+
{ TEXT: labels.newSession, COMMAND: 'new', DISPLAY: 'LINE' },
|
|
1064
|
+
{ TEXT: labels.commands, COMMAND: 'commands', DISPLAY: 'LINE' },
|
|
1065
|
+
{ TEXT: labels.help, COMMAND: 'help', BG_COLOR_TOKEN: 'primary', DISPLAY: 'LINE' },
|
|
1066
|
+
];
|
|
1067
|
+
}
|
|
926
1068
|
|
|
927
1069
|
/** Default keyboard shown with command responses and welcome messages. */
|
|
928
1070
|
export function buildDefaultCommandKeyboard(language?: string): B24Keyboard {
|
|
@@ -939,6 +1081,7 @@ export function buildDefaultCommandKeyboard(language?: string): B24Keyboard {
|
|
|
939
1081
|
}
|
|
940
1082
|
|
|
941
1083
|
export const DEFAULT_COMMAND_KEYBOARD: B24Keyboard = buildDefaultCommandKeyboard();
|
|
1084
|
+
export const DEFAULT_WELCOME_KEYBOARD: B24Keyboard = buildWelcomeKeyboard();
|
|
942
1085
|
|
|
943
1086
|
// ─── Keyboard / Button conversion ────────────────────────────────────────────
|
|
944
1087
|
|
|
@@ -1032,7 +1175,8 @@ export function extractKeyboardFromPayload(
|
|
|
1032
1175
|
|
|
1033
1176
|
const tgData = cd.telegram as { buttons?: ChannelButton[][] } | undefined;
|
|
1034
1177
|
if (tgData?.buttons?.length) {
|
|
1035
|
-
|
|
1178
|
+
const keyboard = convertButtonsToKeyboard(tgData.buttons);
|
|
1179
|
+
return keyboard.length > 0 ? keyboard : undefined;
|
|
1036
1180
|
}
|
|
1037
1181
|
|
|
1038
1182
|
return undefined;
|
|
@@ -1086,6 +1230,13 @@ function normalizeCommandReplyPayload(params: {
|
|
|
1086
1230
|
}
|
|
1087
1231
|
}
|
|
1088
1232
|
|
|
1233
|
+
if (commandName === 'new' && commandParams.trim() === '') {
|
|
1234
|
+
const normalizedText = normalizeNewSessionReply(language, text);
|
|
1235
|
+
if (normalizedText) {
|
|
1236
|
+
return { text: normalizedText, convertMarkdown: false };
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1089
1240
|
return { text };
|
|
1090
1241
|
}
|
|
1091
1242
|
|
|
@@ -1170,7 +1321,7 @@ async function sendInitialWelcomeToWebhookOwner(params: {
|
|
|
1170
1321
|
const text = onboardingMessage(language, config.botName ?? 'OpenClaw', config.dmPolicy);
|
|
1171
1322
|
const options = isPairing
|
|
1172
1323
|
? undefined
|
|
1173
|
-
: { keyboard:
|
|
1324
|
+
: { keyboard: buildWelcomeKeyboard(language) };
|
|
1174
1325
|
|
|
1175
1326
|
try {
|
|
1176
1327
|
await sendService.sendText(sendCtx, text, options);
|
|
@@ -1307,8 +1458,7 @@ async function ensureCommandsRegistered(
|
|
|
1307
1458
|
try {
|
|
1308
1459
|
await api.registerCommand(webhookUrl, bot, {
|
|
1309
1460
|
command: cmd.command,
|
|
1310
|
-
|
|
1311
|
-
...(cmd.params ? { params: { en: cmd.params, ru: cmd.params } } : {}),
|
|
1461
|
+
...getCommandRegistrationPayload(cmd),
|
|
1312
1462
|
});
|
|
1313
1463
|
registered++;
|
|
1314
1464
|
} catch (err: unknown) {
|
|
@@ -1817,6 +1967,90 @@ export const bitrix24Plugin = {
|
|
|
1817
1967
|
const welcomedDialogs = new Set<string>();
|
|
1818
1968
|
const dialogNoticeTimestamps = new Map<string, number>();
|
|
1819
1969
|
const historyCache = new HistoryCache({ maxKeys: HISTORY_CACHE_MAX_KEYS });
|
|
1970
|
+
const activeSessionNamespaces = new Map<string, ActiveSessionNamespaceEntry>();
|
|
1971
|
+
const sessionNamespaceStatePath = join(resolvePollingStateDir(), `session-namespaces-${ctx.accountId}.json`);
|
|
1972
|
+
let persistActiveSessionNamespacesTail: Promise<void> = Promise.resolve();
|
|
1973
|
+
|
|
1974
|
+
const buildActiveSessionNamespaceKey = (conversation: Pick<Bitrix24ConversationRef, 'address'>): string => {
|
|
1975
|
+
return `${ctx.accountId}:${conversation.address}`;
|
|
1976
|
+
};
|
|
1977
|
+
|
|
1978
|
+
const loadActiveSessionNamespaces = async (): Promise<void> => {
|
|
1979
|
+
try {
|
|
1980
|
+
const raw = await readFile(sessionNamespaceStatePath, 'utf-8');
|
|
1981
|
+
const state = JSON.parse(raw) as {
|
|
1982
|
+
sessions?: Record<string, unknown>;
|
|
1983
|
+
};
|
|
1984
|
+
const entries = normalizeActiveSessionNamespaceState(state, ctx.accountId);
|
|
1985
|
+
const persistedSessions = state.sessions ?? {};
|
|
1986
|
+
const needsCompaction = Object.keys(entries).length !== Object.keys(persistedSessions).length
|
|
1987
|
+
|| Object.values(persistedSessions).some((value) => typeof value === 'string');
|
|
1988
|
+
|
|
1989
|
+
activeSessionNamespaces.clear();
|
|
1990
|
+
for (const [key, value] of Object.entries(entries)) {
|
|
1991
|
+
activeSessionNamespaces.set(key, value);
|
|
1992
|
+
}
|
|
1993
|
+
|
|
1994
|
+
if (needsCompaction) {
|
|
1995
|
+
void persistActiveSessionNamespaces();
|
|
1996
|
+
}
|
|
1997
|
+
} catch (err) {
|
|
1998
|
+
logger.debug('Failed to load active session namespaces, starting fresh', err);
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
|
|
2002
|
+
const persistActiveSessionNamespaces = (): Promise<void> => {
|
|
2003
|
+
persistActiveSessionNamespacesTail = persistActiveSessionNamespacesTail
|
|
2004
|
+
.catch(() => undefined)
|
|
2005
|
+
.then(async () => {
|
|
2006
|
+
try {
|
|
2007
|
+
pruneActiveSessionNamespaceEntries(activeSessionNamespaces);
|
|
2008
|
+
await mkdir(dirname(sessionNamespaceStatePath), { recursive: true });
|
|
2009
|
+
const data = JSON.stringify({
|
|
2010
|
+
updatedAt: new Date().toISOString(),
|
|
2011
|
+
sessions: Object.fromEntries(activeSessionNamespaces),
|
|
2012
|
+
}, null, 2);
|
|
2013
|
+
const tmpPath = `${sessionNamespaceStatePath}.${randomUUID()}.tmp`;
|
|
2014
|
+
await writeFile(tmpPath, data, 'utf-8');
|
|
2015
|
+
await rename(tmpPath, sessionNamespaceStatePath);
|
|
2016
|
+
} catch (err) {
|
|
2017
|
+
logger.warn('Failed to persist active session namespaces', err);
|
|
2018
|
+
}
|
|
2019
|
+
});
|
|
2020
|
+
|
|
2021
|
+
return persistActiveSessionNamespacesTail;
|
|
2022
|
+
};
|
|
2023
|
+
|
|
2024
|
+
const resolveActiveConversationSessionKey = (
|
|
2025
|
+
routeSessionKey: string,
|
|
2026
|
+
conversation: Pick<Bitrix24ConversationRef, 'address'>,
|
|
2027
|
+
): string => {
|
|
2028
|
+
return buildConversationSessionKey(
|
|
2029
|
+
routeSessionKey,
|
|
2030
|
+
conversation,
|
|
2031
|
+
activeSessionNamespaces.get(buildActiveSessionNamespaceKey(conversation))?.namespace,
|
|
2032
|
+
);
|
|
2033
|
+
};
|
|
2034
|
+
|
|
2035
|
+
const startNewConversationSession = async (
|
|
2036
|
+
conversation: Pick<Bitrix24ConversationRef, 'address' | 'historyKey' | 'dialogId'>,
|
|
2037
|
+
): Promise<string> => {
|
|
2038
|
+
const sessionNamespace = randomUUID();
|
|
2039
|
+
pruneActiveSessionNamespaceEntries(activeSessionNamespaces);
|
|
2040
|
+
historyCache.clear(conversation.historyKey);
|
|
2041
|
+
activeSessionNamespaces.set(buildActiveSessionNamespaceKey(conversation), {
|
|
2042
|
+
namespace: sessionNamespace,
|
|
2043
|
+
updatedAt: Date.now(),
|
|
2044
|
+
});
|
|
2045
|
+
await persistActiveSessionNamespaces();
|
|
2046
|
+
logger.info('Started new local conversation session', {
|
|
2047
|
+
dialogId: conversation.dialogId,
|
|
2048
|
+
sessionNamespace,
|
|
2049
|
+
});
|
|
2050
|
+
return sessionNamespace;
|
|
2051
|
+
};
|
|
2052
|
+
|
|
2053
|
+
await loadActiveSessionNamespaces();
|
|
1820
2054
|
|
|
1821
2055
|
// Cleanup stale denied dialog entries once per day
|
|
1822
2056
|
const DENIED_CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -2092,7 +2326,7 @@ export const bitrix24Plugin = {
|
|
|
2092
2326
|
RawBody: body,
|
|
2093
2327
|
From: conversation.address,
|
|
2094
2328
|
To: conversation.address,
|
|
2095
|
-
SessionKey:
|
|
2329
|
+
SessionKey: resolveActiveConversationSessionKey(route.sessionKey, conversation),
|
|
2096
2330
|
AccountId: route.accountId,
|
|
2097
2331
|
ChatType: msgCtx.isDm ? 'direct' : 'group',
|
|
2098
2332
|
ConversationLabel: msgCtx.senderName,
|
|
@@ -2286,6 +2520,19 @@ export const bitrix24Plugin = {
|
|
|
2286
2520
|
bot,
|
|
2287
2521
|
dialogId: ownerId,
|
|
2288
2522
|
};
|
|
2523
|
+
const sendQuotedWatchMessage = async (): Promise<void> => {
|
|
2524
|
+
const quoteText = buildWatchQuoteText({
|
|
2525
|
+
senderName: msgCtx.senderName || msgCtx.chatName || msgCtx.chatId,
|
|
2526
|
+
language: msgCtx.language,
|
|
2527
|
+
timestamp: msgCtx.timestamp,
|
|
2528
|
+
anchor: buildWatchQuoteAnchor(msgCtx, ownerId),
|
|
2529
|
+
body: msgCtx.text.trim(),
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
await sendService.sendText(ownerSendCtx, quoteText, {
|
|
2533
|
+
convertMarkdown: false,
|
|
2534
|
+
});
|
|
2535
|
+
};
|
|
2289
2536
|
|
|
2290
2537
|
const noticeText = watchOwnerDmNotice(msgCtx.language, {
|
|
2291
2538
|
chatRef: buildChatContextUrl(
|
|
@@ -2304,31 +2551,21 @@ export const bitrix24Plugin = {
|
|
|
2304
2551
|
convertMarkdown: false,
|
|
2305
2552
|
});
|
|
2306
2553
|
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
});
|
|
2319
|
-
return true;
|
|
2554
|
+
try {
|
|
2555
|
+
await api.sendMessage(
|
|
2556
|
+
webhookUrl,
|
|
2557
|
+
bot,
|
|
2558
|
+
ownerId,
|
|
2559
|
+
null,
|
|
2560
|
+
{ forwardMessages: [forwardedMessageId] },
|
|
2561
|
+
);
|
|
2562
|
+
} catch (err) {
|
|
2563
|
+
logger.warn('Failed to send owner watch notification with native forward, falling back to quote', err);
|
|
2564
|
+
await sendQuotedWatchMessage();
|
|
2320
2565
|
}
|
|
2321
|
-
|
|
2322
|
-
await api.sendMessage(
|
|
2323
|
-
webhookUrl,
|
|
2324
|
-
bot,
|
|
2325
|
-
ownerId,
|
|
2326
|
-
null,
|
|
2327
|
-
{ forwardMessages: [forwardedMessageId] },
|
|
2328
|
-
);
|
|
2329
2566
|
return true;
|
|
2330
2567
|
} catch (err) {
|
|
2331
|
-
logger.warn('Failed to send owner watch notification
|
|
2568
|
+
logger.warn('Failed to send owner watch notification', err);
|
|
2332
2569
|
return false;
|
|
2333
2570
|
}
|
|
2334
2571
|
};
|
|
@@ -2607,9 +2844,10 @@ export const bitrix24Plugin = {
|
|
|
2607
2844
|
messageId,
|
|
2608
2845
|
} = cmdCtx;
|
|
2609
2846
|
const isDm = chatType === 'P';
|
|
2847
|
+
const replyDialogId = isDm ? senderId : dialogId;
|
|
2610
2848
|
const conversation = resolveConversationRef({
|
|
2611
2849
|
accountId: ctx.accountId,
|
|
2612
|
-
dialogId,
|
|
2850
|
+
dialogId: replyDialogId,
|
|
2613
2851
|
isDirect: isDm,
|
|
2614
2852
|
});
|
|
2615
2853
|
|
|
@@ -2627,7 +2865,7 @@ export const bitrix24Plugin = {
|
|
|
2627
2865
|
const sendCtx: SendContext = {
|
|
2628
2866
|
webhookUrl,
|
|
2629
2867
|
bot,
|
|
2630
|
-
dialogId:
|
|
2868
|
+
dialogId: replyDialogId,
|
|
2631
2869
|
};
|
|
2632
2870
|
|
|
2633
2871
|
let runtime;
|
|
@@ -2707,8 +2945,6 @@ export const bitrix24Plugin = {
|
|
|
2707
2945
|
return;
|
|
2708
2946
|
}
|
|
2709
2947
|
|
|
2710
|
-
await sendService.sendStatus(sendCtx, 'IMBOT_AGENT_ACTION_THINKING', 8);
|
|
2711
|
-
|
|
2712
2948
|
if (accessResult === 'deny') {
|
|
2713
2949
|
await sendService.markRead(sendCtx, commandMessageId);
|
|
2714
2950
|
await sendService.answerCommandText(
|
|
@@ -2745,6 +2981,9 @@ export const bitrix24Plugin = {
|
|
|
2745
2981
|
await directTextCoalescer.flush(ctx.accountId, conversation.dialogId);
|
|
2746
2982
|
|
|
2747
2983
|
const defaultCommandKeyboard = buildDefaultCommandKeyboard(cmdCtx.language);
|
|
2984
|
+
const defaultSessionKeyboard = commandName === 'new' && commandParams.trim() === ''
|
|
2985
|
+
? buildWelcomeKeyboard(cmdCtx.language)
|
|
2986
|
+
: defaultCommandKeyboard;
|
|
2748
2987
|
|
|
2749
2988
|
if (commandName === 'help' || commandName === 'commands') {
|
|
2750
2989
|
const helpText = buildCommandsHelpText(cmdCtx.language, { concise: commandName === 'help' });
|
|
@@ -2765,6 +3004,28 @@ export const bitrix24Plugin = {
|
|
|
2765
3004
|
return;
|
|
2766
3005
|
}
|
|
2767
3006
|
|
|
3007
|
+
if (commandName === 'new' && commandParams.trim() === '') {
|
|
3008
|
+
await startNewConversationSession(conversation);
|
|
3009
|
+
const startedText = newSessionReplyTexts(cmdCtx.language).started;
|
|
3010
|
+
|
|
3011
|
+
if (isDm) {
|
|
3012
|
+
await sendService.sendText(
|
|
3013
|
+
sendCtx,
|
|
3014
|
+
startedText,
|
|
3015
|
+
{ keyboard: defaultSessionKeyboard, convertMarkdown: false },
|
|
3016
|
+
);
|
|
3017
|
+
} else {
|
|
3018
|
+
await sendService.answerCommandText(
|
|
3019
|
+
commandSendCtx,
|
|
3020
|
+
startedText,
|
|
3021
|
+
{ keyboard: defaultSessionKeyboard, convertMarkdown: false },
|
|
3022
|
+
);
|
|
3023
|
+
}
|
|
3024
|
+
return;
|
|
3025
|
+
}
|
|
3026
|
+
|
|
3027
|
+
await sendService.sendStatus(sendCtx, 'IMBOT_AGENT_ACTION_THINKING', 8);
|
|
3028
|
+
|
|
2768
3029
|
const route = runtime.channel.routing.resolveAgentRoute({
|
|
2769
3030
|
cfg,
|
|
2770
3031
|
channel: 'bitrix24',
|
|
@@ -2781,7 +3042,7 @@ export const bitrix24Plugin = {
|
|
|
2781
3042
|
CommandBody: commandText,
|
|
2782
3043
|
CommandAuthorized: true,
|
|
2783
3044
|
CommandSource: 'native',
|
|
2784
|
-
CommandTargetSessionKey:
|
|
3045
|
+
CommandTargetSessionKey: resolveActiveConversationSessionKey(route.sessionKey, conversation),
|
|
2785
3046
|
From: conversation.address,
|
|
2786
3047
|
To: `slash:${senderId}`,
|
|
2787
3048
|
SessionKey: slashSessionKey,
|
|
@@ -2817,7 +3078,7 @@ export const bitrix24Plugin = {
|
|
|
2817
3078
|
await replyStatusHeartbeat.stopAndWait();
|
|
2818
3079
|
if (payload.text) {
|
|
2819
3080
|
const keyboard = extractKeyboardFromPayload(payload)
|
|
2820
|
-
??
|
|
3081
|
+
?? defaultSessionKeyboard;
|
|
2821
3082
|
const formattedPayload = normalizeCommandReplyPayload({
|
|
2822
3083
|
commandName,
|
|
2823
3084
|
commandParams,
|
|
@@ -2861,12 +3122,12 @@ export const bitrix24Plugin = {
|
|
|
2861
3122
|
const fallbackText = replyGenerationFailed(cmdCtx.language);
|
|
2862
3123
|
if (isDm) {
|
|
2863
3124
|
await sendService.sendText(sendCtx, fallbackText, {
|
|
2864
|
-
keyboard:
|
|
3125
|
+
keyboard: defaultSessionKeyboard,
|
|
2865
3126
|
convertMarkdown: false,
|
|
2866
3127
|
});
|
|
2867
3128
|
} else {
|
|
2868
3129
|
await sendService.answerCommandText(commandSendCtx, fallbackText, {
|
|
2869
|
-
keyboard:
|
|
3130
|
+
keyboard: defaultSessionKeyboard,
|
|
2870
3131
|
convertMarkdown: false,
|
|
2871
3132
|
});
|
|
2872
3133
|
}
|
|
@@ -2882,12 +3143,12 @@ export const bitrix24Plugin = {
|
|
|
2882
3143
|
const fallbackText = replyGenerationFailed(cmdCtx.language);
|
|
2883
3144
|
if (isDm) {
|
|
2884
3145
|
await sendService.sendText(sendCtx, fallbackText, {
|
|
2885
|
-
keyboard:
|
|
3146
|
+
keyboard: defaultSessionKeyboard,
|
|
2886
3147
|
convertMarkdown: false,
|
|
2887
3148
|
});
|
|
2888
3149
|
} else {
|
|
2889
3150
|
await sendService.answerCommandText(commandSendCtx, fallbackText, {
|
|
2890
|
-
keyboard:
|
|
3151
|
+
keyboard: defaultSessionKeyboard,
|
|
2891
3152
|
convertMarkdown: false,
|
|
2892
3153
|
});
|
|
2893
3154
|
}
|
|
@@ -2982,7 +3243,7 @@ export const bitrix24Plugin = {
|
|
|
2982
3243
|
await sendService.sendText(
|
|
2983
3244
|
sendCtx,
|
|
2984
3245
|
text,
|
|
2985
|
-
isPairing ? undefined : { keyboard:
|
|
3246
|
+
isPairing ? undefined : { keyboard: buildWelcomeKeyboard(language) },
|
|
2986
3247
|
);
|
|
2987
3248
|
welcomedDialogs.add(dialogId);
|
|
2988
3249
|
logger.info('Welcome message sent', { dialogId });
|