@ihazz/bitrix24 1.1.6 → 1.1.8

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.
@@ -34,7 +34,12 @@ export declare function resolveConversationRef(params: {
34
34
  dialogId: string;
35
35
  isDirect: boolean;
36
36
  }): Bitrix24ConversationRef;
37
- export declare function buildConversationSessionKey(routeSessionKey: string, conversation: Pick<Bitrix24ConversationRef, 'address'>): string;
37
+ export declare function buildConversationSessionKey(routeSessionKey: string, conversation: Pick<Bitrix24ConversationRef, 'address'>, sessionNamespace?: string): string;
38
+ type ActiveSessionNamespaceEntry = {
39
+ namespace: string;
40
+ updatedAt: number;
41
+ };
42
+ export declare function normalizeActiveSessionNamespaceState(state: unknown, accountId: string, now?: number): Record<string, ActiveSessionNamespaceEntry>;
38
43
  /** State held per running gateway instance */
39
44
  interface GatewayState {
40
45
  accountId: string;
@@ -46,9 +51,11 @@ interface GatewayState {
46
51
  eventMode: 'fetch' | 'webhook';
47
52
  }
48
53
  export declare function __setGatewayStateForTests(state: GatewayState | null): void;
54
+ export declare function buildWelcomeKeyboard(language?: string): B24Keyboard;
49
55
  /** Default keyboard shown with command responses and welcome messages. */
50
56
  export declare function buildDefaultCommandKeyboard(language?: string): B24Keyboard;
51
57
  export declare const DEFAULT_COMMAND_KEYBOARD: B24Keyboard;
58
+ export declare const DEFAULT_WELCOME_KEYBOARD: B24Keyboard;
52
59
  /** Generic button format used by OpenClaw channelData. */
53
60
  export interface ChannelButton {
54
61
  text: string;
@@ -1 +1 @@
1
- {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"channel.d.ts","sourceRoot":"","sources":["../../src/channel.ts"],"names":[],"mappings":"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"}
@@ -1,18 +1,20 @@
1
- import { createHash } from 'node:crypto';
2
- import { basename } from 'node:path';
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 { listAccountIds, resolveAccount, getConfig } from './config.js';
4
5
  import { Bitrix24Api } from './api.js';
5
6
  import { SendService } from './send-service.js';
6
7
  import { MediaService } from './media-service.js';
7
8
  import { InboundHandler } from './inbound-handler.js';
8
9
  import { PollingService } from './polling-service.js';
10
+ import { resolvePollingStateDir } from './state-paths.js';
9
11
  import { normalizeAllowEntry, normalizeAllowList, checkAccessWithPairing, getWebhookUserId, } from './access-control.js';
10
12
  import { checkGroupAccessPassive, checkGroupAccessWithPairing, resolveAgentWatchRules, resolveGroupAccess, } from './group-access.js';
11
13
  import { DEFAULT_AVATAR_BASE64 } from './bot-avatar.js';
12
14
  import { Bitrix24ApiError, createVerboseLogger, defaultLogger, CHANNEL_PREFIX_RE } from './utils.js';
13
15
  import { getBitrix24Runtime } from './runtime.js';
14
- import { OPENCLAW_COMMANDS, buildCommandsHelpText, formatModelsCommandReply } from './commands.js';
15
- import { accessApproved, accessDenied, commandKeyboardLabels, groupPairingPending, mediaDownloadFailed, groupChatUnsupported, onboardingMessage, ownerAndAllowedUsersOnly, personalBotOwnerOnly, watchOwnerDmNotice, } from './i18n.js';
16
+ import { OPENCLAW_COMMANDS, buildCommandsHelpText, formatModelsCommandReply, getCommandRegistrationPayload, } from './commands.js';
17
+ import { accessApproved, accessDenied, commandKeyboardLabels, groupPairingPending, mediaDownloadFailed, groupChatUnsupported, newSessionReplyTexts, onboardingMessage, normalizeNewSessionReply, ownerAndAllowedUsersOnly, personalBotOwnerOnly, replyGenerationFailed, welcomeKeyboardLabels, watchOwnerDmNotice, } from './i18n.js';
16
18
  import { HistoryCache } from './history-cache.js';
17
19
  const PHASE_STATUS_DURATION_SECONDS = 8;
18
20
  const PHASE_STATUS_REFRESH_GRACE_MS = 1000;
@@ -31,6 +33,9 @@ const CROSS_CHAT_HISTORY_LIMIT = 20;
31
33
  const ACCESS_DENIED_REACTION = 'crossMark';
32
34
  const BOT_MESSAGE_WATCH_REACTION = 'eyes';
33
35
  const FORWARDED_CONTEXT_RANGE = 5;
36
+ const ACTIVE_SESSION_NAMESPACE_TTL_MS = 180 * 24 * 60 * 60 * 1000;
37
+ const ACTIVE_SESSION_NAMESPACE_MAX_KEYS = 1000;
38
+ 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;
34
39
  const REGISTERED_COMMANDS = new Set(OPENCLAW_COMMANDS.map((command) => command.command));
35
40
  // ─── Emoji → B24 reaction code mapping ──────────────────────────────────
36
41
  // B24 uses named reaction codes, not Unicode emoji.
@@ -125,7 +130,7 @@ function buildTopicsBbCode(topics) {
125
130
  .join(', ');
126
131
  }
127
132
  function formatQuoteTimestamp(timestamp, language) {
128
- const locale = (language ?? 'ru').toLowerCase().slice(0, 2);
133
+ const locale = (language ?? 'en').toLowerCase().slice(0, 2);
129
134
  const value = timestamp ?? Date.now();
130
135
  try {
131
136
  return new Intl.DateTimeFormat(locale, {
@@ -137,7 +142,7 @@ function formatQuoteTimestamp(timestamp, language) {
137
142
  }).format(new Date(value)).replace(',', '');
138
143
  }
139
144
  catch {
140
- return new Intl.DateTimeFormat('ru', {
145
+ return new Intl.DateTimeFormat('en', {
141
146
  year: 'numeric',
142
147
  month: '2-digit',
143
148
  day: '2-digit',
@@ -146,6 +151,12 @@ function formatQuoteTimestamp(timestamp, language) {
146
151
  }).format(new Date(value)).replace(',', '');
147
152
  }
148
153
  }
154
+ function buildWatchQuoteAnchor(msgCtx, ownerId) {
155
+ if (msgCtx.isGroup) {
156
+ return `#${msgCtx.chatId}/${msgCtx.messageId}`;
157
+ }
158
+ return `#${msgCtx.chatId}:${ownerId}/${msgCtx.messageId}`;
159
+ }
149
160
  function buildWatchQuoteText(params) {
150
161
  const separator = '------------------------------------------------------';
151
162
  const senderLine = `${escapeBbCodeText(params.senderName)} [${formatQuoteTimestamp(params.timestamp, params.language)}] ${params.anchor}`;
@@ -349,8 +360,81 @@ export function resolveConversationRef(params) {
349
360
  },
350
361
  };
351
362
  }
352
- export function buildConversationSessionKey(routeSessionKey, conversation) {
353
- return `${routeSessionKey}:${conversation.address}`;
363
+ export function buildConversationSessionKey(routeSessionKey, conversation, sessionNamespace) {
364
+ const baseKey = `${routeSessionKey}:${conversation.address}`;
365
+ return sessionNamespace
366
+ ? `${baseKey}:${sessionNamespace}`
367
+ : baseKey;
368
+ }
369
+ function normalizeSessionNamespaceTimestamp(value, now, fallback) {
370
+ let parsed;
371
+ if (typeof value === 'number') {
372
+ parsed = value;
373
+ }
374
+ else if (typeof value === 'string') {
375
+ const asNumber = Number(value);
376
+ parsed = Number.isFinite(asNumber) ? asNumber : Date.parse(value);
377
+ }
378
+ if (!Number.isFinite(parsed) || parsed == null || parsed <= 0) {
379
+ return fallback;
380
+ }
381
+ return Math.min(parsed, now);
382
+ }
383
+ function isValidActiveSessionNamespace(value) {
384
+ return typeof value === 'string' && ACTIVE_SESSION_NAMESPACE_RE.test(value);
385
+ }
386
+ export function normalizeActiveSessionNamespaceState(state, accountId, now = Date.now()) {
387
+ if (!state || typeof state !== 'object') {
388
+ return {};
389
+ }
390
+ const rawState = state;
391
+ const fallbackUpdatedAt = normalizeSessionNamespaceTimestamp(rawState.updatedAt, now, now);
392
+ const accountKeyPrefix = `${accountId}:`;
393
+ const entries = [];
394
+ for (const [key, value] of Object.entries(rawState.sessions ?? {})) {
395
+ if (!key.startsWith(accountKeyPrefix)) {
396
+ continue;
397
+ }
398
+ let namespace;
399
+ let updatedAt = fallbackUpdatedAt;
400
+ if (typeof value === 'string') {
401
+ namespace = value;
402
+ }
403
+ else if (value && typeof value === 'object') {
404
+ const rawEntry = value;
405
+ namespace = rawEntry.namespace;
406
+ updatedAt = normalizeSessionNamespaceTimestamp(rawEntry.updatedAt, now, fallbackUpdatedAt);
407
+ }
408
+ if (!isValidActiveSessionNamespace(namespace)) {
409
+ continue;
410
+ }
411
+ if (now - updatedAt > ACTIVE_SESSION_NAMESPACE_TTL_MS) {
412
+ continue;
413
+ }
414
+ entries.push([key, { namespace, updatedAt }]);
415
+ }
416
+ entries.sort((left, right) => left[1].updatedAt - right[1].updatedAt);
417
+ return Object.fromEntries(entries.slice(-ACTIVE_SESSION_NAMESPACE_MAX_KEYS));
418
+ }
419
+ function pruneActiveSessionNamespaceEntries(entries, now = Date.now()) {
420
+ for (const [key, value] of entries) {
421
+ if (!isValidActiveSessionNamespace(value?.namespace)
422
+ || !Number.isFinite(value?.updatedAt)
423
+ || value.updatedAt <= 0
424
+ || now - value.updatedAt > ACTIVE_SESSION_NAMESPACE_TTL_MS) {
425
+ entries.delete(key);
426
+ }
427
+ }
428
+ if (entries.size <= ACTIVE_SESSION_NAMESPACE_MAX_KEYS) {
429
+ return;
430
+ }
431
+ const overflow = entries.size - ACTIVE_SESSION_NAMESPACE_MAX_KEYS;
432
+ const staleFirst = [...entries.entries()]
433
+ .sort((left, right) => left[1].updatedAt - right[1].updatedAt)
434
+ .slice(0, overflow);
435
+ for (const [key] of staleFirst) {
436
+ entries.delete(key);
437
+ }
354
438
  }
355
439
  function buildHistoryBody(msgCtx) {
356
440
  const text = msgCtx.text.trim();
@@ -518,7 +602,7 @@ function normalizeTopicText(text) {
518
602
  }
519
603
  function tokenizeTopicText(text) {
520
604
  return normalizeTopicText(text)
521
- .split(/[^a-zа-яё0-9]+/i)
605
+ .split(/[^\p{L}\p{N}]+/u)
522
606
  .filter(Boolean);
523
607
  }
524
608
  function matchesWatchTopic(messageText, topic) {
@@ -654,7 +738,18 @@ let gatewayState = null;
654
738
  export function __setGatewayStateForTests(state) {
655
739
  gatewayState = state;
656
740
  }
657
- // ─── Default command keyboard ────────────────────────────────────────────────
741
+ // ─── Keyboard layouts ────────────────────────────────────────────────────────
742
+ export function buildWelcomeKeyboard(language) {
743
+ const labels = welcomeKeyboardLabels(language);
744
+ return [
745
+ { TEXT: labels.todayTasks, ACTION: 'SEND', ACTION_VALUE: labels.todayTasks, DISPLAY: 'LINE' },
746
+ { TEXT: labels.stalledDeals, ACTION: 'SEND', ACTION_VALUE: labels.stalledDeals, DISPLAY: 'LINE' },
747
+ { TYPE: 'NEWLINE' },
748
+ { TEXT: labels.newSession, COMMAND: 'new', DISPLAY: 'LINE' },
749
+ { TEXT: labels.commands, COMMAND: 'commands', DISPLAY: 'LINE' },
750
+ { TEXT: labels.help, COMMAND: 'help', BG_COLOR_TOKEN: 'primary', DISPLAY: 'LINE' },
751
+ ];
752
+ }
658
753
  /** Default keyboard shown with command responses and welcome messages. */
659
754
  export function buildDefaultCommandKeyboard(language) {
660
755
  const labels = commandKeyboardLabels(language);
@@ -668,6 +763,7 @@ export function buildDefaultCommandKeyboard(language) {
668
763
  ];
669
764
  }
670
765
  export const DEFAULT_COMMAND_KEYBOARD = buildDefaultCommandKeyboard();
766
+ export const DEFAULT_WELCOME_KEYBOARD = buildWelcomeKeyboard();
671
767
  function parseRegisteredCommandTrigger(callbackData) {
672
768
  const trimmed = callbackData.trim();
673
769
  const isSlashCommand = trimmed.startsWith('/');
@@ -739,7 +835,8 @@ export function extractKeyboardFromPayload(payload) {
739
835
  }
740
836
  const tgData = cd.telegram;
741
837
  if (tgData?.buttons?.length) {
742
- return convertButtonsToKeyboard(tgData.buttons);
838
+ const keyboard = convertButtonsToKeyboard(tgData.buttons);
839
+ return keyboard.length > 0 ? keyboard : undefined;
743
840
  }
744
841
  return undefined;
745
842
  }
@@ -781,6 +878,12 @@ function normalizeCommandReplyPayload(params) {
781
878
  return { text: formattedText, convertMarkdown: false };
782
879
  }
783
880
  }
881
+ if (commandName === 'new' && commandParams.trim() === '') {
882
+ const normalizedText = normalizeNewSessionReply(language, text);
883
+ if (normalizedText) {
884
+ return { text: normalizedText, convertMarkdown: false };
885
+ }
886
+ }
784
887
  return { text };
785
888
  }
786
889
  /**
@@ -835,7 +938,7 @@ async function sendInitialWelcomeToWebhookOwner(params) {
835
938
  const text = onboardingMessage(language, config.botName ?? 'OpenClaw', config.dmPolicy);
836
939
  const options = isPairing
837
940
  ? undefined
838
- : { keyboard: buildDefaultCommandKeyboard(language) };
941
+ : { keyboard: buildWelcomeKeyboard(language) };
839
942
  try {
840
943
  await sendService.sendText(sendCtx, text, options);
841
944
  welcomedDialogs.add(ownerId);
@@ -942,8 +1045,7 @@ async function ensureCommandsRegistered(api, config, bot, logger) {
942
1045
  try {
943
1046
  await api.registerCommand(webhookUrl, bot, {
944
1047
  command: cmd.command,
945
- title: { en: cmd.en, ru: cmd.ru },
946
- ...(cmd.params ? { params: { en: cmd.params, ru: cmd.params } } : {}),
1048
+ ...getCommandRegistrationPayload(cmd),
947
1049
  });
948
1050
  registered++;
949
1051
  }
@@ -1339,6 +1441,72 @@ export const bitrix24Plugin = {
1339
1441
  const welcomedDialogs = new Set();
1340
1442
  const dialogNoticeTimestamps = new Map();
1341
1443
  const historyCache = new HistoryCache({ maxKeys: HISTORY_CACHE_MAX_KEYS });
1444
+ const activeSessionNamespaces = new Map();
1445
+ const sessionNamespaceStatePath = join(resolvePollingStateDir(), `session-namespaces-${ctx.accountId}.json`);
1446
+ let persistActiveSessionNamespacesTail = Promise.resolve();
1447
+ const buildActiveSessionNamespaceKey = (conversation) => {
1448
+ return `${ctx.accountId}:${conversation.address}`;
1449
+ };
1450
+ const loadActiveSessionNamespaces = async () => {
1451
+ try {
1452
+ const raw = await readFile(sessionNamespaceStatePath, 'utf-8');
1453
+ const state = JSON.parse(raw);
1454
+ const entries = normalizeActiveSessionNamespaceState(state, ctx.accountId);
1455
+ const persistedSessions = state.sessions ?? {};
1456
+ const needsCompaction = Object.keys(entries).length !== Object.keys(persistedSessions).length
1457
+ || Object.values(persistedSessions).some((value) => typeof value === 'string');
1458
+ activeSessionNamespaces.clear();
1459
+ for (const [key, value] of Object.entries(entries)) {
1460
+ activeSessionNamespaces.set(key, value);
1461
+ }
1462
+ if (needsCompaction) {
1463
+ void persistActiveSessionNamespaces();
1464
+ }
1465
+ }
1466
+ catch (err) {
1467
+ logger.debug('Failed to load active session namespaces, starting fresh', err);
1468
+ }
1469
+ };
1470
+ const persistActiveSessionNamespaces = () => {
1471
+ persistActiveSessionNamespacesTail = persistActiveSessionNamespacesTail
1472
+ .catch(() => undefined)
1473
+ .then(async () => {
1474
+ try {
1475
+ pruneActiveSessionNamespaceEntries(activeSessionNamespaces);
1476
+ await mkdir(dirname(sessionNamespaceStatePath), { recursive: true });
1477
+ const data = JSON.stringify({
1478
+ updatedAt: new Date().toISOString(),
1479
+ sessions: Object.fromEntries(activeSessionNamespaces),
1480
+ }, null, 2);
1481
+ const tmpPath = `${sessionNamespaceStatePath}.${randomUUID()}.tmp`;
1482
+ await writeFile(tmpPath, data, 'utf-8');
1483
+ await rename(tmpPath, sessionNamespaceStatePath);
1484
+ }
1485
+ catch (err) {
1486
+ logger.warn('Failed to persist active session namespaces', err);
1487
+ }
1488
+ });
1489
+ return persistActiveSessionNamespacesTail;
1490
+ };
1491
+ const resolveActiveConversationSessionKey = (routeSessionKey, conversation) => {
1492
+ return buildConversationSessionKey(routeSessionKey, conversation, activeSessionNamespaces.get(buildActiveSessionNamespaceKey(conversation))?.namespace);
1493
+ };
1494
+ const startNewConversationSession = async (conversation) => {
1495
+ const sessionNamespace = randomUUID();
1496
+ pruneActiveSessionNamespaceEntries(activeSessionNamespaces);
1497
+ historyCache.clear(conversation.historyKey);
1498
+ activeSessionNamespaces.set(buildActiveSessionNamespaceKey(conversation), {
1499
+ namespace: sessionNamespace,
1500
+ updatedAt: Date.now(),
1501
+ });
1502
+ await persistActiveSessionNamespaces();
1503
+ logger.info('Started new local conversation session', {
1504
+ dialogId: conversation.dialogId,
1505
+ sessionNamespace,
1506
+ });
1507
+ return sessionNamespace;
1508
+ };
1509
+ await loadActiveSessionNamespaces();
1342
1510
  // Cleanup stale denied dialog entries once per day
1343
1511
  const DENIED_CLEANUP_INTERVAL_MS = 24 * 60 * 60 * 1000;
1344
1512
  const deniedCleanupTimer = setInterval(() => {
@@ -1485,6 +1653,8 @@ export const bitrix24Plugin = {
1485
1653
  sendCtx,
1486
1654
  config,
1487
1655
  });
1656
+ let replyDelivered = false;
1657
+ let dispatchFailed = false;
1488
1658
  // Download media files if present
1489
1659
  let mediaFields = {};
1490
1660
  if (msgCtx.media.length > 0) {
@@ -1573,7 +1743,7 @@ export const bitrix24Plugin = {
1573
1743
  RawBody: body,
1574
1744
  From: conversation.address,
1575
1745
  To: conversation.address,
1576
- SessionKey: buildConversationSessionKey(route.sessionKey, conversation),
1746
+ SessionKey: resolveActiveConversationSessionKey(route.sessionKey, conversation),
1577
1747
  AccountId: route.accountId,
1578
1748
  ChatType: msgCtx.isDm ? 'direct' : 'group',
1579
1749
  ConversationLabel: msgCtx.senderName,
@@ -1593,7 +1763,7 @@ export const bitrix24Plugin = {
1593
1763
  ...mediaFields,
1594
1764
  });
1595
1765
  try {
1596
- await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1766
+ const dispatchResult = await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
1597
1767
  ctx: inboundCtx,
1598
1768
  cfg,
1599
1769
  dispatcherOptions: {
@@ -1618,6 +1788,7 @@ export const bitrix24Plugin = {
1618
1788
  keyboard = extracted.keyboard;
1619
1789
  }
1620
1790
  }
1791
+ replyDelivered = true;
1621
1792
  await sendService.sendText(sendCtx, text, keyboard ? { keyboard } : undefined);
1622
1793
  }
1623
1794
  },
@@ -1629,13 +1800,20 @@ export const bitrix24Plugin = {
1629
1800
  },
1630
1801
  },
1631
1802
  });
1803
+ if (!replyDelivered && dispatchResult?.queuedFinal === false) {
1804
+ await sendService.sendText(sendCtx, replyGenerationFailed(msgCtx.language), { convertMarkdown: false });
1805
+ }
1632
1806
  }
1633
1807
  catch (err) {
1808
+ dispatchFailed = true;
1634
1809
  logger.error('Error dispatching message to agent', { senderId: msgCtx.senderId, chatId: msgCtx.chatId, error: err });
1635
1810
  }
1636
1811
  finally {
1637
1812
  replyStatusHeartbeat.stop();
1638
1813
  }
1814
+ if (!replyDelivered && dispatchFailed) {
1815
+ await sendService.sendText(sendCtx, replyGenerationFailed(msgCtx.language), { convertMarkdown: false });
1816
+ }
1639
1817
  }
1640
1818
  finally {
1641
1819
  await mediaService.cleanupDownloadedMedia(downloadedMedia.map((mediaItem) => mediaItem.path));
@@ -1727,6 +1905,18 @@ export const bitrix24Plugin = {
1727
1905
  bot,
1728
1906
  dialogId: ownerId,
1729
1907
  };
1908
+ const sendQuotedWatchMessage = async () => {
1909
+ const quoteText = buildWatchQuoteText({
1910
+ senderName: msgCtx.senderName || msgCtx.chatName || msgCtx.chatId,
1911
+ language: msgCtx.language,
1912
+ timestamp: msgCtx.timestamp,
1913
+ anchor: buildWatchQuoteAnchor(msgCtx, ownerId),
1914
+ body: msgCtx.text.trim(),
1915
+ });
1916
+ await sendService.sendText(ownerSendCtx, quoteText, {
1917
+ convertMarkdown: false,
1918
+ });
1919
+ };
1730
1920
  const noticeText = watchOwnerDmNotice(msgCtx.language, {
1731
1921
  chatRef: buildChatContextUrl(msgCtx.chatId, msgCtx.messageId, msgCtx.isDm
1732
1922
  ? (msgCtx.senderName || msgCtx.chatName || msgCtx.chatId)
@@ -1738,24 +1928,17 @@ export const bitrix24Plugin = {
1738
1928
  await sendService.sendText(ownerSendCtx, noticeText, {
1739
1929
  convertMarkdown: false,
1740
1930
  });
1741
- if (msgCtx.eventScope === 'user' && msgCtx.isDm) {
1742
- const quoteText = buildWatchQuoteText({
1743
- senderName: msgCtx.senderName || msgCtx.chatName || msgCtx.chatId,
1744
- language: msgCtx.language,
1745
- timestamp: msgCtx.timestamp,
1746
- anchor: `#${msgCtx.chatId}:${ownerId}/${msgCtx.messageId}`,
1747
- body: msgCtx.text.trim(),
1748
- });
1749
- await sendService.sendText(ownerSendCtx, quoteText, {
1750
- convertMarkdown: false,
1751
- });
1752
- return true;
1931
+ try {
1932
+ await api.sendMessage(webhookUrl, bot, ownerId, null, { forwardMessages: [forwardedMessageId] });
1933
+ }
1934
+ catch (err) {
1935
+ logger.warn('Failed to send owner watch notification with native forward, falling back to quote', err);
1936
+ await sendQuotedWatchMessage();
1753
1937
  }
1754
- await api.sendMessage(webhookUrl, bot, ownerId, null, { forwardMessages: [forwardedMessageId] });
1755
1938
  return true;
1756
1939
  }
1757
1940
  catch (err) {
1758
- logger.warn('Failed to send owner watch notification with native forward', err);
1941
+ logger.warn('Failed to send owner watch notification', err);
1759
1942
  return false;
1760
1943
  }
1761
1944
  };
@@ -1978,9 +2161,10 @@ export const bitrix24Plugin = {
1978
2161
  onCommand: async (cmdCtx) => {
1979
2162
  const { commandId, commandName, commandParams, commandText, senderId, dialogId, chatId, chatType, messageId, } = cmdCtx;
1980
2163
  const isDm = chatType === 'P';
2164
+ const replyDialogId = isDm ? senderId : dialogId;
1981
2165
  const conversation = resolveConversationRef({
1982
2166
  accountId: ctx.accountId,
1983
- dialogId,
2167
+ dialogId: replyDialogId,
1984
2168
  isDirect: isDm,
1985
2169
  });
1986
2170
  logger.info('Inbound command', {
@@ -1996,7 +2180,7 @@ export const bitrix24Plugin = {
1996
2180
  const sendCtx = {
1997
2181
  webhookUrl,
1998
2182
  bot,
1999
- dialogId: conversation.dialogId,
2183
+ dialogId: replyDialogId,
2000
2184
  };
2001
2185
  let runtime;
2002
2186
  let cfg;
@@ -2068,7 +2252,6 @@ export const bitrix24Plugin = {
2068
2252
  await sendService.answerCommandText(commandSendCtx, groupChatUnsupported(cmdCtx.language), { convertMarkdown: false });
2069
2253
  return;
2070
2254
  }
2071
- await sendService.sendStatus(sendCtx, 'IMBOT_AGENT_ACTION_THINKING', 8);
2072
2255
  if (accessResult === 'deny') {
2073
2256
  await sendService.markRead(sendCtx, commandMessageId);
2074
2257
  await sendService.answerCommandText(commandSendCtx, buildAccessDeniedNotice(cmdCtx.language, isDm ? config.dmPolicy : groupAccess?.groupPolicy, {
@@ -2089,6 +2272,9 @@ export const bitrix24Plugin = {
2089
2272
  }
2090
2273
  await directTextCoalescer.flush(ctx.accountId, conversation.dialogId);
2091
2274
  const defaultCommandKeyboard = buildDefaultCommandKeyboard(cmdCtx.language);
2275
+ const defaultSessionKeyboard = commandName === 'new' && commandParams.trim() === ''
2276
+ ? buildWelcomeKeyboard(cmdCtx.language)
2277
+ : defaultCommandKeyboard;
2092
2278
  if (commandName === 'help' || commandName === 'commands') {
2093
2279
  const helpText = buildCommandsHelpText(cmdCtx.language, { concise: commandName === 'help' });
2094
2280
  if (isDm) {
@@ -2099,6 +2285,18 @@ export const bitrix24Plugin = {
2099
2285
  }
2100
2286
  return;
2101
2287
  }
2288
+ if (commandName === 'new' && commandParams.trim() === '') {
2289
+ await startNewConversationSession(conversation);
2290
+ const startedText = newSessionReplyTexts(cmdCtx.language).started;
2291
+ if (isDm) {
2292
+ await sendService.sendText(sendCtx, startedText, { keyboard: defaultSessionKeyboard, convertMarkdown: false });
2293
+ }
2294
+ else {
2295
+ await sendService.answerCommandText(commandSendCtx, startedText, { keyboard: defaultSessionKeyboard, convertMarkdown: false });
2296
+ }
2297
+ return;
2298
+ }
2299
+ await sendService.sendStatus(sendCtx, 'IMBOT_AGENT_ACTION_THINKING', 8);
2102
2300
  const route = runtime.channel.routing.resolveAgentRoute({
2103
2301
  cfg,
2104
2302
  channel: 'bitrix24',
@@ -2113,7 +2311,7 @@ export const bitrix24Plugin = {
2113
2311
  CommandBody: commandText,
2114
2312
  CommandAuthorized: true,
2115
2313
  CommandSource: 'native',
2116
- CommandTargetSessionKey: buildConversationSessionKey(route.sessionKey, conversation),
2314
+ CommandTargetSessionKey: resolveActiveConversationSessionKey(route.sessionKey, conversation),
2117
2315
  From: conversation.address,
2118
2316
  To: `slash:${senderId}`,
2119
2317
  SessionKey: slashSessionKey,
@@ -2136,9 +2334,10 @@ export const bitrix24Plugin = {
2136
2334
  config,
2137
2335
  });
2138
2336
  let commandReplyDelivered = false;
2337
+ let commandDispatchFailed = false;
2139
2338
  try {
2140
2339
  await replyStatusHeartbeat.start();
2141
- await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
2340
+ const dispatchResult = await runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher({
2142
2341
  ctx: inboundCtx,
2143
2342
  cfg,
2144
2343
  dispatcherOptions: {
@@ -2146,7 +2345,7 @@ export const bitrix24Plugin = {
2146
2345
  await replyStatusHeartbeat.stopAndWait();
2147
2346
  if (payload.text) {
2148
2347
  const keyboard = extractKeyboardFromPayload(payload)
2149
- ?? defaultCommandKeyboard;
2348
+ ?? defaultSessionKeyboard;
2150
2349
  const formattedPayload = normalizeCommandReplyPayload({
2151
2350
  commandName,
2152
2351
  commandParams,
@@ -2154,14 +2353,15 @@ export const bitrix24Plugin = {
2154
2353
  language: cmdCtx.language,
2155
2354
  });
2156
2355
  if (!commandReplyDelivered) {
2157
- commandReplyDelivered = true;
2158
2356
  if (isDm) {
2357
+ commandReplyDelivered = true;
2159
2358
  await sendService.sendText(sendCtx, formattedPayload.text, {
2160
2359
  keyboard,
2161
2360
  convertMarkdown: formattedPayload.convertMarkdown,
2162
2361
  });
2163
2362
  }
2164
2363
  else {
2364
+ commandReplyDelivered = true;
2165
2365
  await sendService.answerCommandText(commandSendCtx, formattedPayload.text, {
2166
2366
  keyboard,
2167
2367
  convertMarkdown: formattedPayload.convertMarkdown,
@@ -2169,6 +2369,7 @@ export const bitrix24Plugin = {
2169
2369
  }
2170
2370
  return;
2171
2371
  }
2372
+ commandReplyDelivered = true;
2172
2373
  await sendService.sendText(sendCtx, formattedPayload.text, {
2173
2374
  keyboard,
2174
2375
  convertMarkdown: formattedPayload.convertMarkdown,
@@ -2183,13 +2384,44 @@ export const bitrix24Plugin = {
2183
2384
  },
2184
2385
  },
2185
2386
  });
2387
+ if (!commandReplyDelivered && dispatchResult?.queuedFinal === false) {
2388
+ const fallbackText = replyGenerationFailed(cmdCtx.language);
2389
+ if (isDm) {
2390
+ await sendService.sendText(sendCtx, fallbackText, {
2391
+ keyboard: defaultSessionKeyboard,
2392
+ convertMarkdown: false,
2393
+ });
2394
+ }
2395
+ else {
2396
+ await sendService.answerCommandText(commandSendCtx, fallbackText, {
2397
+ keyboard: defaultSessionKeyboard,
2398
+ convertMarkdown: false,
2399
+ });
2400
+ }
2401
+ }
2186
2402
  }
2187
2403
  catch (err) {
2404
+ commandDispatchFailed = true;
2188
2405
  logger.error('Error dispatching command to agent', { commandName, senderId, dialogId, error: err });
2189
2406
  }
2190
2407
  finally {
2191
2408
  replyStatusHeartbeat.stop();
2192
2409
  }
2410
+ if (!commandReplyDelivered && commandDispatchFailed) {
2411
+ const fallbackText = replyGenerationFailed(cmdCtx.language);
2412
+ if (isDm) {
2413
+ await sendService.sendText(sendCtx, fallbackText, {
2414
+ keyboard: defaultSessionKeyboard,
2415
+ convertMarkdown: false,
2416
+ });
2417
+ }
2418
+ else {
2419
+ await sendService.answerCommandText(commandSendCtx, fallbackText, {
2420
+ keyboard: defaultSessionKeyboard,
2421
+ convertMarkdown: false,
2422
+ });
2423
+ }
2424
+ }
2193
2425
  },
2194
2426
  onJoinChat: async (joinCtx) => {
2195
2427
  const { senderId, dialogId, chatId, chatType, language } = joinCtx;
@@ -2268,7 +2500,7 @@ export const bitrix24Plugin = {
2268
2500
  const isPairing = config.dmPolicy === 'pairing';
2269
2501
  const text = onboardingMessage(language, config.botName ?? 'OpenClaw', config.dmPolicy);
2270
2502
  try {
2271
- await sendService.sendText(sendCtx, text, isPairing ? undefined : { keyboard: buildDefaultCommandKeyboard(language) });
2503
+ await sendService.sendText(sendCtx, text, isPairing ? undefined : { keyboard: buildWelcomeKeyboard(language) });
2272
2504
  welcomedDialogs.add(dialogId);
2273
2505
  logger.info('Welcome message sent', { dialogId });
2274
2506
  }