@xopcai/xopc 0.0.104 → 0.0.106
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/browser-ext/manifest.json +1 -1
- package/dist/extensions/feishu/src/adapters/gateway-setup.js +2 -2
- package/dist/extensions/feishu/src/adapters/gateway-setup.js.map +1 -1
- package/dist/extensions/feishu/src/directory/directory-adapter.js +1 -1
- package/dist/extensions/feishu/src/directory/directory-adapter.js.map +1 -1
- package/dist/extensions/feishu/src/outbound/outbound-adapter.js +4 -3
- package/dist/extensions/feishu/src/outbound/outbound-adapter.js.map +1 -1
- package/dist/extensions/feishu/src/plugin.d.ts +6 -3
- package/dist/extensions/feishu/src/plugin.js +59 -40
- package/dist/extensions/feishu/src/plugin.js.map +1 -1
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js +20 -16
- package/dist/extensions/feishu/src/streaming/streaming-adapter.js.map +1 -1
- package/dist/extensions/feishu/src/transport/socket-mode/monitor.js +1 -1
- package/dist/extensions/feishu/src/transport/webhook/monitor.js +1 -1
- package/dist/extensions/feishu/src/workflow-progress.d.ts +1 -1
- package/dist/extensions/feishu/src/workflow-progress.js +2 -2
- package/dist/extensions/feishu/src/workflow-progress.js.map +1 -1
- package/dist/extensions/telegram/src/adapters/gateway-setup.js +8 -6
- package/dist/extensions/telegram/src/adapters/gateway-setup.js.map +1 -1
- package/dist/extensions/telegram/src/command-handler.d.ts +1 -1
- package/dist/extensions/telegram/src/command-handler.js +11 -9
- package/dist/extensions/telegram/src/command-handler.js.map +1 -1
- package/dist/extensions/telegram/src/inbound-processor.js +1 -2
- package/dist/extensions/telegram/src/inbound-processor.js.map +1 -1
- package/dist/extensions/telegram/src/plugin.d.ts +1 -0
- package/dist/extensions/telegram/src/plugin.js +6 -6
- package/dist/extensions/telegram/src/plugin.js.map +1 -1
- package/dist/extensions/telegram/src/workflow-progress.d.ts +1 -1
- package/dist/extensions/telegram/src/workflow-progress.js.map +1 -1
- package/dist/extensions/telegram/xopc.extension.json +1 -1
- package/dist/extensions/weixin/src/cli/gateway-qr-login.d.ts +4 -0
- package/dist/extensions/weixin/src/cli/gateway-qr-login.js +34 -5
- package/dist/extensions/weixin/src/cli/gateway-qr-login.js.map +1 -1
- package/dist/extensions/weixin/src/cli/qr-login.js +4 -3
- package/dist/extensions/weixin/src/cli/qr-login.js.map +1 -1
- package/dist/extensions/weixin/src/plugin.d.ts +1 -0
- package/dist/extensions/weixin/src/plugin.js +7 -6
- package/dist/extensions/weixin/src/plugin.js.map +1 -1
- package/dist/extensions/weixin/src/workflow-progress.d.ts +1 -1
- package/dist/extensions/weixin/src/workflow-progress.js.map +1 -1
- package/dist/gateway/static/root/assets/agents-3eP0vqFO.js +396 -0
- package/dist/gateway/static/root/assets/{apps-page-L-IwQTe0.js → apps-page-CZ2ZKrko.js} +1 -1
- package/dist/gateway/static/root/assets/channels-settings-BbETa93K.js +1 -0
- package/dist/gateway/static/root/assets/cron-page-DCtUkHMu.js +1 -0
- package/dist/gateway/static/root/assets/{dist-C6vclgEL.js → dist-OM9qGfm5.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-debug-page-D4JYAbsX.js → extension-debug-page-D-WHrr_w.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-page-_T62gi6e.js → extension-page-CCgjSoKn.js} +1 -1
- package/dist/gateway/static/root/assets/{extension-settings-page-CkJbjyM-.js → extension-settings-page-BJH5a-Gn.js} +1 -1
- package/dist/gateway/static/root/assets/{fetch-B5aiIguv.js → fetch-CkvRO30i.js} +1 -1
- package/dist/gateway/static/root/assets/{gateway-startup-retry-DqmLXhs6.js → gateway-startup-retry-BkKyRDrF.js} +1 -1
- package/dist/gateway/static/root/assets/{heartbeat-config-api-HwJV1K_Y.js → heartbeat-config-api-CVyP6pFj.js} +1 -1
- package/dist/gateway/static/root/assets/index-CEZxETzw.js +4845 -0
- package/dist/gateway/static/root/assets/{logs-page-DsjtTjBy.js → logs-page-R6Vv_Ja6.js} +2 -2
- package/dist/gateway/static/root/assets/{note-detail-page-B2V7RhgD.js → note-detail-page-BUDarLhh.js} +54 -54
- package/dist/gateway/static/root/assets/{note-time-BG8GCTr4.js → note-time-Cc2-T21g.js} +1 -1
- package/dist/gateway/static/root/assets/{notes-page-xrcVBQ6Z.js → notes-page-EAHl16bq.js} +1 -1
- package/dist/gateway/static/root/assets/{schema-form-S-QUvhul.js → schema-form-CrlOmEZt.js} +1 -1
- package/dist/gateway/static/root/assets/sessions-page-Ce_xuuSB.js +1 -0
- package/dist/gateway/static/root/assets/{settings-advanced-gate-DujQl9c7.js → settings-advanced-gate-BBAQxxew.js} +1 -1
- package/dist/gateway/static/root/assets/{settings-form-section-r1V7dRV6.js → settings-form-section-DjPD-ErC.js} +1 -1
- package/dist/gateway/static/root/assets/settings-page-KhQ9QgH0.js +3 -0
- package/dist/gateway/static/root/assets/{share-preview-page-DOFy0FNt.js → share-preview-page-BjvPht3V.js} +1 -1
- package/dist/gateway/static/root/assets/skills-page-C0laq7l1.js +2 -0
- package/dist/gateway/static/root/assets/{theme-store-Bo3j5Ovv.js → theme-store-DYgVdRB4.js} +1 -1
- package/dist/gateway/static/root/assets/url-DPEStU4M.js +3 -0
- package/dist/gateway/static/root/assets/{voice-api-key-field-AjmUAlEQ.js → voice-api-key-field-DeGckWkl.js} +1 -1
- package/dist/gateway/static/root/assets/{workflow-page.utils-DLEJ92gK.js → workflow-page.utils-D4sqR0yX.js} +1 -1
- package/dist/gateway/static/root/assets/workflows-page-DFjM95g5.js +27 -0
- package/dist/gateway/static/root/index.html +5 -5
- package/dist/package.js +1 -1
- package/dist/src/agent/context/workspace-seed.js +1 -1
- package/dist/src/agent/inbound/turn-dispatcher.d.ts +2 -0
- package/dist/src/agent/inbound/turn-dispatcher.js +1 -0
- package/dist/src/agent/inbound/turn-dispatcher.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-config.js +16 -8
- package/dist/src/agent/mcp/bundle-mcp-config.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-policy.js +2 -2
- package/dist/src/agent/mcp/bundle-mcp-policy.js.map +1 -1
- package/dist/src/agent/mcp/bundle-mcp-runtime.js +1 -1
- package/dist/src/agent/mcp/mcp-http.js +1 -1
- package/dist/src/agent/mcp/mcp-http.js.map +1 -1
- package/dist/src/agent/mcp/mcp-transport-config.js +2 -6
- package/dist/src/agent/mcp/mcp-transport-config.js.map +1 -1
- package/dist/src/agent/prompt/memory/index.js +30 -2
- package/dist/src/agent/prompt/memory/index.js.map +1 -1
- package/dist/src/agent/service/direct-turn-helpers.d.ts +2 -0
- package/dist/src/agent/service/direct-turn-helpers.js +5 -2
- package/dist/src/agent/service/direct-turn-helpers.js.map +1 -1
- package/dist/src/agent/service/process-direct-streaming.d.ts +2 -0
- package/dist/src/agent/service/process-direct-streaming.js +15 -1
- package/dist/src/agent/service/process-direct-streaming.js.map +1 -1
- package/dist/src/agent/service.js +2 -1
- package/dist/src/agent/service.js.map +1 -1
- package/dist/src/agent/service.types.d.ts +3 -0
- package/dist/src/agent/skills/hub-pull.js +1 -1
- package/dist/src/agent/source-context/index.d.ts +2 -0
- package/dist/src/agent/source-context/index.js +3 -0
- package/dist/src/agent/source-context/injector.d.ts +3 -0
- package/dist/src/agent/source-context/injector.js +36 -0
- package/dist/src/agent/source-context/injector.js.map +1 -0
- package/dist/src/agent/source-context/types.d.ts +19 -0
- package/dist/src/agent/source-context/types.js +10 -0
- package/dist/src/agent/source-context/types.js.map +1 -0
- package/dist/src/agent/tools/factory.js +5 -5
- package/dist/src/agent/tools/index.js +3 -3
- package/dist/src/agent/tools/session-search-tool.js +1 -1
- package/dist/src/agent/tools/shell.js +1 -1
- package/dist/src/agent/tools/web-extract.js +2 -2
- package/dist/src/agent/tools/web.js +1 -1
- package/dist/src/agent/workflow/index.js +2 -1
- package/dist/src/agent/workflow/runtime.d.ts +2 -2
- package/dist/src/agent/workflow/runtime.js +1 -14
- package/dist/src/agent/workflow/runtime.js.map +1 -1
- package/dist/src/agent/workflow/snapshot-empty.d.ts +2 -0
- package/dist/src/agent/workflow/snapshot-empty.js +19 -0
- package/dist/src/agent/workflow/snapshot-empty.js.map +1 -0
- package/dist/src/agent/workflow/snapshot.js +1 -1
- package/dist/src/agent/workflow/snapshot.js.map +1 -1
- package/dist/src/browser/actions/extension-registry-bridge.js +1 -1
- package/dist/src/browser/actions/registry.js +2 -2
- package/dist/src/browser/cdp-local-launcher.js +1 -1
- package/dist/src/browser/index.js +3 -3
- package/dist/src/browser/providers/browser-ext-install.js +1 -1
- package/dist/src/browser/providers/cloakbrowser.js +1 -1
- package/dist/src/cli/commands/browser-cli-helpers.js +1 -1
- package/dist/src/cli/commands/doctor/checks/database-schema.js +1 -1
- package/dist/src/config/paths.d.ts +1 -0
- package/dist/src/config/paths.js +5 -1
- package/dist/src/config/paths.js.map +1 -1
- package/dist/src/connectors/health.js +1 -1
- package/dist/src/connectors/install.js +1 -1
- package/dist/src/document-understanding/extract.d.ts +13 -0
- package/dist/src/document-understanding/extract.js +63 -0
- package/dist/src/document-understanding/extract.js.map +1 -0
- package/dist/src/gateway/hono/routes/mcp.js +1 -1
- package/dist/src/gateway/hono/routes/notes.js +46 -27
- package/dist/src/gateway/hono/routes/notes.js.map +1 -1
- package/dist/src/gateway/service.js +11 -0
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/mcp/channel-bridge.js +1 -1
- package/dist/src/notes/agent-context.d.ts +40 -0
- package/dist/src/notes/agent-context.js +383 -0
- package/dist/src/notes/agent-context.js.map +1 -0
- package/dist/src/notes/index.d.ts +2 -0
- package/dist/src/notes/index.js +2 -1
- package/dist/src/notes/service.d.ts +5 -0
- package/dist/src/notes/service.js +22 -0
- package/dist/src/notes/service.js.map +1 -1
- package/dist/src/notes/store.js +2 -0
- package/dist/src/notes/store.js.map +1 -1
- package/dist/src/share/share-thumbnail.js +1 -1
- package/dist/src/storage/sqlite/index.d.ts +1 -0
- package/dist/src/storage/sqlite/index.js +2 -1
- package/dist/src/storage/sqlite/migrations/005_add_note_agent_contexts.sql +10 -0
- package/dist/src/storage/sqlite/migrations/runner.d.ts +1 -1
- package/dist/src/storage/sqlite/migrations/runner.js +3 -3
- package/dist/src/storage/sqlite/migrations/runner.js.map +1 -1
- package/dist/src/storage/sqlite/migrations/runner.ts +1 -1
- package/dist/src/storage/sqlite/note-agent-context-repository.d.ts +10 -0
- package/dist/src/storage/sqlite/note-agent-context-repository.js +35 -0
- package/dist/src/storage/sqlite/note-agent-context-repository.js.map +1 -0
- package/dist/src/storage/sqlite/schema.js +1 -1
- package/dist/src/tui/tui-startup-resources.js +1 -1
- package/dist/src/voice/stt/availability.d.ts +6 -0
- package/dist/src/voice/stt/availability.js +14 -6
- package/dist/src/voice/stt/availability.js.map +1 -1
- package/dist/src/voice/stt/index.js +2 -1
- package/dist/src/voice/stt/index.js.map +1 -1
- package/dist/src/voice/stt/mention.d.ts +2 -0
- package/dist/src/voice/stt/mention.js +20 -0
- package/dist/src/voice/stt/mention.js.map +1 -0
- package/dist/src/voice/stt/preflight.d.ts +1 -4
- package/dist/src/voice/stt/preflight.js +1 -18
- package/dist/src/voice/stt/preflight.js.map +1 -1
- package/package.json +4 -4
- package/dist/gateway/static/root/assets/agents-CvsBrBfi.js +0 -396
- package/dist/gateway/static/root/assets/channels-settings-D95KuPeP.js +0 -1
- package/dist/gateway/static/root/assets/cron-page-CfkWXDAI.js +0 -1
- package/dist/gateway/static/root/assets/index-DvTb6wNR.js +0 -4845
- package/dist/gateway/static/root/assets/sessions-page-CrQer6Au.js +0 -1
- package/dist/gateway/static/root/assets/settings-page-C9nrONQ2.js +0 -3
- package/dist/gateway/static/root/assets/skills-page-DmTsrs_a.js +0 -2
- package/dist/gateway/static/root/assets/url-WgQIjFZu.js +0 -3
- package/dist/gateway/static/root/assets/workflows-page-BnjjKrM1.js +0 -27
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inbound-processor.js","names":[],"sources":["../../../../extensions/telegram/src/inbound-processor.ts"],"sourcesContent":["/**\n * Telegram Inbound Processor\n *\n * Handles inbound message processing from Telegram:\n * - Message queuing (per chat)\n * - Deduplication\n * - Access control\n * - Media download\n * - STT transcription\n * - Event publishing to bus\n */\n\nimport type { Bot, Context } from 'grammy';\nimport type { Message } from '@grammyjs/types';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type { TelegramAccountManager } from './account-manager.js';\nimport { telegramUpdateDedupe, buildTelegramUpdateKey } from './dedupe.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { normalizeTelegramCommandName, parseSlashCommand } from '@xopcai/xopc/chat-commands/command-parse.js';\nimport { tryConsumeTelegramClarifyFreeText } from '@xopcai/xopc/gateway/clarify-runtime.js';\nimport { resolveRoute } from '@xopcai/xopc/routing/index.js';\nimport { resolveTelegramGroupContext } from './group-config-resolver.js';\nimport { resolveTelegramFocusedSessionKey } from './focus-handler.js';\nimport { buildTelegramConversationId } from './conversation-id.js';\nimport { checkMentionInTranscription } from '@xopcai/xopc/voice/stt/index.js';\n\nconst log = createLogger('TelegramInboundProcessor');\n\n/** Must bypass the per-chat queue so /abort can cancel an in-flight turn. */\nfunction isAbortSlashCommand(text: string): boolean {\n const parsed = parseSlashCommand(text);\n if (!parsed) return false;\n const cmd = normalizeTelegramCommandName(parsed.command);\n return cmd === 'abort' || cmd === 'stop' || cmd === 'cancel';\n}\n\n/** Maximum voice message duration for STT in seconds */\nconst STT_MAX_VOICE_DURATION_SECONDS = 60;\n\n// =============================================================================\n// External Service Interfaces (for dependency injection)\n// =============================================================================\n\nexport interface AccessControlService {\n normalizeAllowFromWithStore(options: { allowFrom?: Array<string | number> }): unknown;\n evaluateGroupBaseAccess(options: {\n isGroup: boolean;\n groupConfig?: unknown;\n topicConfig?: unknown;\n hasGroupAllowOverride: boolean;\n effectiveGroupAllow: unknown;\n senderId?: string;\n senderUsername?: string;\n }): { allowed: boolean; reason?: string };\n resolveRequireMention(options: {\n topicConfig?: unknown;\n groupConfig?: unknown;\n defaultRequireMention?: boolean;\n }): boolean;\n hasBotMention(options: { botUsername: string; text?: string; entities?: unknown }): boolean;\n removeBotMention(text: string, botUsername: string): string;\n}\n\nexport interface SessionKeyService {\n generateSessionKey(options: {\n source: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n threadId?: string;\n accountId?: string;\n agentId?: string;\n }): string;\n}\n\nexport interface STTService {\n transcribe(buffer: Buffer, config: unknown, options?: { language?: string }): Promise<{ text: string }>;\n isSTTAvailable(config: unknown): boolean;\n}\n\nexport interface MediaUtils {\n getMimeType(type: string, filePath?: string): string;\n}\n\n// =============================================================================\n// Dependencies Interface\n// =============================================================================\n\nexport interface InboundProcessorDeps {\n bus: MessageBus;\n config: Config;\n accountManager: TelegramAccountManager;\n // External services (injected for testability)\n accessControl: AccessControlService;\n sessionKeyService: SessionKeyService;\n sttService: STTService;\n mediaUtils: MediaUtils;\n}\n\ninterface QueuedMessage {\n ctx: Context;\n accountId: string;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n// =============================================================================\n// Media Processing Types\n// =============================================================================\n\ninterface MediaItem {\n type: string;\n fileId: string;\n emoji?: string;\n}\n\ninterface ProcessedAttachment {\n type: string;\n mimeType: string;\n data: string;\n name?: string;\n size?: number;\n}\n\n// =============================================================================\n// Helper Functions (reduce nesting in main processor)\n// =============================================================================\n\n/**\n * Extract media items from message\n */\nfunction extractMediaItems(message: Message): MediaItem[] {\n const media: MediaItem[] = [];\n \n if (message.photo?.length) {\n media.push({ type: 'photo', fileId: message.photo[message.photo.length - 1].file_id });\n }\n if (message.document) {\n media.push({ type: 'document', fileId: message.document.file_id });\n }\n if (message.video) {\n media.push({ type: 'video', fileId: message.video.file_id });\n }\n if (message.audio) {\n media.push({ type: 'audio', fileId: message.audio.file_id });\n }\n if (message.voice) {\n media.push({ type: 'voice', fileId: message.voice.file_id });\n }\n if (message.sticker) {\n media.push({ type: 'sticker', fileId: message.sticker.file_id, emoji: message.sticker.emoji });\n }\n\n return media;\n}\n\n/**\n * Download + STT a Telegram voice message (for mention probe or media pipeline).\n */\nasync function downloadAndTranscribeTelegramVoice(params: {\n voice: NonNullable<Message['voice']>;\n bot: Bot;\n botToken: string;\n accountApiRoot: string;\n sttService: STTService;\n sttConfig: unknown;\n}): Promise<string> {\n const { voice, bot, botToken, accountApiRoot, sttService, sttConfig } = params;\n const voiceDuration = voice.duration || 0;\n if (voiceDuration > STT_MAX_VOICE_DURATION_SECONDS) {\n return '';\n }\n if (!sttService.isSTTAvailable(sttConfig)) {\n return '';\n }\n try {\n const file = await bot.api.getFile(voice.file_id);\n const downloadUrl = `${accountApiRoot}/file/bot${botToken}/${file.file_path}`;\n const response = await fetch(downloadUrl);\n if (!response.ok) {\n return '';\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n if (buffer.length === 0) {\n return '';\n }\n const sttResult = await sttService.transcribe(buffer, sttConfig, {\n language: (sttConfig as { provider?: string })?.provider === 'alibaba' ? 'zh' : undefined,\n });\n return sttResult.text?.trim() ?? '';\n } catch {\n return '';\n }\n}\n\n/**\n * Process a single media item (download + optional STT)\n */\nasync function processMediaItem(\n item: MediaItem,\n bot: Bot,\n botToken: string,\n accountApiRoot: string,\n message: Message,\n sttService: STTService,\n mediaUtils: MediaUtils,\n sttConfig: unknown,\n reuseVoiceTranscript?: string,\n): Promise<{ attachment: ProcessedAttachment | null; transcribedText: string }> {\n try {\n const file = await bot.api.getFile(item.fileId);\n const downloadUrl = `${accountApiRoot}/file/bot${botToken}/${file.file_path}`;\n const response = await fetch(downloadUrl);\n\n // Debug log for media download\n log.info({\n type: item.type,\n fileId: item.fileId,\n downloadUrl,\n responseStatus: response.status,\n }, 'Media download response');\n\n if (!response.ok) {\n throw new Error(`Failed to download: ${response.status}`);\n }\n\n const buffer = await response.arrayBuffer();\n\n // Debug log for buffer size\n log.info({\n type: item.type,\n fileId: item.fileId,\n bufferSize: buffer.byteLength,\n filePath: file.file_path,\n }, 'Media buffer downloaded');\n\n if (buffer.byteLength === 0) {\n log.warn({\n type: item.type,\n fileId: item.fileId,\n filePath: file.file_path,\n }, 'Media buffer is empty, may cause issues');\n }\n let transcribedText = '';\n\n // Handle voice messages with STT\n if (item.type === 'voice' && sttService.isSTTAvailable(sttConfig)) {\n if (reuseVoiceTranscript !== undefined && reuseVoiceTranscript !== '') {\n transcribedText = reuseVoiceTranscript;\n } else {\n const voiceDuration = message.voice?.duration || 0;\n if (voiceDuration <= STT_MAX_VOICE_DURATION_SECONDS) {\n try {\n const sttResult = await sttService.transcribe(Buffer.from(buffer), sttConfig, {\n language: (sttConfig as { provider?: string })?.provider === 'alibaba' ? 'zh' : undefined,\n });\n transcribedText = sttResult.text;\n } catch (sttError) {\n log.error({ sttError }, 'STT transcription failed');\n transcribedText = '[STT failed]';\n }\n } else {\n transcribedText = `[Voice message too long (>${STT_MAX_VOICE_DURATION_SECONDS}s)]`;\n }\n }\n }\n\n const base64 = Buffer.from(buffer).toString('base64');\n const mimeType = mediaUtils.getMimeType(item.type, file.file_path);\n\n // Debug log for attachment creation\n log.info({\n type: item.type,\n mimeType,\n base64Length: base64.length,\n size: buffer.byteLength,\n name: file.file_path?.split('/').pop(),\n }, 'Attachment created');\n\n return {\n attachment: {\n type: item.type,\n mimeType,\n data: base64,\n name: file.file_path?.split('/').pop(),\n size: buffer.byteLength,\n },\n transcribedText,\n };\n } catch (err) {\n log.error({ type: item.type, fileId: item.fileId, err }, 'Failed to download media');\n return { attachment: null, transcribedText: '' };\n }\n}\n\n/**\n * Process all media items\n */\nasync function processAllMedia(\n media: MediaItem[],\n bot: Bot,\n botToken: string,\n accountApiRoot: string,\n message: Message,\n sttService: STTService,\n mediaUtils: MediaUtils,\n sttConfig: unknown,\n reuseVoiceTranscript?: string,\n): Promise<{ attachments: ProcessedAttachment[]; transcribedText: string }> {\n const attachments: ProcessedAttachment[] = [];\n let transcribedText = '';\n let voiceReuseConsumed = false;\n\n for (const item of media) {\n const reuse =\n item.type === 'voice' && reuseVoiceTranscript && !voiceReuseConsumed\n ? reuseVoiceTranscript\n : undefined;\n if (reuse) {\n voiceReuseConsumed = true;\n }\n const result = await processMediaItem(\n item,\n bot,\n botToken,\n accountApiRoot,\n message,\n sttService,\n mediaUtils,\n sttConfig,\n reuse,\n );\n \n if (result.attachment) {\n attachments.push(result.attachment);\n }\n if (result.transcribedText) {\n transcribedText = result.transcribedText;\n }\n }\n\n return { attachments, transcribedText };\n}\n\n// =============================================================================\n// Main Processor Factory\n// =============================================================================\n\n/**\n * Create inbound message processor\n */\nexport function createInboundProcessor(deps: InboundProcessorDeps) {\n const {\n bus,\n config,\n accountManager,\n accessControl,\n sessionKeyService,\n sttService,\n mediaUtils,\n } = deps;\n\n const messageQueues = new Map<string, QueuedMessage[]>();\n const processingLocks = new Map<string, Promise<void>>();\n\n const getChatKey = (accountId: string, chatId: string): string => `${accountId}:${chatId}`;\n\n const processNextMessage = async (chatKey: string): Promise<void> => {\n if (processingLocks.has(chatKey)) return;\n\n const queue = messageQueues.get(chatKey);\n if (!queue || queue.length === 0) return;\n\n let lockResolve: () => void;\n const lockPromise = new Promise<void>((resolve) => {\n lockResolve = resolve;\n });\n processingLocks.set(chatKey, lockPromise);\n\n const { ctx, accountId, resolve, reject } = queue.shift()!;\n\n try {\n await processMessageInternal(ctx, accountId);\n resolve();\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)));\n } finally {\n lockResolve?.();\n processingLocks.delete(chatKey);\n if (queue.length > 0) {\n processNextMessage(chatKey);\n } else {\n messageQueues.delete(chatKey);\n }\n }\n };\n\n const processMessageInternal = async (ctx: Context, accountId: string) => {\n // Deduplication check\n const updateKey = buildTelegramUpdateKey(ctx);\n if (updateKey && telegramUpdateDedupe.checkAndAdd(updateKey)) {\n log.debug({ updateKey, accountId }, 'Duplicate update detected, skipping');\n return;\n }\n\n const account = accountManager.getAccount(accountId);\n if (!account) {\n log.warn({ accountId }, 'Account not found for message processing');\n return;\n }\n\n const botUsername = accountManager.getBotUsername(accountId);\n if (!botUsername) {\n log.warn({ accountId }, 'Bot username not available');\n return;\n }\n\n const message = ctx.message ?? ctx.channelPost;\n if (!message) return;\n\n const chatId = String(ctx.chat?.id);\n const senderId = String(ctx.from?.id);\n const senderUsername = ctx.from?.username;\n const content = message.text ?? message.caption ?? '';\n const isGroup = ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup';\n const threadId = (message as { message_thread_id?: number }).message_thread_id;\n\n // Access control (group/topic allowlists, mention gating)\n const effectiveAllowFrom = accessControl.normalizeAllowFromWithStore({\n allowFrom: isGroup ? account.groupAllowFrom : account.allowFrom,\n });\n\n const baseAccess = accessControl.evaluateGroupBaseAccess({\n isGroup,\n groupConfig: account.groups?.[chatId],\n topicConfig: threadId ? account.groups?.[chatId]?.topics?.[String(threadId)] : undefined,\n hasGroupAllowOverride: !!(account.groups?.[chatId]?.allowFrom ||\n (threadId && account.groups?.[chatId]?.topics?.[String(threadId)]?.allowFrom)),\n effectiveGroupAllow: effectiveAllowFrom,\n senderId,\n senderUsername,\n });\n\n if (!baseAccess.allowed) {\n log.debug({ accountId, chatId, reason: baseAccess.reason }, 'Message blocked by base access');\n return;\n }\n\n const botEarly = accountManager.getBot(accountId);\n const botTokenEarly = account.botToken;\n const accountApiRootEarly = account.apiRoot?.replace(/\\/$/, '') || 'https://api.telegram.org';\n\n let voiceProbeText = '';\n if (\n isGroup &&\n message.voice &&\n botEarly &&\n botTokenEarly &&\n sttService.isSTTAvailable(config?.tools?.media?.audio)\n ) {\n voiceProbeText = await downloadAndTranscribeTelegramVoice({\n voice: message.voice,\n bot: botEarly,\n botToken: botTokenEarly,\n accountApiRoot: accountApiRootEarly,\n sttService,\n sttConfig: config?.tools?.media?.audio,\n });\n }\n\n if (isGroup) {\n const requireMention = accessControl.resolveRequireMention({\n topicConfig: threadId ? account.groups?.[chatId]?.topics?.[String(threadId)] : undefined,\n groupConfig: account.groups?.[chatId],\n defaultRequireMention: true,\n });\n\n const hasMedia = !!(message.photo || message.document || message.video || message.audio || message.voice);\n const captionEntities = (message as any).caption_entities;\n const voiceMention = voiceProbeText.trim();\n const hasMention =\n accessControl.hasBotMention({ botUsername, text: content, entities: message.entities }) ||\n (hasMedia &&\n accessControl.hasBotMention({\n botUsername,\n text: message.caption ?? '',\n entities: captionEntities,\n })) ||\n (voiceMention.length > 0 &&\n (accessControl.hasBotMention({ botUsername, text: voiceMention }) ||\n checkMentionInTranscription(voiceMention, [botUsername])));\n\n if (requireMention && !hasMention) {\n const isVoiceOnly =\n !!message.voice &&\n !message.photo &&\n !message.document &&\n !message.video &&\n !message.audio &&\n !(message.caption ?? '').trim() &&\n !content.trim();\n\n if (isVoiceOnly) {\n log.debug({ accountId, chatId }, 'Group voice without mention after STT probe, ignored');\n return;\n }\n\n if (!hasMedia || content.trim().length > 0) {\n log.debug({ accountId, chatId }, 'Group message without mention ignored');\n return;\n }\n log.debug({ accountId, chatId }, 'Group media message without mention - processing anyway');\n }\n }\n\n const cleanContent = isGroup ? accessControl.removeBotMention(content, botUsername) : content;\n\n const groupCtx = resolveTelegramGroupContext({ account, chatId, threadId });\n const route = resolveRoute({\n config,\n channel: 'telegram',\n accountId,\n peerKind: isGroup ? 'group' : 'dm',\n peerId: isGroup ? chatId : senderId,\n threadId: threadId ? String(threadId) : null,\n });\n const routedAgentId = groupCtx.agentId ?? route.agentId;\n\n const defaultSessionKey = sessionKeyService.generateSessionKey({\n source: 'telegram',\n chatId,\n senderId,\n isGroup,\n threadId: threadId ? String(threadId) : undefined,\n accountId,\n agentId: routedAgentId,\n });\n\n const sessionKey = resolveTelegramFocusedSessionKey({\n chatId,\n threadId: threadId ? String(threadId) : undefined,\n defaultSessionKey,\n });\n\n const conversationId = buildTelegramConversationId(chatId, threadId);\n\n // Collect and process media\n const media = extractMediaItems(message);\n const bot = accountManager.getBot(accountId);\n const botToken = account.botToken;\n\n let attachments: ProcessedAttachment[] = [];\n let transcribedText = '';\n\n if (bot && botToken && media.length > 0) {\n const accountApiRoot = account.apiRoot?.replace(/\\/$/, '') || 'https://api.telegram.org';\n const mediaResult = await processAllMedia(\n media,\n bot,\n botToken,\n accountApiRoot,\n message,\n sttService,\n mediaUtils,\n config?.tools?.media?.audio,\n voiceProbeText || undefined,\n );\n attachments = mediaResult.attachments;\n transcribedText = mediaResult.transcribedText;\n }\n\n // Combine transcribed text with content. If the user caption is a slash command, put it first\n // so routing and logs match a normal `/cmd` message (STT prefix no longer hides the command).\n const captionIsCommand = cleanContent.trim().startsWith('/');\n let finalContent = transcribedText\n ? captionIsCommand && cleanContent\n ? cleanContent.trim() + (transcribedText ? `\\n\\n${transcribedText}` : '')\n : transcribedText + (cleanContent ? '\\n\\n' + cleanContent : '')\n : cleanContent;\n\n const stickerItem = media.find((m) => m.type === 'sticker');\n if (stickerItem && !finalContent.trim()) {\n finalContent = stickerItem.emoji?.trim()\n ? `[Sticker ${stickerItem.emoji}]`\n : '[Sticker]';\n }\n\n const isCommand = cleanContent.trim().startsWith('/');\n\n log.info({\n accountId,\n chatId,\n senderId,\n isGroup,\n sessionKey,\n contentLength: finalContent.length,\n attachmentCount: attachments.length,\n isCommand,\n }, 'Processing Telegram message');\n\n if (\n finalContent.trim().length > 0 &&\n !isCommand &&\n tryConsumeTelegramClarifyFreeText(sessionKey, finalContent.trim())\n ) {\n log.debug({ sessionKey }, 'Telegram: consumed message as clarify reply');\n return;\n }\n\n await bus.publishInbound({\n channel: 'telegram',\n sender_id: senderId,\n chat_id: chatId,\n content: finalContent,\n metadata: {\n accountId,\n sessionKey,\n senderUsername,\n messageId: String(message.message_id),\n isGroup,\n isCommand,\n threadId: threadId ? String(threadId) : undefined,\n conversationId,\n channelSystemPrompt: groupCtx.systemPrompt,\n channelAgentId: routedAgentId,\n media: media.length > 0 ? media : undefined,\n transcribedVoice: !!transcribedText || undefined,\n },\n attachments: attachments.length > 0 ? attachments : undefined,\n });\n };\n\n // Main enqueue function\n return (ctx: Context, accountId: string): Promise<void> => {\n const message = ctx.message;\n if (message) {\n const raw = message.text ?? message.caption ?? '';\n if (raw.trim() && isAbortSlashCommand(raw.trim())) {\n return processMessageInternal(ctx, accountId);\n }\n }\n\n const chatId = String(ctx.chat?.id);\n const chatKey = getChatKey(accountId, chatId);\n\n return new Promise((resolve, reject) => {\n const queue = messageQueues.get(chatKey) || [];\n queue.push({ ctx, accountId, resolve, reject });\n messageQueues.set(chatKey, queue);\n processNextMessage(chatKey);\n });\n };\n}\n"],"mappings":";;;;;;;;;;;;;aAkB4D;AAS5D,MAAM,MAAM,aAAa,2BAA2B;;AAGpD,SAAS,oBAAoB,MAAuB;CAClD,MAAM,SAAS,kBAAkB,KAAK;AACtC,KAAI,CAAC,OAAQ,QAAO;CACpB,MAAM,MAAM,6BAA6B,OAAO,QAAQ;AACxD,QAAO,QAAQ,WAAW,QAAQ,UAAU,QAAQ;;;AAItD,MAAM,iCAAiC;;;;AA8FvC,SAAS,kBAAkB,SAA+B;CACxD,MAAM,QAAqB,EAAE;AAE7B,KAAI,QAAQ,OAAO,OACjB,OAAM,KAAK;EAAE,MAAM;EAAS,QAAQ,QAAQ,MAAM,QAAQ,MAAM,SAAS,GAAG;EAAS,CAAC;AAExF,KAAI,QAAQ,SACV,OAAM,KAAK;EAAE,MAAM;EAAY,QAAQ,QAAQ,SAAS;EAAS,CAAC;AAEpE,KAAI,QAAQ,MACV,OAAM,KAAK;EAAE,MAAM;EAAS,QAAQ,QAAQ,MAAM;EAAS,CAAC;AAE9D,KAAI,QAAQ,MACV,OAAM,KAAK;EAAE,MAAM;EAAS,QAAQ,QAAQ,MAAM;EAAS,CAAC;AAE9D,KAAI,QAAQ,MACV,OAAM,KAAK;EAAE,MAAM;EAAS,QAAQ,QAAQ,MAAM;EAAS,CAAC;AAE9D,KAAI,QAAQ,QACV,OAAM,KAAK;EAAE,MAAM;EAAW,QAAQ,QAAQ,QAAQ;EAAS,OAAO,QAAQ,QAAQ;EAAO,CAAC;AAGhG,QAAO;;;;;AAMT,eAAe,mCAAmC,QAO9B;CAClB,MAAM,EAAE,OAAO,KAAK,UAAU,gBAAgB,YAAY,cAAc;AAExE,MADsB,MAAM,YAAY,KACpB,+BAClB,QAAO;AAET,KAAI,CAAC,WAAW,eAAe,UAAU,CACvC,QAAO;AAET,KAAI;EAEF,MAAM,cAAc,GAAG,eAAe,WAAW,SAAS,IAAG,MAD1C,IAAI,IAAI,QAAQ,MAAM,QAAQ,EACiB;EAClE,MAAM,WAAW,MAAM,MAAM,YAAY;AACzC,MAAI,CAAC,SAAS,GACZ,QAAO;EAET,MAAM,SAAS,OAAO,KAAK,MAAM,SAAS,aAAa,CAAC;AACxD,MAAI,OAAO,WAAW,EACpB,QAAO;AAKT,UAAO,MAHiB,WAAW,WAAW,QAAQ,WAAW,EAC/D,UAAW,WAAqC,aAAa,YAAY,OAAO,KAAA,GACjF,CAAC,EACe,MAAM,MAAM,IAAI;SAC3B;AACN,SAAO;;;;;;AAOX,eAAe,iBACb,MACA,KACA,UACA,gBACA,SACA,YACA,YACA,WACA,sBAC8E;AAC9E,KAAI;EACF,MAAM,OAAO,MAAM,IAAI,IAAI,QAAQ,KAAK,OAAO;EAC/C,MAAM,cAAc,GAAG,eAAe,WAAW,SAAS,GAAG,KAAK;EAClE,MAAM,WAAW,MAAM,MAAM,YAAY;AAGzC,MAAI,KAAK;GACP,MAAM,KAAK;GACX,QAAQ,KAAK;GACb;GACA,gBAAgB,SAAS;GAC1B,EAAE,0BAA0B;AAE7B,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,uBAAuB,SAAS,SAAS;EAG3D,MAAM,SAAS,MAAM,SAAS,aAAa;AAG3C,MAAI,KAAK;GACP,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,YAAY,OAAO;GACnB,UAAU,KAAK;GAChB,EAAE,0BAA0B;AAE7B,MAAI,OAAO,eAAe,EACxB,KAAI,KAAK;GACP,MAAM,KAAK;GACX,QAAQ,KAAK;GACb,UAAU,KAAK;GAChB,EAAE,0CAA0C;EAE/C,IAAI,kBAAkB;AAGtB,MAAI,KAAK,SAAS,WAAW,WAAW,eAAe,UAAU,CAC/D,KAAI,yBAAyB,KAAA,KAAa,yBAAyB,GACjE,mBAAkB;YAEI,QAAQ,OAAO,YAAY,MAC5B,+BACnB,KAAI;AAIF,sBAAkB,MAHM,WAAW,WAAW,OAAO,KAAK,OAAO,EAAE,WAAW,EAC5E,UAAW,WAAqC,aAAa,YAAY,OAAO,KAAA,GACjF,CAAC,EAC0B;WACrB,UAAU;AACjB,OAAI,MAAM,EAAE,UAAU,EAAE,2BAA2B;AACnD,qBAAkB;;MAGpB,mBAAkB,6BAA6B,+BAA+B;EAKpF,MAAM,SAAS,OAAO,KAAK,OAAO,CAAC,SAAS,SAAS;EACrD,MAAM,WAAW,WAAW,YAAY,KAAK,MAAM,KAAK,UAAU;AAGlE,MAAI,KAAK;GACP,MAAM,KAAK;GACX;GACA,cAAc,OAAO;GACrB,MAAM,OAAO;GACb,MAAM,KAAK,WAAW,MAAM,IAAI,CAAC,KAAK;GACvC,EAAE,qBAAqB;AAExB,SAAO;GACL,YAAY;IACV,MAAM,KAAK;IACX;IACA,MAAM;IACN,MAAM,KAAK,WAAW,MAAM,IAAI,CAAC,KAAK;IACtC,MAAM,OAAO;IACd;GACD;GACD;UACM,KAAK;AACZ,MAAI,MAAM;GAAE,MAAM,KAAK;GAAM,QAAQ,KAAK;GAAQ;GAAK,EAAE,2BAA2B;AACpF,SAAO;GAAE,YAAY;GAAM,iBAAiB;GAAI;;;;;;AAOpD,eAAe,gBACb,OACA,KACA,UACA,gBACA,SACA,YACA,YACA,WACA,sBAC0E;CAC1E,MAAM,cAAqC,EAAE;CAC7C,IAAI,kBAAkB;CACtB,IAAI,qBAAqB;AAEzB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QACJ,KAAK,SAAS,WAAW,wBAAwB,CAAC,qBAC9C,uBACA,KAAA;AACN,MAAI,MACF,sBAAqB;EAEvB,MAAM,SAAS,MAAM,iBACnB,MACA,KACA,UACA,gBACA,SACA,YACA,YACA,WACA,MACD;AAED,MAAI,OAAO,WACT,aAAY,KAAK,OAAO,WAAW;AAErC,MAAI,OAAO,gBACT,mBAAkB,OAAO;;AAI7B,QAAO;EAAE;EAAa;EAAiB;;;;;AAUzC,SAAgB,uBAAuB,MAA4B;CACjE,MAAM,EACJ,KACA,QACA,gBACA,eACA,mBACA,YACA,eACE;CAEJ,MAAM,gCAAgB,IAAI,KAA8B;CACxD,MAAM,kCAAkB,IAAI,KAA4B;CAExD,MAAM,cAAc,WAAmB,WAA2B,GAAG,UAAU,GAAG;CAElF,MAAM,qBAAqB,OAAO,YAAmC;AACnE,MAAI,gBAAgB,IAAI,QAAQ,CAAE;EAElC,MAAM,QAAQ,cAAc,IAAI,QAAQ;AACxC,MAAI,CAAC,SAAS,MAAM,WAAW,EAAG;EAElC,IAAI;EACJ,MAAM,cAAc,IAAI,SAAe,YAAY;AACjD,iBAAc;IACd;AACF,kBAAgB,IAAI,SAAS,YAAY;EAEzC,MAAM,EAAE,KAAK,WAAW,SAAS,WAAW,MAAM,OAAO;AAEzD,MAAI;AACF,SAAM,uBAAuB,KAAK,UAAU;AAC5C,YAAS;WACF,KAAK;AACZ,UAAO,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,CAAC;YACnD;AACR,kBAAe;AACf,mBAAgB,OAAO,QAAQ;AAC/B,OAAI,MAAM,SAAS,EACjB,oBAAmB,QAAQ;OAE3B,eAAc,OAAO,QAAQ;;;CAKnC,MAAM,yBAAyB,OAAO,KAAc,cAAsB;EAExE,MAAM,YAAY,uBAAuB,IAAI;AAC7C,MAAI,aAAa,qBAAqB,YAAY,UAAU,EAAE;AAC5D,OAAI,MAAM;IAAE;IAAW;IAAW,EAAE,sCAAsC;AAC1E;;EAGF,MAAM,UAAU,eAAe,WAAW,UAAU;AACpD,MAAI,CAAC,SAAS;AACZ,OAAI,KAAK,EAAE,WAAW,EAAE,2CAA2C;AACnE;;EAGF,MAAM,cAAc,eAAe,eAAe,UAAU;AAC5D,MAAI,CAAC,aAAa;AAChB,OAAI,KAAK,EAAE,WAAW,EAAE,6BAA6B;AACrD;;EAGF,MAAM,UAAU,IAAI,WAAW,IAAI;AACnC,MAAI,CAAC,QAAS;EAEd,MAAM,SAAS,OAAO,IAAI,MAAM,GAAG;EACnC,MAAM,WAAW,OAAO,IAAI,MAAM,GAAG;EACrC,MAAM,iBAAiB,IAAI,MAAM;EACjC,MAAM,UAAU,QAAQ,QAAQ,QAAQ,WAAW;EACnD,MAAM,UAAU,IAAI,MAAM,SAAS,WAAW,IAAI,MAAM,SAAS;EACjE,MAAM,WAAY,QAA2C;EAG7D,MAAM,qBAAqB,cAAc,4BAA4B,EACnE,WAAW,UAAU,QAAQ,iBAAiB,QAAQ,WACvD,CAAC;EAEF,MAAM,aAAa,cAAc,wBAAwB;GACvD;GACA,aAAa,QAAQ,SAAS;GAC9B,aAAa,WAAW,QAAQ,SAAS,SAAS,SAAS,OAAO,SAAS,IAAI,KAAA;GAC/E,uBAAuB,CAAC,EAAE,QAAQ,SAAS,SAAS,aACjD,YAAY,QAAQ,SAAS,SAAS,SAAS,OAAO,SAAS,GAAG;GACrE,qBAAqB;GACrB;GACA;GACD,CAAC;AAEF,MAAI,CAAC,WAAW,SAAS;AACvB,OAAI,MAAM;IAAE;IAAW;IAAQ,QAAQ,WAAW;IAAQ,EAAE,iCAAiC;AAC7F;;EAGF,MAAM,WAAW,eAAe,OAAO,UAAU;EACjD,MAAM,gBAAgB,QAAQ;EAC9B,MAAM,sBAAsB,QAAQ,SAAS,QAAQ,OAAO,GAAG,IAAI;EAEnE,IAAI,iBAAiB;AACrB,MACE,WACA,QAAQ,SACR,YACA,iBACA,WAAW,eAAe,QAAQ,OAAO,OAAO,MAAM,CAEtD,kBAAiB,MAAM,mCAAmC;GACxD,OAAO,QAAQ;GACf,KAAK;GACL,UAAU;GACV,gBAAgB;GAChB;GACA,WAAW,QAAQ,OAAO,OAAO;GAClC,CAAC;AAGJ,MAAI,SAAS;GACX,MAAM,iBAAiB,cAAc,sBAAsB;IACzD,aAAa,WAAW,QAAQ,SAAS,SAAS,SAAS,OAAO,SAAS,IAAI,KAAA;IAC/E,aAAa,QAAQ,SAAS;IAC9B,uBAAuB;IACxB,CAAC;GAEF,MAAM,WAAW,CAAC,EAAE,QAAQ,SAAS,QAAQ,YAAY,QAAQ,SAAS,QAAQ,SAAS,QAAQ;GACnG,MAAM,kBAAmB,QAAgB;GACzC,MAAM,eAAe,eAAe,MAAM;GAC1C,MAAM,aACJ,cAAc,cAAc;IAAE;IAAa,MAAM;IAAS,UAAU,QAAQ;IAAU,CAAC,IACtF,YACC,cAAc,cAAc;IAC1B;IACA,MAAM,QAAQ,WAAW;IACzB,UAAU;IACX,CAAC,IACH,aAAa,SAAS,MACpB,cAAc,cAAc;IAAE;IAAa,MAAM;IAAc,CAAC,IAC/D,4BAA4B,cAAc,CAAC,YAAY,CAAC;AAE9D,OAAI,kBAAkB,CAAC,YAAY;AAUjC,QARE,CAAC,CAAC,QAAQ,SACV,CAAC,QAAQ,SACT,CAAC,QAAQ,YACT,CAAC,QAAQ,SACT,CAAC,QAAQ,SACT,EAAE,QAAQ,WAAW,IAAI,MAAM,IAC/B,CAAC,QAAQ,MAAM,EAEA;AACf,SAAI,MAAM;MAAE;MAAW;MAAQ,EAAE,uDAAuD;AACxF;;AAGF,QAAI,CAAC,YAAY,QAAQ,MAAM,CAAC,SAAS,GAAG;AAC1C,SAAI,MAAM;MAAE;MAAW;MAAQ,EAAE,wCAAwC;AACzE;;AAEF,QAAI,MAAM;KAAE;KAAW;KAAQ,EAAE,0DAA0D;;;EAI/F,MAAM,eAAe,UAAU,cAAc,iBAAiB,SAAS,YAAY,GAAG;EAEtF,MAAM,WAAW,4BAA4B;GAAE;GAAS;GAAQ;GAAU,CAAC;EAC3E,MAAM,QAAQ,aAAa;GACzB;GACA,SAAS;GACT;GACA,UAAU,UAAU,UAAU;GAC9B,QAAQ,UAAU,SAAS;GAC3B,UAAU,WAAW,OAAO,SAAS,GAAG;GACzC,CAAC;EACF,MAAM,gBAAgB,SAAS,WAAW,MAAM;EAEhD,MAAM,oBAAoB,kBAAkB,mBAAmB;GAC7D,QAAQ;GACR;GACA;GACA;GACA,UAAU,WAAW,OAAO,SAAS,GAAG,KAAA;GACxC;GACA,SAAS;GACV,CAAC;EAEF,MAAM,aAAa,iCAAiC;GAClD;GACA,UAAU,WAAW,OAAO,SAAS,GAAG,KAAA;GACxC;GACD,CAAC;EAEF,MAAM,iBAAiB,4BAA4B,QAAQ,SAAS;EAGpE,MAAM,QAAQ,kBAAkB,QAAQ;EACxC,MAAM,MAAM,eAAe,OAAO,UAAU;EAC5C,MAAM,WAAW,QAAQ;EAEzB,IAAI,cAAqC,EAAE;EAC3C,IAAI,kBAAkB;AAEtB,MAAI,OAAO,YAAY,MAAM,SAAS,GAAG;GAEvC,MAAM,cAAc,MAAM,gBACxB,OACA,KACA,UAJqB,QAAQ,SAAS,QAAQ,OAAO,GAAG,IAAI,4BAM5D,SACA,YACA,YACA,QAAQ,OAAO,OAAO,OACtB,kBAAkB,KAAA,EACnB;AACD,iBAAc,YAAY;AAC1B,qBAAkB,YAAY;;EAKhC,MAAM,mBAAmB,aAAa,MAAM,CAAC,WAAW,IAAI;EAC5D,IAAI,eAAe,kBACf,oBAAoB,eAClB,aAAa,MAAM,IAAI,kBAAkB,OAAO,oBAAoB,MACpE,mBAAmB,eAAe,SAAS,eAAe,MAC5D;EAEJ,MAAM,cAAc,MAAM,MAAM,MAAM,EAAE,SAAS,UAAU;AAC3D,MAAI,eAAe,CAAC,aAAa,MAAM,CACrC,gBAAe,YAAY,OAAO,MAAM,GACpC,YAAY,YAAY,MAAM,KAC9B;EAGN,MAAM,YAAY,aAAa,MAAM,CAAC,WAAW,IAAI;AAErD,MAAI,KAAK;GACP;GACA;GACA;GACA;GACA;GACA,eAAe,aAAa;GAC5B,iBAAiB,YAAY;GAC7B;GACD,EAAE,8BAA8B;AAEjC,MACE,aAAa,MAAM,CAAC,SAAS,KAC7B,CAAC,aACD,kCAAkC,YAAY,aAAa,MAAM,CAAC,EAClE;AACA,OAAI,MAAM,EAAE,YAAY,EAAE,8CAA8C;AACxE;;AAGF,QAAM,IAAI,eAAe;GACvB,SAAS;GACT,WAAW;GACX,SAAS;GACT,SAAS;GACT,UAAU;IACR;IACA;IACA;IACA,WAAW,OAAO,QAAQ,WAAW;IACrC;IACA;IACA,UAAU,WAAW,OAAO,SAAS,GAAG,KAAA;IACxC;IACA,qBAAqB,SAAS;IAC9B,gBAAgB;IAChB,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;IAClC,kBAAkB,CAAC,CAAC,mBAAmB,KAAA;IACxC;GACD,aAAa,YAAY,SAAS,IAAI,cAAc,KAAA;GACrD,CAAC;;AAIJ,SAAQ,KAAc,cAAqC;EACzD,MAAM,UAAU,IAAI;AACpB,MAAI,SAAS;GACX,MAAM,MAAM,QAAQ,QAAQ,QAAQ,WAAW;AAC/C,OAAI,IAAI,MAAM,IAAI,oBAAoB,IAAI,MAAM,CAAC,CAC/C,QAAO,uBAAuB,KAAK,UAAU;;EAKjD,MAAM,UAAU,WAAW,WADZ,OAAO,IAAI,MAAM,GACY,CAAC;AAE7C,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,QAAQ,cAAc,IAAI,QAAQ,IAAI,EAAE;AAC9C,SAAM,KAAK;IAAE;IAAK;IAAW;IAAS;IAAQ,CAAC;AAC/C,iBAAc,IAAI,SAAS,MAAM;AACjC,sBAAmB,QAAQ;IAC3B"}
|
|
1
|
+
{"version":3,"file":"inbound-processor.js","names":[],"sources":["../../../../extensions/telegram/src/inbound-processor.ts"],"sourcesContent":["/**\n * Telegram Inbound Processor\n *\n * Handles inbound message processing from Telegram:\n * - Message queuing (per chat)\n * - Deduplication\n * - Access control\n * - Media download\n * - STT transcription\n * - Event publishing to bus\n */\n\nimport type { Bot, Context } from 'grammy';\nimport type { Message } from '@grammyjs/types';\nimport type { Config } from '@xopcai/xopc/config/schema.js';\nimport type { MessageBus } from '@xopcai/xopc/infra/bus/index.js';\nimport type { TelegramAccountManager } from './account-manager.js';\nimport { telegramUpdateDedupe, buildTelegramUpdateKey } from './dedupe.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { normalizeTelegramCommandName, parseSlashCommand } from '@xopcai/xopc/chat-commands/command-parse.js';\nimport { tryConsumeTelegramClarifyFreeText } from '@xopcai/xopc/gateway/clarify-runtime.js';\nimport { resolveRoute } from '@xopcai/xopc/routing/index.js';\nimport { resolveTelegramGroupContext } from './group-config-resolver.js';\nimport { resolveTelegramFocusedSessionKey } from './focus-handler.js';\nimport { buildTelegramConversationId } from './conversation-id.js';\nimport { checkMentionInTranscription } from '@xopcai/xopc/voice/stt/mention.js';\n\nconst log = createLogger('TelegramInboundProcessor');\n\n/** Must bypass the per-chat queue so /abort can cancel an in-flight turn. */\nfunction isAbortSlashCommand(text: string): boolean {\n const parsed = parseSlashCommand(text);\n if (!parsed) return false;\n const cmd = normalizeTelegramCommandName(parsed.command);\n return cmd === 'abort' || cmd === 'stop' || cmd === 'cancel';\n}\n\n/** Maximum voice message duration for STT in seconds */\nconst STT_MAX_VOICE_DURATION_SECONDS = 60;\n\n// =============================================================================\n// External Service Interfaces (for dependency injection)\n// =============================================================================\n\nexport interface AccessControlService {\n normalizeAllowFromWithStore(options: { allowFrom?: Array<string | number> }): unknown;\n evaluateGroupBaseAccess(options: {\n isGroup: boolean;\n groupConfig?: unknown;\n topicConfig?: unknown;\n hasGroupAllowOverride: boolean;\n effectiveGroupAllow: unknown;\n senderId?: string;\n senderUsername?: string;\n }): { allowed: boolean; reason?: string };\n resolveRequireMention(options: {\n topicConfig?: unknown;\n groupConfig?: unknown;\n defaultRequireMention?: boolean;\n }): boolean;\n hasBotMention(options: { botUsername: string; text?: string; entities?: unknown }): boolean;\n removeBotMention(text: string, botUsername: string): string;\n}\n\nexport interface SessionKeyService {\n generateSessionKey(options: {\n source: string;\n chatId: string;\n senderId: string;\n isGroup: boolean;\n threadId?: string;\n accountId?: string;\n agentId?: string;\n }): string;\n}\n\nexport interface STTService {\n transcribe(buffer: Buffer, config: unknown, options?: { language?: string }): Promise<{ text: string }>;\n isSTTAvailable(config: unknown): boolean;\n}\n\nexport interface MediaUtils {\n getMimeType(type: string, filePath?: string): string;\n}\n\n// =============================================================================\n// Dependencies Interface\n// =============================================================================\n\nexport interface InboundProcessorDeps {\n bus: MessageBus;\n config: Config;\n accountManager: TelegramAccountManager;\n // External services (injected for testability)\n accessControl: AccessControlService;\n sessionKeyService: SessionKeyService;\n sttService: STTService;\n mediaUtils: MediaUtils;\n}\n\ninterface QueuedMessage {\n ctx: Context;\n accountId: string;\n resolve: () => void;\n reject: (err: Error) => void;\n}\n\n// =============================================================================\n// Media Processing Types\n// =============================================================================\n\ninterface MediaItem {\n type: string;\n fileId: string;\n emoji?: string;\n}\n\ninterface ProcessedAttachment {\n type: string;\n mimeType: string;\n data: string;\n name?: string;\n size?: number;\n}\n\n// =============================================================================\n// Helper Functions (reduce nesting in main processor)\n// =============================================================================\n\n/**\n * Extract media items from message\n */\nfunction extractMediaItems(message: Message): MediaItem[] {\n const media: MediaItem[] = [];\n \n if (message.photo?.length) {\n media.push({ type: 'photo', fileId: message.photo[message.photo.length - 1].file_id });\n }\n if (message.document) {\n media.push({ type: 'document', fileId: message.document.file_id });\n }\n if (message.video) {\n media.push({ type: 'video', fileId: message.video.file_id });\n }\n if (message.audio) {\n media.push({ type: 'audio', fileId: message.audio.file_id });\n }\n if (message.voice) {\n media.push({ type: 'voice', fileId: message.voice.file_id });\n }\n if (message.sticker) {\n media.push({ type: 'sticker', fileId: message.sticker.file_id, emoji: message.sticker.emoji });\n }\n\n return media;\n}\n\n/**\n * Download + STT a Telegram voice message (for mention probe or media pipeline).\n */\nasync function downloadAndTranscribeTelegramVoice(params: {\n voice: NonNullable<Message['voice']>;\n bot: Bot;\n botToken: string;\n accountApiRoot: string;\n sttService: STTService;\n sttConfig: unknown;\n}): Promise<string> {\n const { voice, bot, botToken, accountApiRoot, sttService, sttConfig } = params;\n const voiceDuration = voice.duration || 0;\n if (voiceDuration > STT_MAX_VOICE_DURATION_SECONDS) {\n return '';\n }\n if (!sttService.isSTTAvailable(sttConfig)) {\n return '';\n }\n try {\n const file = await bot.api.getFile(voice.file_id);\n const downloadUrl = `${accountApiRoot}/file/bot${botToken}/${file.file_path}`;\n const response = await fetch(downloadUrl);\n if (!response.ok) {\n return '';\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n if (buffer.length === 0) {\n return '';\n }\n const sttResult = await sttService.transcribe(buffer, sttConfig, {\n language: (sttConfig as { provider?: string })?.provider === 'alibaba' ? 'zh' : undefined,\n });\n return sttResult.text?.trim() ?? '';\n } catch {\n return '';\n }\n}\n\n/**\n * Process a single media item (download + optional STT)\n */\nasync function processMediaItem(\n item: MediaItem,\n bot: Bot,\n botToken: string,\n accountApiRoot: string,\n message: Message,\n sttService: STTService,\n mediaUtils: MediaUtils,\n sttConfig: unknown,\n reuseVoiceTranscript?: string,\n): Promise<{ attachment: ProcessedAttachment | null; transcribedText: string }> {\n try {\n const file = await bot.api.getFile(item.fileId);\n const downloadUrl = `${accountApiRoot}/file/bot${botToken}/${file.file_path}`;\n const response = await fetch(downloadUrl);\n\n // Debug log for media download\n log.info({\n type: item.type,\n fileId: item.fileId,\n downloadUrl,\n responseStatus: response.status,\n }, 'Media download response');\n\n if (!response.ok) {\n throw new Error(`Failed to download: ${response.status}`);\n }\n\n const buffer = await response.arrayBuffer();\n\n // Debug log for buffer size\n log.info({\n type: item.type,\n fileId: item.fileId,\n bufferSize: buffer.byteLength,\n filePath: file.file_path,\n }, 'Media buffer downloaded');\n\n if (buffer.byteLength === 0) {\n log.warn({\n type: item.type,\n fileId: item.fileId,\n filePath: file.file_path,\n }, 'Media buffer is empty, may cause issues');\n }\n let transcribedText = '';\n\n // Handle voice messages with STT\n if (item.type === 'voice' && sttService.isSTTAvailable(sttConfig)) {\n if (reuseVoiceTranscript !== undefined && reuseVoiceTranscript !== '') {\n transcribedText = reuseVoiceTranscript;\n } else {\n const voiceDuration = message.voice?.duration || 0;\n if (voiceDuration <= STT_MAX_VOICE_DURATION_SECONDS) {\n try {\n const sttResult = await sttService.transcribe(Buffer.from(buffer), sttConfig, {\n language: (sttConfig as { provider?: string })?.provider === 'alibaba' ? 'zh' : undefined,\n });\n transcribedText = sttResult.text;\n } catch (sttError) {\n log.error({ sttError }, 'STT transcription failed');\n transcribedText = '[STT failed]';\n }\n } else {\n transcribedText = `[Voice message too long (>${STT_MAX_VOICE_DURATION_SECONDS}s)]`;\n }\n }\n }\n\n const base64 = Buffer.from(buffer).toString('base64');\n const mimeType = mediaUtils.getMimeType(item.type, file.file_path);\n\n // Debug log for attachment creation\n log.info({\n type: item.type,\n mimeType,\n base64Length: base64.length,\n size: buffer.byteLength,\n name: file.file_path?.split('/').pop(),\n }, 'Attachment created');\n\n return {\n attachment: {\n type: item.type,\n mimeType,\n data: base64,\n name: file.file_path?.split('/').pop(),\n size: buffer.byteLength,\n },\n transcribedText,\n };\n } catch (err) {\n log.error({ type: item.type, fileId: item.fileId, err }, 'Failed to download media');\n return { attachment: null, transcribedText: '' };\n }\n}\n\n/**\n * Process all media items\n */\nasync function processAllMedia(\n media: MediaItem[],\n bot: Bot,\n botToken: string,\n accountApiRoot: string,\n message: Message,\n sttService: STTService,\n mediaUtils: MediaUtils,\n sttConfig: unknown,\n reuseVoiceTranscript?: string,\n): Promise<{ attachments: ProcessedAttachment[]; transcribedText: string }> {\n const attachments: ProcessedAttachment[] = [];\n let transcribedText = '';\n let voiceReuseConsumed = false;\n\n for (const item of media) {\n const reuse =\n item.type === 'voice' && reuseVoiceTranscript && !voiceReuseConsumed\n ? reuseVoiceTranscript\n : undefined;\n if (reuse) {\n voiceReuseConsumed = true;\n }\n const result = await processMediaItem(\n item,\n bot,\n botToken,\n accountApiRoot,\n message,\n sttService,\n mediaUtils,\n sttConfig,\n reuse,\n );\n \n if (result.attachment) {\n attachments.push(result.attachment);\n }\n if (result.transcribedText) {\n transcribedText = result.transcribedText;\n }\n }\n\n return { attachments, transcribedText };\n}\n\n// =============================================================================\n// Main Processor Factory\n// =============================================================================\n\n/**\n * Create inbound message processor\n */\nexport function createInboundProcessor(deps: InboundProcessorDeps) {\n const {\n bus,\n config,\n accountManager,\n accessControl,\n sessionKeyService,\n sttService,\n mediaUtils,\n } = deps;\n\n const messageQueues = new Map<string, QueuedMessage[]>();\n const processingLocks = new Map<string, Promise<void>>();\n\n const getChatKey = (accountId: string, chatId: string): string => `${accountId}:${chatId}`;\n\n const processNextMessage = async (chatKey: string): Promise<void> => {\n if (processingLocks.has(chatKey)) return;\n\n const queue = messageQueues.get(chatKey);\n if (!queue || queue.length === 0) return;\n\n let lockResolve: () => void;\n const lockPromise = new Promise<void>((resolve) => {\n lockResolve = resolve;\n });\n processingLocks.set(chatKey, lockPromise);\n\n const { ctx, accountId, resolve, reject } = queue.shift()!;\n\n try {\n await processMessageInternal(ctx, accountId);\n resolve();\n } catch (err) {\n reject(err instanceof Error ? err : new Error(String(err)));\n } finally {\n lockResolve?.();\n processingLocks.delete(chatKey);\n if (queue.length > 0) {\n processNextMessage(chatKey);\n } else {\n messageQueues.delete(chatKey);\n }\n }\n };\n\n const processMessageInternal = async (ctx: Context, accountId: string) => {\n // Deduplication check\n const updateKey = buildTelegramUpdateKey(ctx);\n if (updateKey && telegramUpdateDedupe.checkAndAdd(updateKey)) {\n log.debug({ updateKey, accountId }, 'Duplicate update detected, skipping');\n return;\n }\n\n const account = accountManager.getAccount(accountId);\n if (!account) {\n log.warn({ accountId }, 'Account not found for message processing');\n return;\n }\n\n const botUsername = accountManager.getBotUsername(accountId);\n if (!botUsername) {\n log.warn({ accountId }, 'Bot username not available');\n return;\n }\n\n const message = ctx.message ?? ctx.channelPost;\n if (!message) return;\n\n const chatId = String(ctx.chat?.id);\n const senderId = String(ctx.from?.id);\n const senderUsername = ctx.from?.username;\n const content = message.text ?? message.caption ?? '';\n const isGroup = ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup';\n const threadId = (message as { message_thread_id?: number }).message_thread_id;\n\n // Access control (group/topic allowlists, mention gating)\n const effectiveAllowFrom = accessControl.normalizeAllowFromWithStore({\n allowFrom: isGroup ? account.groupAllowFrom : account.allowFrom,\n });\n\n const baseAccess = accessControl.evaluateGroupBaseAccess({\n isGroup,\n groupConfig: account.groups?.[chatId],\n topicConfig: threadId ? account.groups?.[chatId]?.topics?.[String(threadId)] : undefined,\n hasGroupAllowOverride: !!(account.groups?.[chatId]?.allowFrom ||\n (threadId && account.groups?.[chatId]?.topics?.[String(threadId)]?.allowFrom)),\n effectiveGroupAllow: effectiveAllowFrom,\n senderId,\n senderUsername,\n });\n\n if (!baseAccess.allowed) {\n log.debug({ accountId, chatId, reason: baseAccess.reason }, 'Message blocked by base access');\n return;\n }\n\n const botEarly = accountManager.getBot(accountId);\n const botTokenEarly = account.botToken;\n const accountApiRootEarly = account.apiRoot?.replace(/\\/$/, '') || 'https://api.telegram.org';\n\n let voiceProbeText = '';\n if (\n isGroup &&\n message.voice &&\n botEarly &&\n botTokenEarly &&\n sttService.isSTTAvailable(config?.tools?.media?.audio)\n ) {\n voiceProbeText = await downloadAndTranscribeTelegramVoice({\n voice: message.voice,\n bot: botEarly,\n botToken: botTokenEarly,\n accountApiRoot: accountApiRootEarly,\n sttService,\n sttConfig: config?.tools?.media?.audio,\n });\n }\n\n if (isGroup) {\n const requireMention = accessControl.resolveRequireMention({\n topicConfig: threadId ? account.groups?.[chatId]?.topics?.[String(threadId)] : undefined,\n groupConfig: account.groups?.[chatId],\n defaultRequireMention: true,\n });\n\n const hasMedia = !!(message.photo || message.document || message.video || message.audio || message.voice);\n const captionEntities = (message as any).caption_entities;\n const voiceMention = voiceProbeText.trim();\n const hasMention =\n accessControl.hasBotMention({ botUsername, text: content, entities: message.entities }) ||\n (hasMedia &&\n accessControl.hasBotMention({\n botUsername,\n text: message.caption ?? '',\n entities: captionEntities,\n })) ||\n (voiceMention.length > 0 &&\n (accessControl.hasBotMention({ botUsername, text: voiceMention }) ||\n checkMentionInTranscription(voiceMention, [botUsername])));\n\n if (requireMention && !hasMention) {\n const isVoiceOnly =\n !!message.voice &&\n !message.photo &&\n !message.document &&\n !message.video &&\n !message.audio &&\n !(message.caption ?? '').trim() &&\n !content.trim();\n\n if (isVoiceOnly) {\n log.debug({ accountId, chatId }, 'Group voice without mention after STT probe, ignored');\n return;\n }\n\n if (!hasMedia || content.trim().length > 0) {\n log.debug({ accountId, chatId }, 'Group message without mention ignored');\n return;\n }\n log.debug({ accountId, chatId }, 'Group media message without mention - processing anyway');\n }\n }\n\n const cleanContent = isGroup ? accessControl.removeBotMention(content, botUsername) : content;\n\n const groupCtx = resolveTelegramGroupContext({ account, chatId, threadId });\n const route = resolveRoute({\n config,\n channel: 'telegram',\n accountId,\n peerKind: isGroup ? 'group' : 'dm',\n peerId: isGroup ? chatId : senderId,\n threadId: threadId ? String(threadId) : null,\n });\n const routedAgentId = groupCtx.agentId ?? route.agentId;\n\n const defaultSessionKey = sessionKeyService.generateSessionKey({\n source: 'telegram',\n chatId,\n senderId,\n isGroup,\n threadId: threadId ? String(threadId) : undefined,\n accountId,\n agentId: routedAgentId,\n });\n\n const sessionKey = resolveTelegramFocusedSessionKey({\n chatId,\n threadId: threadId ? String(threadId) : undefined,\n defaultSessionKey,\n });\n\n const conversationId = buildTelegramConversationId(chatId, threadId);\n\n // Collect and process media\n const media = extractMediaItems(message);\n const bot = accountManager.getBot(accountId);\n const botToken = account.botToken;\n\n let attachments: ProcessedAttachment[] = [];\n let transcribedText = '';\n\n if (bot && botToken && media.length > 0) {\n const accountApiRoot = account.apiRoot?.replace(/\\/$/, '') || 'https://api.telegram.org';\n const mediaResult = await processAllMedia(\n media,\n bot,\n botToken,\n accountApiRoot,\n message,\n sttService,\n mediaUtils,\n config?.tools?.media?.audio,\n voiceProbeText || undefined,\n );\n attachments = mediaResult.attachments;\n transcribedText = mediaResult.transcribedText;\n }\n\n // Combine transcribed text with content. If the user caption is a slash command, put it first\n // so routing and logs match a normal `/cmd` message (STT prefix no longer hides the command).\n const captionIsCommand = cleanContent.trim().startsWith('/');\n let finalContent = transcribedText\n ? captionIsCommand && cleanContent\n ? cleanContent.trim() + (transcribedText ? `\\n\\n${transcribedText}` : '')\n : transcribedText + (cleanContent ? '\\n\\n' + cleanContent : '')\n : cleanContent;\n\n const stickerItem = media.find((m) => m.type === 'sticker');\n if (stickerItem && !finalContent.trim()) {\n finalContent = stickerItem.emoji?.trim()\n ? `[Sticker ${stickerItem.emoji}]`\n : '[Sticker]';\n }\n\n const isCommand = cleanContent.trim().startsWith('/');\n\n log.info({\n accountId,\n chatId,\n senderId,\n isGroup,\n sessionKey,\n contentLength: finalContent.length,\n attachmentCount: attachments.length,\n isCommand,\n }, 'Processing Telegram message');\n\n if (\n finalContent.trim().length > 0 &&\n !isCommand &&\n tryConsumeTelegramClarifyFreeText(sessionKey, finalContent.trim())\n ) {\n log.debug({ sessionKey }, 'Telegram: consumed message as clarify reply');\n return;\n }\n\n await bus.publishInbound({\n channel: 'telegram',\n sender_id: senderId,\n chat_id: chatId,\n content: finalContent,\n metadata: {\n accountId,\n sessionKey,\n senderUsername,\n messageId: String(message.message_id),\n isGroup,\n isCommand,\n threadId: threadId ? String(threadId) : undefined,\n conversationId,\n channelSystemPrompt: groupCtx.systemPrompt,\n channelAgentId: routedAgentId,\n media: media.length > 0 ? media : undefined,\n transcribedVoice: !!transcribedText || undefined,\n },\n attachments: attachments.length > 0 ? attachments : undefined,\n });\n };\n\n // Main enqueue function\n return (ctx: Context, accountId: string): Promise<void> => {\n const message = ctx.message;\n if (message) {\n const raw = message.text ?? message.caption ?? '';\n if (raw.trim() && isAbortSlashCommand(raw.trim())) {\n return processMessageInternal(ctx, accountId);\n }\n }\n\n const chatId = String(ctx.chat?.id);\n const chatKey = getChatKey(accountId, chatId);\n\n return new Promise((resolve, reject) => {\n const queue = messageQueues.get(chatKey) || [];\n queue.push({ ctx, accountId, resolve, reject });\n messageQueues.set(chatKey, queue);\n processNextMessage(chatKey);\n });\n };\n}\n"],"mappings":"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"}
|
|
@@ -75,6 +75,7 @@ export declare class TelegramChannelPlugin implements ChannelPlugin<TelegramReso
|
|
|
75
75
|
streaming: ChannelStreamingAdapter;
|
|
76
76
|
commands: ChannelCommandAdapter;
|
|
77
77
|
init(options: ChannelPluginInitOptions): Promise<void>;
|
|
78
|
+
private registerWorkflowProgressCapability;
|
|
78
79
|
private bindOutboundComponents;
|
|
79
80
|
onConfigUpdated(cfg: Config): Promise<void>;
|
|
80
81
|
private reapplyFromConfig;
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
import { createLogger } from "../../../src/utils/logger/index.js";
|
|
2
2
|
import { init_logger } from "../../../src/utils/logger.js";
|
|
3
3
|
import { TelegramConfigSchema, init_config_schema } from "./config-schema.js";
|
|
4
|
-
import { getWorkflowProgressBroker } from "../../../src/agent/workflow/progress-broker.js";
|
|
5
|
-
import "../../../src/agent/workflow/index.js";
|
|
6
4
|
import { registerChannelExecApprovalHandler } from "../../../src/channels/exec-approval-runtime.js";
|
|
7
|
-
import { transcribe } from "../../../src/voice/stt/transcribe-core.js";
|
|
8
5
|
import { isSTTAvailable } from "../../../src/voice/stt/availability.js";
|
|
9
|
-
import "../../../src/voice/stt/index.js";
|
|
10
6
|
import { generateSessionKey } from "../../../src/chat-commands/session-key.js";
|
|
11
7
|
import { submitClarifyChoiceFromChannel } from "../../../src/gateway/clarify-runtime.js";
|
|
12
8
|
import { resolveStandardPairingPath } from "../../../src/channels/pairing/paths.js";
|
|
@@ -17,7 +13,6 @@ import { createInboundDebouncer } from "../../../src/infra/debounce.js";
|
|
|
17
13
|
import { getMimeType } from "../../../src/channels/media.js";
|
|
18
14
|
import { TelegramAccountManager } from "./account-manager.js";
|
|
19
15
|
import { createOutboundSender } from "./outbound-sender.js";
|
|
20
|
-
import { createTelegramWorkflowProgressCapability } from "./workflow-progress.js";
|
|
21
16
|
import { createTelegramCommandHandler } from "./command-handler.js";
|
|
22
17
|
import { handleTelegramFocusCommand } from "./focus-handler.js";
|
|
23
18
|
import { createInboundProcessor } from "./inbound-processor.js";
|
|
@@ -156,7 +151,7 @@ var TelegramChannelPlugin = class {
|
|
|
156
151
|
accountManager: this.accountManager,
|
|
157
152
|
getConfig: () => this.cfg
|
|
158
153
|
}));
|
|
159
|
-
this.
|
|
154
|
+
await this.registerWorkflowProgressCapability();
|
|
160
155
|
const debounceMs = this.defaults.queue?.debounceMs ?? TELEGRAM_CHANNEL_DEFAULTS.queue.debounceMs;
|
|
161
156
|
const keyPolicy = telegramDebouncerKeyPolicy();
|
|
162
157
|
this.debouncer = createInboundDebouncer({
|
|
@@ -174,6 +169,10 @@ var TelegramChannelPlugin = class {
|
|
|
174
169
|
});
|
|
175
170
|
log.debug("Telegram plugin initialized");
|
|
176
171
|
}
|
|
172
|
+
async registerWorkflowProgressCapability() {
|
|
173
|
+
const [{ getWorkflowProgressBroker }, { createTelegramWorkflowProgressCapability }] = await Promise.all([import("../../../src/agent/workflow/progress-broker.js"), import("./workflow-progress.js")]);
|
|
174
|
+
this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(createTelegramWorkflowProgressCapability(this.accountManager));
|
|
175
|
+
}
|
|
177
176
|
bindOutboundComponents() {
|
|
178
177
|
this.outboundSender = createOutboundSender({
|
|
179
178
|
accountManager: this.accountManager,
|
|
@@ -210,6 +209,7 @@ var TelegramChannelPlugin = class {
|
|
|
210
209
|
}) },
|
|
211
210
|
sttService: {
|
|
212
211
|
transcribe: async (buffer, config, options) => {
|
|
212
|
+
const { transcribe } = await import("../../../src/voice/stt/transcribe-core.js");
|
|
213
213
|
return { text: (await transcribe(buffer, config, options)).text };
|
|
214
214
|
},
|
|
215
215
|
isSTTAvailable: (config) => isSTTAvailable(config)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["sttTranscribe"],"sources":["../../../../extensions/telegram/src/plugin.ts"],"sourcesContent":["/**\n * Telegram Channel Plugin - Implementation based on ChannelPlugin interface\n *\n * This plugin integrates Telegram with the ChannelPlugin architecture.\n * It reuses components from this package's src tree where possible.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport { Bot, type Context } from 'grammy';\n\nimport type { Config } from '@xopcai/xopc/config/index.js';\nimport type {\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginSessionModelHooks,\n ChannelPluginStartOptions,\n ChannelOutboundAdapter,\n ChannelSecurityContext,\n ChannelGatewayAdapter,\n ChannelStreamingAdapter,\n ChannelCommandAdapter,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport { generateSessionKey } from '@xopcai/xopc/chat-commands/session-key.js';\nimport { submitClarifyChoiceFromChannel } from '@xopcai/xopc/gateway/clarify-runtime.js';\n\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { issuePairingChallenge, resolveStandardPairingPath } from '@xopcai/xopc/channels/pairing/index.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\nimport { createTimeoutAbortSignal } from './timeout-abort.js';\nimport { createInboundDebouncer } from '@xopcai/xopc/infra/debounce.js';\nimport { getMimeType } from '@xopcai/xopc/channels/media.js';\nimport { transcribe as sttTranscribe, isSTTAvailable } from '@xopcai/xopc/voice/stt/index.js';\nimport type { STTConfig } from '@xopcai/xopc/voice/stt/types.js';\n\nimport { TelegramAccountManager } from './account-manager.js';\nimport { createOutboundSender } from './outbound-sender.js';\nimport { getWorkflowProgressBroker } from '@xopcai/xopc/agent/workflow/index.js';\nimport { createTelegramWorkflowProgressCapability } from './workflow-progress.js';\nimport { createTelegramCommandHandler } from './command-handler.js';\nimport { createInboundProcessor } from './inbound-processor.js';\nimport { TELEGRAM_CHANNEL_DEFAULTS } from './plugin-defaults.js';\nimport {\n createTelegramPluginAdapters,\n createTelegramSetupWizard,\n createTelegramOutboundSendMethods,\n telegramTextChunker,\n TELEGRAM_OUTBOUND_DEFAULTS,\n createTelegramInboundAccessControl,\n telegramDebouncerKeyPolicy,\n type TelegramMessageEvent,\n} from './adapters/index.js';\nimport {\n createTelegramGatewayAdapter,\n createTelegramStreamingAdapter,\n createTelegramCommandAdapter,\n} from './channel.js';\nimport type { TelegramResolvedAccount } from './adapters/index.js';\nimport type { ChannelCronDeliveryAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { normalizeTelegramDeliveryChatId } from './delivery-chat-id.js';\nimport { telegramOnboardAdapter } from './adapters/onboard-cli.js';\nimport { telegramGatewaySetupActions } from './adapters/gateway-setup.js';\nimport { TelegramConfigSchema } from './config-schema.js';\nimport { normalizeTelegramApiRoot } from './api-root.js';\nimport { resolveTelegramBotToken } from './token-resolver.js';\nimport { formatTelegramStartupError, isTelegramUnauthorizedTokenError } from './startup-errors.js';\nimport { startTelegramPollingSession } from './polling-session.js';\nimport { createProxyFetch } from './proxy-fetch.js';\nimport { runTelegramDoctorChecks } from './doctor.js';\nimport { telegramReplyTracker } from './reply-params.js';\nimport { createTelegramInboundCoalescer } from './inbound-coalescer.js';\nimport { sendTelegramAckReaction, handleTelegramMessageReaction } from './reactions.js';\nimport { handleTelegramChannelAction, bindTelegramMessageActionAccountManager } from './actions/message-actions.js';\nimport { registerChannelExecApprovalHandler } from '@xopcai/xopc/channels/exec-approval-runtime.js';\nimport { createTelegramExecApprovalHandler } from './exec-approval-handler.js';\nimport { handleTelegramFocusCommand } from './focus-handler.js';\nimport { resolveTelegramApproval } from './approval-store.js';\nimport { isTelegramExecApprovalApprover } from './exec-approvals.js';\n\n/** Bound initial `getMe` so a bad `apiRoot` or unreachable API cannot block gateway startup for minutes. */\nconst TELEGRAM_GETME_TIMEOUT_MS = 20_000;\n/** grammY per-request ceiling; must exceed long-poll `getUpdates` (~30s) but avoid multi-minute hangs on bad hosts. */\nconst TELEGRAM_CLIENT_TIMEOUT_SECONDS = 75;\n\nconst log = createLogger('TelegramPlugin');\n\nfunction contextWithMessage(ctx: Context, message: import('@grammyjs/types').Message): Context {\n if (ctx.message === message) return ctx;\n\n const clone = Object.create(Object.getPrototypeOf(ctx)) as Context;\n Object.defineProperties(clone, Object.getOwnPropertyDescriptors(ctx));\n Object.defineProperty(clone, 'message', {\n configurable: true,\n enumerable: true,\n value: message,\n writable: true,\n });\n return clone;\n}\n\nexport type { TelegramResolvedAccount as TelegramAccount } from './channel.js';\n\nexport class TelegramChannelPlugin implements ChannelPlugin<TelegramResolvedAccount> {\n readonly id = 'telegram' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.telegram'],\n };\n\n readonly meta = {\n id: 'telegram',\n label: 'Telegram',\n selectionLabel: 'Telegram Bot',\n docsPath: '/channels/telegram',\n blurb: 'Telegram Bot API channel',\n order: 0,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities = {\n chatTypes: ['direct', 'group', 'channel', 'thread'] as Array<'direct' | 'group' | 'channel' | 'thread'>,\n reactions: true,\n threads: true,\n media: true,\n polls: false,\n nativeCommands: true,\n blockStreaming: true,\n } as const;\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: TELEGRAM_CHANNEL_DEFAULTS.queue.debounceMs },\n outbound: { textChunkLimit: TELEGRAM_CHANNEL_DEFAULTS.outbound.textChunkLimit },\n };\n\n readonly setupWizard = createTelegramSetupWizard();\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = TelegramConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly cronDelivery: ChannelCronDeliveryAdapter = {\n async normalizeDeliveryTarget(to) {\n return { chatId: normalizeTelegramDeliveryChatId(to) };\n },\n };\n\n readonly onboard = telegramOnboardAdapter;\n\n readonly runtimeActions = telegramGatewaySetupActions;\n\n readonly doctor = {\n check: (params: { cfg: Config }) => runTelegramDoctorChecks(params),\n };\n\n readonly pairing = createStandardPairingAdapter('telegram');\n\n private bus!: NonNullable<ChannelPluginInitOptions['bus']>;\n private cfg!: NonNullable<ChannelPluginInitOptions['config']>;\n private debouncer!: ReturnType<typeof createInboundDebouncer<TelegramMessageEvent>>;\n\n private accountManager!: TelegramAccountManager;\n private outboundSender!: ReturnType<typeof createOutboundSender>;\n private commandHandler!: ReturnType<typeof createTelegramCommandHandler>;\n private inboundProcessor!: ReturnType<typeof createInboundProcessor>;\n private sessionModelHooks?: ChannelPluginSessionModelHooks;\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n private inboundCoalescer = createTelegramInboundCoalescer();\n private execApprovalUnregister: (() => void) | null = null;\n\n readonly actions = {\n handleAction: handleTelegramChannelAction,\n };\n\n config!: import('@xopcai/xopc/channels/plugin-types.js').ChannelConfigAdapter<TelegramResolvedAccount>;\n security!: import('@xopcai/xopc/channels/plugin-types.js').ChannelSecurityAdapter<TelegramResolvedAccount>;\n status!: import('@xopcai/xopc/channels/plugin-types.js').ChannelStatusAdapter<TelegramResolvedAccount>;\n\n outbound!: ChannelOutboundAdapter;\n\n gateway!: ChannelGatewayAdapter<TelegramResolvedAccount>;\n\n streaming!: ChannelStreamingAdapter;\n\n commands!: ChannelCommandAdapter;\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n this.sessionModelHooks = options.sessionModel;\n\n this.accountManager = new TelegramAccountManager();\n this.loadAccounts();\n this.bindOutboundComponents();\n bindTelegramMessageActionAccountManager(this.accountManager);\n this.execApprovalUnregister?.();\n this.execApprovalUnregister = registerChannelExecApprovalHandler(\n 'telegram',\n createTelegramExecApprovalHandler({\n accountManager: this.accountManager,\n getConfig: () => this.cfg,\n }),\n );\n\n // Workflow progress is fed from the agent-side broker (subscribes to\n // tool_execution_update for the `workflow` tool); the capability turns\n // each snapshot into an edit-in-place Telegram message. Registration is\n // idempotent — the broker replaces a prior cap with the same channelId.\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createTelegramWorkflowProgressCapability(this.accountManager),\n );\n\n const debounceMs =\n this.defaults.queue?.debounceMs ?? TELEGRAM_CHANNEL_DEFAULTS.queue.debounceMs;\n const keyPolicy = telegramDebouncerKeyPolicy();\n this.debouncer = createInboundDebouncer<TelegramMessageEvent>({\n debounceMs,\n ...keyPolicy,\n onFlush: async (items) => {\n await this.processMessages(items);\n },\n onError: (err, items) => {\n log.error({ err, count: items.length }, 'Debounced message processing failed');\n },\n });\n\n log.debug('Telegram plugin initialized');\n }\n\n private bindOutboundComponents(): void {\n this.outboundSender = createOutboundSender({\n accountManager: this.accountManager,\n config: this.cfg,\n });\n const sm = this.sessionModelHooks;\n this.commandHandler = createTelegramCommandHandler({\n bus: this.bus,\n config: this.cfg,\n accountManager: this.accountManager,\n getSessionModel: (sessionKey) => sm?.getModelForSession(sessionKey),\n setSessionModel: (sessionKey, modelId) => {\n if (!sm) return;\n void sm.switchModelForSession(sessionKey, modelId);\n },\n });\n const adapters = createTelegramPluginAdapters({\n accountManager: this.accountManager,\n });\n this.config = adapters.config;\n this.security = adapters.security;\n this.status = adapters.status;\n\n const accessControl = createTelegramInboundAccessControl();\n\n this.inboundProcessor = createInboundProcessor({\n bus: this.bus,\n config: this.cfg,\n accountManager: this.accountManager,\n accessControl,\n sessionKeyService: {\n generateSessionKey: (opts) =>\n generateSessionKey({\n source: 'telegram',\n chatId: opts.chatId,\n senderId: opts.senderId,\n isGroup: opts.isGroup,\n threadId: opts.threadId,\n accountId: opts.accountId,\n }),\n },\n sttService: {\n transcribe: async (buffer, config, options) => {\n const result = await sttTranscribe(buffer, config as STTConfig, options);\n return { text: result.text };\n },\n isSTTAvailable: (config) => isSTTAvailable(config as STTConfig | undefined),\n },\n mediaUtils: { getMimeType },\n });\n\n const sends = createTelegramOutboundSendMethods(\n (opts) => this.outboundSender.send(opts),\n this.accountManager,\n );\n this.outbound = {\n deliveryMode: 'direct',\n chunker: telegramTextChunker,\n chunkerMode: 'text',\n textChunkLimit: TELEGRAM_OUTBOUND_DEFAULTS.textChunkLimit,\n ...sends,\n };\n\n this.gateway = createTelegramGatewayAdapter({\n startAccount: (account) => this.startAccount(account),\n stopAccount: (accountId) => this.accountManager.stopRunner(accountId),\n });\n this.streaming = createTelegramStreamingAdapter({ accountManager: this.accountManager });\n this.commands = createTelegramCommandAdapter();\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prevTg = this.cfg.channels?.telegram as unknown;\n const nextTg = cfg.channels?.telegram as { enabled?: boolean } | undefined;\n // Match Weixin: only `enabled === true` keeps inbound (polling). Stops immediately on disable / missing section.\n const channelOff = !nextTg || nextTg.enabled !== true;\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n this.accountManager.reset();\n this.bindOutboundComponents();\n return;\n }\n\n if (isDeepStrictEqual(prevTg, nextTg)) {\n this.cfg = cfg;\n this.bindOutboundComponents();\n // Config unchanged but runners may be stopped (e.g. disable → enable with identical JSON shape after normalize).\n if (!this.channelIsRunning(cfg)) {\n await this.reapplyFromConfig(cfg);\n }\n return;\n }\n await this.reapplyFromConfig(cfg);\n }\n\n private async reapplyFromConfig(cfg: Config): Promise<void> {\n this.cfg = cfg;\n await this.stop();\n this.accountManager.reset();\n const telegramCfg = cfg.channels?.telegram as Record<string, unknown> | undefined;\n const channelOn = telegramCfg != null && telegramCfg.enabled === true;\n if (!channelOn) {\n this.bindOutboundComponents();\n return;\n }\n this.loadAccounts();\n this.bindOutboundComponents();\n await this.start();\n }\n\n private loadAccounts(): void {\n const telegramCfg = this.cfg.channels?.telegram as Record<string, unknown> | undefined;\n if (!telegramCfg) return;\n if (telegramCfg.enabled !== true) {\n return;\n }\n\n const accounts = telegramCfg.accounts as Record<string, Record<string, unknown>> | undefined;\n if (!accounts || Object.keys(accounts).length === 0) return;\n\n const channelApiRoot =\n typeof telegramCfg.apiRoot === 'string'\n ? normalizeTelegramApiRoot(telegramCfg.apiRoot)\n : undefined;\n const channelProxy =\n typeof telegramCfg.proxy === 'string' && telegramCfg.proxy.trim()\n ? telegramCfg.proxy.trim()\n : undefined;\n const channelReplyToMode = telegramCfg.replyToMode as TelegramResolvedAccount['replyToMode'];\n const channelStreaming = telegramCfg.streaming as TelegramResolvedAccount['streaming'];\n\n const tokenOwners = new Map<string, string[]>();\n\n for (const [id, account] of Object.entries(accounts)) {\n const { token, source } = resolveTelegramBotToken({\n botToken: typeof account.botToken === 'string' ? account.botToken : undefined,\n tokenFile: typeof account.tokenFile === 'string' ? account.tokenFile : undefined,\n });\n if (token) {\n const owners = tokenOwners.get(token) ?? [];\n owners.push(id);\n tokenOwners.set(token, owners);\n }\n\n const accApiRoot =\n typeof account.apiRoot === 'string'\n ? normalizeTelegramApiRoot(account.apiRoot)\n : undefined;\n const accProxy =\n typeof account.proxy === 'string' && account.proxy.trim() ? account.proxy.trim() : undefined;\n\n this.accountManager.registerAccount({\n ...account,\n accountId: id,\n botToken: token,\n tokenFile: typeof account.tokenFile === 'string' ? account.tokenFile : undefined,\n tokenSource: source,\n ...(accApiRoot || channelApiRoot ? { apiRoot: accApiRoot || channelApiRoot } : {}),\n ...(accProxy || channelProxy ? { proxy: accProxy || channelProxy } : {}),\n replyToMode:\n (account.replyToMode as TelegramResolvedAccount['replyToMode']) ?? channelReplyToMode,\n streaming:\n (account.streaming as TelegramResolvedAccount['streaming']) ?? channelStreaming,\n } as import('@xopcai/xopc/channels/channel-domain.js').TelegramAccountConfig);\n }\n\n for (const [token, owners] of tokenOwners) {\n if (owners.length > 1) {\n log.error(\n { accountIds: owners, tokenPreview: `${token.slice(0, 8)}…` },\n `Duplicate Telegram bot token across accounts: ${owners.join(', ')}`,\n );\n }\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n return this.accountManager.getAllAccounts().some(\n (a) => a.enabled !== false && !!a.botToken && this.accountManager.isRunning(a.accountId),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.telegram as { enabled?: boolean } | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const accountIds = options?.accountId\n ? [options.accountId]\n : this.config.listAccountIds(this.cfg);\n\n for (const accountId of accountIds) {\n const account = this.config.resolveAccount(this.cfg, accountId);\n if (!account.enabled || !account.botToken) continue;\n await this.startAccount(account);\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n if (!this.config) return;\n const ids = accountId ? [accountId] : this.config.listAccountIds(this.cfg);\n for (const id of ids) {\n await this.accountManager.stopRunner(id);\n log.info({ accountId: id }, 'Telegram account stopped');\n }\n // Only unregister the broker capability on a full stop (no specific\n // account). A per-account stop still leaves other accounts capable of\n // sending progress through the same registered cap.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n this.execApprovalUnregister?.();\n this.execApprovalUnregister = null;\n }\n }\n\n private async startAccount(account: TelegramResolvedAccount): Promise<void> {\n if (this.accountManager.isRunning(account.accountId)) return;\n\n const resolved = resolveTelegramBotToken({\n botToken: account.botToken,\n tokenFile: account.tokenFile,\n });\n if (!resolved.token) return;\n\n const duplicateAccounts = this.accountManager\n .getAllAccounts()\n .filter((a) => {\n const t = resolveTelegramBotToken({\n botToken: a.botToken,\n tokenFile: a.tokenFile,\n }).token;\n return t === resolved.token && a.accountId !== account.accountId;\n })\n .map((a) => a.accountId);\n if (duplicateAccounts.length > 0) {\n const em = `Duplicate bot token shared with account(s): ${duplicateAccounts.join(', ')}`;\n log.error({ accountId: account.accountId, duplicateAccounts }, em);\n this.accountManager.updateStatus({\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n lastError: em,\n });\n return;\n }\n\n this.accountManager.markStarting(account.accountId);\n\n try {\n const client: {\n timeoutSeconds: number;\n apiRoot?: string;\n fetch?: typeof fetch;\n } = {\n timeoutSeconds: TELEGRAM_CLIENT_TIMEOUT_SECONDS,\n ...(account.apiRoot ? { apiRoot: account.apiRoot } : {}),\n ...(account.proxy ? { fetch: createProxyFetch(account.proxy) } : {}),\n };\n const bot = new Bot(resolved.token, { client });\n const getMeSignal = createTimeoutAbortSignal(TELEGRAM_GETME_TIMEOUT_MS);\n let me;\n try {\n me = await bot.api.getMe(getMeSignal.signal);\n } finally {\n getMeSignal.dispose();\n }\n\n this.accountManager.registerBot(account.accountId, bot);\n this.accountManager.setBotUsername(account.accountId, me.username);\n this.setupMessageHandler(account.accountId, bot);\n\n const session = startTelegramPollingSession({\n accountId: account.accountId,\n botToken: resolved.token,\n bot,\n stallThresholdMs: account.pollingStallThresholdMs,\n onExit: () => {\n void this.accountManager.stopRunner(account.accountId);\n },\n });\n this.accountManager.registerPollingSession(account.accountId, session);\n this.accountManager.updateStatus({\n accountId: account.accountId,\n running: true,\n mode: 'polling',\n });\n\n log.info({ accountId: account.accountId, username: me.username }, 'Telegram account started');\n } catch (err) {\n const em = formatTelegramStartupError(err);\n const level = isTelegramUnauthorizedTokenError(err) ? 'error' : 'warn';\n log[level](\n {\n accountId: account.accountId,\n apiRootConfigured: !!account.apiRoot,\n unauthorized: isTelegramUnauthorizedTokenError(err),\n errorMessage: em,\n },\n `Telegram account not started: ${em}`,\n );\n this.accountManager.updateStatus({\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n lastError: em.slice(0, 400),\n });\n } finally {\n this.accountManager.markStartComplete(account.accountId);\n }\n }\n\n private attachRunnerExitHandler(_accountId: string, _runner: unknown): void {\n // Polling exit is handled by startTelegramPollingSession.onExit.\n }\n\n private setupMessageHandler(accountId: string, bot: Bot): void {\n const account = this.config.resolveAccount(this.cfg, accountId);\n\n const handleInboundMessage = async (ctx: Context) => {\n const text = ctx.message?.text ?? ctx.message?.caption ?? ctx.channelPost?.text ?? ctx.channelPost?.caption ?? '';\n const command = text.trim().split(' ')[0].split('@')[0].toLowerCase();\n\n if (command === '/models') {\n await this.commandHandler.handleModels(ctx);\n return;\n }\n if (command === '/start') {\n await this.commandHandler.handleStart(ctx);\n return;\n }\n if (command === '/cleanup') {\n await this.commandHandler.handleCleanup(ctx);\n return;\n }\n if (command === '/focus') {\n await handleTelegramFocusCommand({ ctx, accountId, config: this.cfg });\n return;\n }\n\n const message = ctx.message ?? ctx.channelPost;\n if (!message) return;\n\n await this.inboundCoalescer.enqueue({\n ctx,\n accountId,\n message,\n onReady: async (batch) => {\n for (const msg of batch.messages) {\n const batchCtx = batch.ctx;\n await this.debouncer.enqueue({\n ctx: contextWithMessage(batchCtx, msg),\n accountId: batch.accountId,\n message: msg,\n });\n }\n },\n });\n };\n\n bot.on('message', async (ctx) => {\n try {\n await handleInboundMessage(ctx);\n } catch (err) {\n log.error({ accountId, err }, 'Message handler error');\n }\n });\n\n bot.on('channel_post', async (ctx) => {\n try {\n await handleInboundMessage(ctx);\n } catch (err) {\n log.error({ accountId, err }, 'Channel post handler error');\n }\n });\n\n bot.on('message_reaction', async (ctx) => {\n try {\n await handleTelegramMessageReaction({\n ctx,\n accountId,\n bus: this.bus,\n mode: account.reactionNotifications as 'off' | 'own' | 'all' | undefined,\n });\n } catch (err) {\n log.error({ accountId, err }, 'Message reaction handler error');\n }\n });\n\n bot.on('callback_query:data', async (ctx) => {\n try {\n await this.handleCallbackQuery(ctx, accountId);\n } catch (err) {\n log.error({ accountId, err }, 'Callback query handler error');\n await ctx.answerCallbackQuery('An error occurred').catch(() => {});\n }\n });\n }\n\n private async handleCallbackQuery(ctx: Context, _accountId: string): Promise<void> {\n const data = ctx.callbackQuery?.data;\n if (!data) return;\n\n if (data.startsWith('provider:')) {\n const providerId = data.substring('provider:'.length);\n await this.commandHandler.handleProviderSelect(ctx, providerId);\n return;\n }\n\n if (data.startsWith('model:')) {\n const modelId = data.substring('model:'.length);\n await this.commandHandler.handleModelSelect(ctx, modelId);\n return;\n }\n\n if (data === 'cancel') {\n await this.commandHandler.handleCancel(ctx);\n return;\n }\n\n if (data === 'providers') {\n await this.commandHandler.handleShowProviders(ctx);\n return;\n }\n\n if (data === 'cleanup:confirm') {\n await this.commandHandler.handleCleanupConfirm(ctx);\n return;\n }\n\n if (data.startsWith('clarify:')) {\n const rest = data.slice('clarify:'.length);\n const lastColon = rest.lastIndexOf(':');\n if (lastColon > 0) {\n const requestId = rest.slice(0, lastColon);\n const idx = Number.parseInt(rest.slice(lastColon + 1), 10);\n if (requestId && Number.isFinite(idx) && submitClarifyChoiceFromChannel(requestId, idx)) {\n await ctx.answerCallbackQuery();\n return;\n }\n }\n await ctx.answerCallbackQuery('No pending question');\n return;\n }\n\n if (data.startsWith('approval:approve:') || data.startsWith('approval:deny:')) {\n const approved = data.startsWith('approval:approve:');\n const approvalId = data.slice(approved ? 'approval:approve:'.length : 'approval:deny:'.length);\n const senderId = String(ctx.from?.id ?? '');\n if (\n !isTelegramExecApprovalApprover({\n cfg: this.cfg,\n accountId: _accountId,\n senderId,\n })\n ) {\n await ctx.answerCallbackQuery('Not authorized');\n return;\n }\n if (resolveTelegramApproval(approvalId, approved)) {\n await ctx.answerCallbackQuery(approved ? 'Approved' : 'Denied');\n } else {\n await ctx.answerCallbackQuery('Approval expired or already resolved');\n }\n return;\n }\n\n await ctx.answerCallbackQuery('Unknown action');\n }\n\n private async processMessages(items: TelegramMessageEvent[]): Promise<void> {\n if (items.length === 0) return;\n\n const tgSection = this.cfg.channels?.telegram as { enabled?: boolean } | undefined;\n if (!tgSection || tgSection.enabled !== true) {\n return;\n }\n\n const last = items[items.length - 1];\n const ctx = last.ctx;\n const accountId = last.accountId;\n if (!this.accountManager.getAccount(accountId)) {\n log.warn({ accountId }, 'Unknown account');\n return;\n }\n\n const account = this.config.resolveAccount(this.cfg, accountId);\n\n const isGroup = ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup';\n const senderId = ctx.from?.id?.toString() ?? '';\n const senderUsername = ctx.from?.username;\n const chatId = ctx.chat?.id?.toString() ?? '';\n telegramReplyTracker.reset(accountId, chatId);\n\n const securityCtx: ChannelSecurityContext = {\n accountId,\n chatId,\n senderId,\n senderName: senderUsername,\n isGroup,\n };\n\n const accessResult = this.security.checkAccess?.(securityCtx, account, this.cfg);\n\n if (!accessResult?.allowed) {\n if (!isGroup && accessResult?.reason === 'pairing-required') {\n const cid = ctx.chat?.id;\n const bot = cid != null ? this.accountManager.getBot(accountId) : undefined;\n if (bot && cid != null && senderId) {\n void issuePairingChallenge({\n channel: 'telegram',\n pairingFilePath: resolveStandardPairingPath('telegram', accountId),\n accountId,\n senderId,\n senderIdLine: `Your Telegram user id: ${senderId}`,\n sendPairingReply: async (text) => {\n await bot.api.sendMessage(cid, text);\n },\n onCreated: ({ code }) => {\n log.info({ accountId, senderId, code }, 'Telegram DM pairing code issued');\n },\n onReplyError: (err) => {\n log.warn({ err, accountId, chatId: String(cid) }, 'Telegram pairing reply failed');\n },\n });\n }\n return;\n }\n log.warn(\n {\n accountId,\n chatId,\n senderId,\n isGroup,\n reason: accessResult?.reason,\n dmPolicy: account.dmPolicy,\n groupPolicy: account.groupPolicy,\n },\n 'Telegram: message dropped by channel security (check dmPolicy/groupPolicy and allowFrom)',\n );\n return;\n }\n\n const bot = this.accountManager.getBot(accountId);\n const messageId = ctx.message?.message_id;\n const reactionLevel = (account as { reactionLevel?: string }).reactionLevel ?? 'ack';\n const ackEmoji = (account as { ackReaction?: string }).ackReaction ?? '👀';\n if (\n bot &&\n messageId &&\n reactionLevel !== 'off' &&\n ctx.chat?.id != null &&\n !ctx.channelPost\n ) {\n void sendTelegramAckReaction({\n bot,\n chatId: ctx.chat.id,\n messageId,\n emoji: ackEmoji,\n });\n }\n\n await this.inboundProcessor(ctx, accountId);\n }\n}\n\nexport const telegramPlugin = new TelegramChannelPlugin();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"plugin.js","names":[],"sources":["../../../../extensions/telegram/src/plugin.ts"],"sourcesContent":["/**\n * Telegram Channel Plugin - Implementation based on ChannelPlugin interface\n *\n * This plugin integrates Telegram with the ChannelPlugin architecture.\n * It reuses components from this package's src tree where possible.\n */\n\nimport { isDeepStrictEqual } from 'node:util';\n\nimport { Bot, type Context } from 'grammy';\n\nimport type { Config } from '@xopcai/xopc/config/index.js';\nimport type {\n ChannelPlugin,\n ChannelPluginDefaults,\n ChannelPluginInitOptions,\n ChannelPluginReloadMeta,\n ChannelPluginSessionModelHooks,\n ChannelPluginStartOptions,\n ChannelOutboundAdapter,\n ChannelSecurityContext,\n ChannelGatewayAdapter,\n ChannelStreamingAdapter,\n ChannelCommandAdapter,\n} from '@xopcai/xopc/channels/plugin-types.js';\nimport { generateSessionKey } from '@xopcai/xopc/chat-commands/session-key.js';\nimport { submitClarifyChoiceFromChannel } from '@xopcai/xopc/gateway/clarify-runtime.js';\n\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\nimport { issuePairingChallenge, resolveStandardPairingPath } from '@xopcai/xopc/channels/pairing/index.js';\nimport { createStandardPairingAdapter } from '@xopcai/xopc/channels/pairing/pairing-store-adapter.js';\nimport { createTimeoutAbortSignal } from './timeout-abort.js';\nimport { createInboundDebouncer } from '@xopcai/xopc/infra/debounce.js';\nimport { getMimeType } from '@xopcai/xopc/channels/media.js';\nimport { isSTTAvailable } from '@xopcai/xopc/voice/stt/availability.js';\nimport type { STTConfig } from '@xopcai/xopc/voice/stt/types.js';\n\nimport { TelegramAccountManager } from './account-manager.js';\nimport { createOutboundSender } from './outbound-sender.js';\n\nimport { createTelegramCommandHandler } from './command-handler.js';\nimport { createInboundProcessor } from './inbound-processor.js';\nimport { TELEGRAM_CHANNEL_DEFAULTS } from './plugin-defaults.js';\nimport {\n createTelegramPluginAdapters,\n createTelegramSetupWizard,\n createTelegramOutboundSendMethods,\n telegramTextChunker,\n TELEGRAM_OUTBOUND_DEFAULTS,\n createTelegramInboundAccessControl,\n telegramDebouncerKeyPolicy,\n type TelegramMessageEvent,\n} from './adapters/index.js';\nimport {\n createTelegramGatewayAdapter,\n createTelegramStreamingAdapter,\n createTelegramCommandAdapter,\n} from './channel.js';\nimport type { TelegramResolvedAccount } from './adapters/index.js';\nimport type { ChannelCronDeliveryAdapter } from '@xopcai/xopc/channels/plugins/types.adapters.js';\nimport { normalizeTelegramDeliveryChatId } from './delivery-chat-id.js';\nimport { telegramOnboardAdapter } from './adapters/onboard-cli.js';\nimport { telegramGatewaySetupActions } from './adapters/gateway-setup.js';\nimport { TelegramConfigSchema } from './config-schema.js';\nimport { normalizeTelegramApiRoot } from './api-root.js';\nimport { resolveTelegramBotToken } from './token-resolver.js';\nimport { formatTelegramStartupError, isTelegramUnauthorizedTokenError } from './startup-errors.js';\nimport { startTelegramPollingSession } from './polling-session.js';\nimport { createProxyFetch } from './proxy-fetch.js';\nimport { runTelegramDoctorChecks } from './doctor.js';\nimport { telegramReplyTracker } from './reply-params.js';\nimport { createTelegramInboundCoalescer } from './inbound-coalescer.js';\nimport { sendTelegramAckReaction, handleTelegramMessageReaction } from './reactions.js';\nimport { handleTelegramChannelAction, bindTelegramMessageActionAccountManager } from './actions/message-actions.js';\nimport { registerChannelExecApprovalHandler } from '@xopcai/xopc/channels/exec-approval-runtime.js';\nimport { createTelegramExecApprovalHandler } from './exec-approval-handler.js';\nimport { handleTelegramFocusCommand } from './focus-handler.js';\nimport { resolveTelegramApproval } from './approval-store.js';\nimport { isTelegramExecApprovalApprover } from './exec-approvals.js';\n\n/** Bound initial `getMe` so a bad `apiRoot` or unreachable API cannot block gateway startup for minutes. */\nconst TELEGRAM_GETME_TIMEOUT_MS = 20_000;\n/** grammY per-request ceiling; must exceed long-poll `getUpdates` (~30s) but avoid multi-minute hangs on bad hosts. */\nconst TELEGRAM_CLIENT_TIMEOUT_SECONDS = 75;\n\nconst log = createLogger('TelegramPlugin');\n\nfunction contextWithMessage(ctx: Context, message: import('@grammyjs/types').Message): Context {\n if (ctx.message === message) return ctx;\n\n const clone = Object.create(Object.getPrototypeOf(ctx)) as Context;\n Object.defineProperties(clone, Object.getOwnPropertyDescriptors(ctx));\n Object.defineProperty(clone, 'message', {\n configurable: true,\n enumerable: true,\n value: message,\n writable: true,\n });\n return clone;\n}\n\nexport type { TelegramResolvedAccount as TelegramAccount } from './channel.js';\n\nexport class TelegramChannelPlugin implements ChannelPlugin<TelegramResolvedAccount> {\n readonly id = 'telegram' as const;\n\n readonly reload: ChannelPluginReloadMeta = {\n configPrefixes: ['channels.telegram'],\n };\n\n readonly meta = {\n id: 'telegram',\n label: 'Telegram',\n selectionLabel: 'Telegram Bot',\n docsPath: '/channels/telegram',\n blurb: 'Telegram Bot API channel',\n order: 0,\n deferConnectUntilAfterListen: true,\n } as const;\n\n readonly capabilities = {\n chatTypes: ['direct', 'group', 'channel', 'thread'] as Array<'direct' | 'group' | 'channel' | 'thread'>,\n reactions: true,\n threads: true,\n media: true,\n polls: false,\n nativeCommands: true,\n blockStreaming: true,\n } as const;\n\n readonly defaults: ChannelPluginDefaults = {\n queue: { debounceMs: TELEGRAM_CHANNEL_DEFAULTS.queue.debounceMs },\n outbound: { textChunkLimit: TELEGRAM_CHANNEL_DEFAULTS.outbound.textChunkLimit },\n };\n\n readonly setupWizard = createTelegramSetupWizard();\n\n readonly configSchema = {\n schema: {},\n validate: (raw: unknown) => {\n const r = TelegramConfigSchema.safeParse(raw);\n return r.success ? { ok: true as const } : { ok: false as const, errors: [r.error.message] };\n },\n };\n\n readonly cronDelivery: ChannelCronDeliveryAdapter = {\n async normalizeDeliveryTarget(to) {\n return { chatId: normalizeTelegramDeliveryChatId(to) };\n },\n };\n\n readonly onboard = telegramOnboardAdapter;\n\n readonly runtimeActions = telegramGatewaySetupActions;\n\n readonly doctor = {\n check: (params: { cfg: Config }) => runTelegramDoctorChecks(params),\n };\n\n readonly pairing = createStandardPairingAdapter('telegram');\n\n private bus!: NonNullable<ChannelPluginInitOptions['bus']>;\n private cfg!: NonNullable<ChannelPluginInitOptions['config']>;\n private debouncer!: ReturnType<typeof createInboundDebouncer<TelegramMessageEvent>>;\n\n private accountManager!: TelegramAccountManager;\n private outboundSender!: ReturnType<typeof createOutboundSender>;\n private commandHandler!: ReturnType<typeof createTelegramCommandHandler>;\n private inboundProcessor!: ReturnType<typeof createInboundProcessor>;\n private sessionModelHooks?: ChannelPluginSessionModelHooks;\n /** Unregister fn for the workflow-progress capability registered against the global broker. */\n private workflowProgressUnregister: (() => void) | null = null;\n private inboundCoalescer = createTelegramInboundCoalescer();\n private execApprovalUnregister: (() => void) | null = null;\n\n readonly actions = {\n handleAction: handleTelegramChannelAction,\n };\n\n config!: import('@xopcai/xopc/channels/plugin-types.js').ChannelConfigAdapter<TelegramResolvedAccount>;\n security!: import('@xopcai/xopc/channels/plugin-types.js').ChannelSecurityAdapter<TelegramResolvedAccount>;\n status!: import('@xopcai/xopc/channels/plugin-types.js').ChannelStatusAdapter<TelegramResolvedAccount>;\n\n outbound!: ChannelOutboundAdapter;\n\n gateway!: ChannelGatewayAdapter<TelegramResolvedAccount>;\n\n streaming!: ChannelStreamingAdapter;\n\n commands!: ChannelCommandAdapter;\n\n async init(options: ChannelPluginInitOptions): Promise<void> {\n this.bus = options.bus;\n this.cfg = options.config;\n this.sessionModelHooks = options.sessionModel;\n\n this.accountManager = new TelegramAccountManager();\n this.loadAccounts();\n this.bindOutboundComponents();\n bindTelegramMessageActionAccountManager(this.accountManager);\n this.execApprovalUnregister?.();\n this.execApprovalUnregister = registerChannelExecApprovalHandler(\n 'telegram',\n createTelegramExecApprovalHandler({\n accountManager: this.accountManager,\n getConfig: () => this.cfg,\n }),\n );\n\n await this.registerWorkflowProgressCapability();\n\n const debounceMs =\n this.defaults.queue?.debounceMs ?? TELEGRAM_CHANNEL_DEFAULTS.queue.debounceMs;\n const keyPolicy = telegramDebouncerKeyPolicy();\n this.debouncer = createInboundDebouncer<TelegramMessageEvent>({\n debounceMs,\n ...keyPolicy,\n onFlush: async (items) => {\n await this.processMessages(items);\n },\n onError: (err, items) => {\n log.error({ err, count: items.length }, 'Debounced message processing failed');\n },\n });\n\n log.debug('Telegram plugin initialized');\n }\n\n private async registerWorkflowProgressCapability(): Promise<void> {\n // Lazy-load workflow progress so channel runtime startup does not pull the\n // whole workflow/agent/provider graph into Electron's dynamic extension path.\n const [{ getWorkflowProgressBroker }, { createTelegramWorkflowProgressCapability }] = await Promise.all([\n import('@xopcai/xopc/agent/workflow/progress-broker.js'),\n import('./workflow-progress.js'),\n ]);\n this.workflowProgressUnregister = getWorkflowProgressBroker().registerChannel(\n createTelegramWorkflowProgressCapability(this.accountManager),\n );\n }\n\n private bindOutboundComponents(): void {\n this.outboundSender = createOutboundSender({\n accountManager: this.accountManager,\n config: this.cfg,\n });\n const sm = this.sessionModelHooks;\n this.commandHandler = createTelegramCommandHandler({\n bus: this.bus,\n config: this.cfg,\n accountManager: this.accountManager,\n getSessionModel: (sessionKey) => sm?.getModelForSession(sessionKey),\n setSessionModel: (sessionKey, modelId) => {\n if (!sm) return;\n void sm.switchModelForSession(sessionKey, modelId);\n },\n });\n const adapters = createTelegramPluginAdapters({\n accountManager: this.accountManager,\n });\n this.config = adapters.config;\n this.security = adapters.security;\n this.status = adapters.status;\n\n const accessControl = createTelegramInboundAccessControl();\n\n this.inboundProcessor = createInboundProcessor({\n bus: this.bus,\n config: this.cfg,\n accountManager: this.accountManager,\n accessControl,\n sessionKeyService: {\n generateSessionKey: (opts) =>\n generateSessionKey({\n source: 'telegram',\n chatId: opts.chatId,\n senderId: opts.senderId,\n isGroup: opts.isGroup,\n threadId: opts.threadId,\n accountId: opts.accountId,\n }),\n },\n sttService: {\n transcribe: async (buffer, config, options) => {\n // Lazy-load STT providers only when voice transcription is actually used.\n const { transcribe } = await import('@xopcai/xopc/voice/stt/transcribe-core.js');\n const result = await transcribe(buffer, config as STTConfig, options);\n return { text: result.text };\n },\n isSTTAvailable: (config) => isSTTAvailable(config as STTConfig | undefined),\n },\n mediaUtils: { getMimeType },\n });\n\n const sends = createTelegramOutboundSendMethods(\n (opts) => this.outboundSender.send(opts),\n this.accountManager,\n );\n this.outbound = {\n deliveryMode: 'direct',\n chunker: telegramTextChunker,\n chunkerMode: 'text',\n textChunkLimit: TELEGRAM_OUTBOUND_DEFAULTS.textChunkLimit,\n ...sends,\n };\n\n this.gateway = createTelegramGatewayAdapter({\n startAccount: (account) => this.startAccount(account),\n stopAccount: (accountId) => this.accountManager.stopRunner(accountId),\n });\n this.streaming = createTelegramStreamingAdapter({ accountManager: this.accountManager });\n this.commands = createTelegramCommandAdapter();\n }\n\n async onConfigUpdated(cfg: Config): Promise<void> {\n const prevTg = this.cfg.channels?.telegram as unknown;\n const nextTg = cfg.channels?.telegram as { enabled?: boolean } | undefined;\n // Match Weixin: only `enabled === true` keeps inbound (polling). Stops immediately on disable / missing section.\n const channelOff = !nextTg || nextTg.enabled !== true;\n if (channelOff) {\n this.cfg = cfg;\n await this.stop();\n this.accountManager.reset();\n this.bindOutboundComponents();\n return;\n }\n\n if (isDeepStrictEqual(prevTg, nextTg)) {\n this.cfg = cfg;\n this.bindOutboundComponents();\n // Config unchanged but runners may be stopped (e.g. disable → enable with identical JSON shape after normalize).\n if (!this.channelIsRunning(cfg)) {\n await this.reapplyFromConfig(cfg);\n }\n return;\n }\n await this.reapplyFromConfig(cfg);\n }\n\n private async reapplyFromConfig(cfg: Config): Promise<void> {\n this.cfg = cfg;\n await this.stop();\n this.accountManager.reset();\n const telegramCfg = cfg.channels?.telegram as Record<string, unknown> | undefined;\n const channelOn = telegramCfg != null && telegramCfg.enabled === true;\n if (!channelOn) {\n this.bindOutboundComponents();\n return;\n }\n this.loadAccounts();\n this.bindOutboundComponents();\n await this.start();\n }\n\n private loadAccounts(): void {\n const telegramCfg = this.cfg.channels?.telegram as Record<string, unknown> | undefined;\n if (!telegramCfg) return;\n if (telegramCfg.enabled !== true) {\n return;\n }\n\n const accounts = telegramCfg.accounts as Record<string, Record<string, unknown>> | undefined;\n if (!accounts || Object.keys(accounts).length === 0) return;\n\n const channelApiRoot =\n typeof telegramCfg.apiRoot === 'string'\n ? normalizeTelegramApiRoot(telegramCfg.apiRoot)\n : undefined;\n const channelProxy =\n typeof telegramCfg.proxy === 'string' && telegramCfg.proxy.trim()\n ? telegramCfg.proxy.trim()\n : undefined;\n const channelReplyToMode = telegramCfg.replyToMode as TelegramResolvedAccount['replyToMode'];\n const channelStreaming = telegramCfg.streaming as TelegramResolvedAccount['streaming'];\n\n const tokenOwners = new Map<string, string[]>();\n\n for (const [id, account] of Object.entries(accounts)) {\n const { token, source } = resolveTelegramBotToken({\n botToken: typeof account.botToken === 'string' ? account.botToken : undefined,\n tokenFile: typeof account.tokenFile === 'string' ? account.tokenFile : undefined,\n });\n if (token) {\n const owners = tokenOwners.get(token) ?? [];\n owners.push(id);\n tokenOwners.set(token, owners);\n }\n\n const accApiRoot =\n typeof account.apiRoot === 'string'\n ? normalizeTelegramApiRoot(account.apiRoot)\n : undefined;\n const accProxy =\n typeof account.proxy === 'string' && account.proxy.trim() ? account.proxy.trim() : undefined;\n\n this.accountManager.registerAccount({\n ...account,\n accountId: id,\n botToken: token,\n tokenFile: typeof account.tokenFile === 'string' ? account.tokenFile : undefined,\n tokenSource: source,\n ...(accApiRoot || channelApiRoot ? { apiRoot: accApiRoot || channelApiRoot } : {}),\n ...(accProxy || channelProxy ? { proxy: accProxy || channelProxy } : {}),\n replyToMode:\n (account.replyToMode as TelegramResolvedAccount['replyToMode']) ?? channelReplyToMode,\n streaming:\n (account.streaming as TelegramResolvedAccount['streaming']) ?? channelStreaming,\n } as import('@xopcai/xopc/channels/channel-domain.js').TelegramAccountConfig);\n }\n\n for (const [token, owners] of tokenOwners) {\n if (owners.length > 1) {\n log.error(\n { accountIds: owners, tokenPreview: `${token.slice(0, 8)}…` },\n `Duplicate Telegram bot token across accounts: ${owners.join(', ')}`,\n );\n }\n }\n }\n\n channelIsRunning(cfg: Config): boolean {\n return this.accountManager.getAllAccounts().some(\n (a) => a.enabled !== false && !!a.botToken && this.accountManager.isRunning(a.accountId),\n );\n }\n\n async start(options?: ChannelPluginStartOptions): Promise<void> {\n const section = this.cfg.channels?.telegram as { enabled?: boolean } | undefined;\n if (!section || section.enabled !== true) {\n return;\n }\n\n const accountIds = options?.accountId\n ? [options.accountId]\n : this.config.listAccountIds(this.cfg);\n\n for (const accountId of accountIds) {\n const account = this.config.resolveAccount(this.cfg, accountId);\n if (!account.enabled || !account.botToken) continue;\n await this.startAccount(account);\n }\n }\n\n async stop(accountId?: string): Promise<void> {\n if (!this.config) return;\n const ids = accountId ? [accountId] : this.config.listAccountIds(this.cfg);\n for (const id of ids) {\n await this.accountManager.stopRunner(id);\n log.info({ accountId: id }, 'Telegram account stopped');\n }\n // Only unregister the broker capability on a full stop (no specific\n // account). A per-account stop still leaves other accounts capable of\n // sending progress through the same registered cap.\n if (!accountId) {\n this.workflowProgressUnregister?.();\n this.workflowProgressUnregister = null;\n this.execApprovalUnregister?.();\n this.execApprovalUnregister = null;\n }\n }\n\n private async startAccount(account: TelegramResolvedAccount): Promise<void> {\n if (this.accountManager.isRunning(account.accountId)) return;\n\n const resolved = resolveTelegramBotToken({\n botToken: account.botToken,\n tokenFile: account.tokenFile,\n });\n if (!resolved.token) return;\n\n const duplicateAccounts = this.accountManager\n .getAllAccounts()\n .filter((a) => {\n const t = resolveTelegramBotToken({\n botToken: a.botToken,\n tokenFile: a.tokenFile,\n }).token;\n return t === resolved.token && a.accountId !== account.accountId;\n })\n .map((a) => a.accountId);\n if (duplicateAccounts.length > 0) {\n const em = `Duplicate bot token shared with account(s): ${duplicateAccounts.join(', ')}`;\n log.error({ accountId: account.accountId, duplicateAccounts }, em);\n this.accountManager.updateStatus({\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n lastError: em,\n });\n return;\n }\n\n this.accountManager.markStarting(account.accountId);\n\n try {\n const client: {\n timeoutSeconds: number;\n apiRoot?: string;\n fetch?: typeof fetch;\n } = {\n timeoutSeconds: TELEGRAM_CLIENT_TIMEOUT_SECONDS,\n ...(account.apiRoot ? { apiRoot: account.apiRoot } : {}),\n ...(account.proxy ? { fetch: createProxyFetch(account.proxy) } : {}),\n };\n const bot = new Bot(resolved.token, { client });\n const getMeSignal = createTimeoutAbortSignal(TELEGRAM_GETME_TIMEOUT_MS);\n let me;\n try {\n me = await bot.api.getMe(getMeSignal.signal);\n } finally {\n getMeSignal.dispose();\n }\n\n this.accountManager.registerBot(account.accountId, bot);\n this.accountManager.setBotUsername(account.accountId, me.username);\n this.setupMessageHandler(account.accountId, bot);\n\n const session = startTelegramPollingSession({\n accountId: account.accountId,\n botToken: resolved.token,\n bot,\n stallThresholdMs: account.pollingStallThresholdMs,\n onExit: () => {\n void this.accountManager.stopRunner(account.accountId);\n },\n });\n this.accountManager.registerPollingSession(account.accountId, session);\n this.accountManager.updateStatus({\n accountId: account.accountId,\n running: true,\n mode: 'polling',\n });\n\n log.info({ accountId: account.accountId, username: me.username }, 'Telegram account started');\n } catch (err) {\n const em = formatTelegramStartupError(err);\n const level = isTelegramUnauthorizedTokenError(err) ? 'error' : 'warn';\n log[level](\n {\n accountId: account.accountId,\n apiRootConfigured: !!account.apiRoot,\n unauthorized: isTelegramUnauthorizedTokenError(err),\n errorMessage: em,\n },\n `Telegram account not started: ${em}`,\n );\n this.accountManager.updateStatus({\n accountId: account.accountId,\n running: false,\n mode: 'stopped',\n lastError: em.slice(0, 400),\n });\n } finally {\n this.accountManager.markStartComplete(account.accountId);\n }\n }\n\n private attachRunnerExitHandler(_accountId: string, _runner: unknown): void {\n // Polling exit is handled by startTelegramPollingSession.onExit.\n }\n\n private setupMessageHandler(accountId: string, bot: Bot): void {\n const account = this.config.resolveAccount(this.cfg, accountId);\n\n const handleInboundMessage = async (ctx: Context) => {\n const text = ctx.message?.text ?? ctx.message?.caption ?? ctx.channelPost?.text ?? ctx.channelPost?.caption ?? '';\n const command = text.trim().split(' ')[0].split('@')[0].toLowerCase();\n\n if (command === '/models') {\n await this.commandHandler.handleModels(ctx);\n return;\n }\n if (command === '/start') {\n await this.commandHandler.handleStart(ctx);\n return;\n }\n if (command === '/cleanup') {\n await this.commandHandler.handleCleanup(ctx);\n return;\n }\n if (command === '/focus') {\n await handleTelegramFocusCommand({ ctx, accountId, config: this.cfg });\n return;\n }\n\n const message = ctx.message ?? ctx.channelPost;\n if (!message) return;\n\n await this.inboundCoalescer.enqueue({\n ctx,\n accountId,\n message,\n onReady: async (batch) => {\n for (const msg of batch.messages) {\n const batchCtx = batch.ctx;\n await this.debouncer.enqueue({\n ctx: contextWithMessage(batchCtx, msg),\n accountId: batch.accountId,\n message: msg,\n });\n }\n },\n });\n };\n\n bot.on('message', async (ctx) => {\n try {\n await handleInboundMessage(ctx);\n } catch (err) {\n log.error({ accountId, err }, 'Message handler error');\n }\n });\n\n bot.on('channel_post', async (ctx) => {\n try {\n await handleInboundMessage(ctx);\n } catch (err) {\n log.error({ accountId, err }, 'Channel post handler error');\n }\n });\n\n bot.on('message_reaction', async (ctx) => {\n try {\n await handleTelegramMessageReaction({\n ctx,\n accountId,\n bus: this.bus,\n mode: account.reactionNotifications as 'off' | 'own' | 'all' | undefined,\n });\n } catch (err) {\n log.error({ accountId, err }, 'Message reaction handler error');\n }\n });\n\n bot.on('callback_query:data', async (ctx) => {\n try {\n await this.handleCallbackQuery(ctx, accountId);\n } catch (err) {\n log.error({ accountId, err }, 'Callback query handler error');\n await ctx.answerCallbackQuery('An error occurred').catch(() => {});\n }\n });\n }\n\n private async handleCallbackQuery(ctx: Context, _accountId: string): Promise<void> {\n const data = ctx.callbackQuery?.data;\n if (!data) return;\n\n if (data.startsWith('provider:')) {\n const providerId = data.substring('provider:'.length);\n await this.commandHandler.handleProviderSelect(ctx, providerId);\n return;\n }\n\n if (data.startsWith('model:')) {\n const modelId = data.substring('model:'.length);\n await this.commandHandler.handleModelSelect(ctx, modelId);\n return;\n }\n\n if (data === 'cancel') {\n await this.commandHandler.handleCancel(ctx);\n return;\n }\n\n if (data === 'providers') {\n await this.commandHandler.handleShowProviders(ctx);\n return;\n }\n\n if (data === 'cleanup:confirm') {\n await this.commandHandler.handleCleanupConfirm(ctx);\n return;\n }\n\n if (data.startsWith('clarify:')) {\n const rest = data.slice('clarify:'.length);\n const lastColon = rest.lastIndexOf(':');\n if (lastColon > 0) {\n const requestId = rest.slice(0, lastColon);\n const idx = Number.parseInt(rest.slice(lastColon + 1), 10);\n if (requestId && Number.isFinite(idx) && submitClarifyChoiceFromChannel(requestId, idx)) {\n await ctx.answerCallbackQuery();\n return;\n }\n }\n await ctx.answerCallbackQuery('No pending question');\n return;\n }\n\n if (data.startsWith('approval:approve:') || data.startsWith('approval:deny:')) {\n const approved = data.startsWith('approval:approve:');\n const approvalId = data.slice(approved ? 'approval:approve:'.length : 'approval:deny:'.length);\n const senderId = String(ctx.from?.id ?? '');\n if (\n !isTelegramExecApprovalApprover({\n cfg: this.cfg,\n accountId: _accountId,\n senderId,\n })\n ) {\n await ctx.answerCallbackQuery('Not authorized');\n return;\n }\n if (resolveTelegramApproval(approvalId, approved)) {\n await ctx.answerCallbackQuery(approved ? 'Approved' : 'Denied');\n } else {\n await ctx.answerCallbackQuery('Approval expired or already resolved');\n }\n return;\n }\n\n await ctx.answerCallbackQuery('Unknown action');\n }\n\n private async processMessages(items: TelegramMessageEvent[]): Promise<void> {\n if (items.length === 0) return;\n\n const tgSection = this.cfg.channels?.telegram as { enabled?: boolean } | undefined;\n if (!tgSection || tgSection.enabled !== true) {\n return;\n }\n\n const last = items[items.length - 1];\n const ctx = last.ctx;\n const accountId = last.accountId;\n if (!this.accountManager.getAccount(accountId)) {\n log.warn({ accountId }, 'Unknown account');\n return;\n }\n\n const account = this.config.resolveAccount(this.cfg, accountId);\n\n const isGroup = ctx.chat?.type === 'group' || ctx.chat?.type === 'supergroup';\n const senderId = ctx.from?.id?.toString() ?? '';\n const senderUsername = ctx.from?.username;\n const chatId = ctx.chat?.id?.toString() ?? '';\n telegramReplyTracker.reset(accountId, chatId);\n\n const securityCtx: ChannelSecurityContext = {\n accountId,\n chatId,\n senderId,\n senderName: senderUsername,\n isGroup,\n };\n\n const accessResult = this.security.checkAccess?.(securityCtx, account, this.cfg);\n\n if (!accessResult?.allowed) {\n if (!isGroup && accessResult?.reason === 'pairing-required') {\n const cid = ctx.chat?.id;\n const bot = cid != null ? this.accountManager.getBot(accountId) : undefined;\n if (bot && cid != null && senderId) {\n void issuePairingChallenge({\n channel: 'telegram',\n pairingFilePath: resolveStandardPairingPath('telegram', accountId),\n accountId,\n senderId,\n senderIdLine: `Your Telegram user id: ${senderId}`,\n sendPairingReply: async (text) => {\n await bot.api.sendMessage(cid, text);\n },\n onCreated: ({ code }) => {\n log.info({ accountId, senderId, code }, 'Telegram DM pairing code issued');\n },\n onReplyError: (err) => {\n log.warn({ err, accountId, chatId: String(cid) }, 'Telegram pairing reply failed');\n },\n });\n }\n return;\n }\n log.warn(\n {\n accountId,\n chatId,\n senderId,\n isGroup,\n reason: accessResult?.reason,\n dmPolicy: account.dmPolicy,\n groupPolicy: account.groupPolicy,\n },\n 'Telegram: message dropped by channel security (check dmPolicy/groupPolicy and allowFrom)',\n );\n return;\n }\n\n const bot = this.accountManager.getBot(accountId);\n const messageId = ctx.message?.message_id;\n const reactionLevel = (account as { reactionLevel?: string }).reactionLevel ?? 'ack';\n const ackEmoji = (account as { ackReaction?: string }).ackReaction ?? '👀';\n if (\n bot &&\n messageId &&\n reactionLevel !== 'off' &&\n ctx.chat?.id != null &&\n !ctx.channelPost\n ) {\n void sendTelegramAckReaction({\n bot,\n chatId: ctx.chat.id,\n messageId,\n emoji: ackEmoji,\n });\n }\n\n await this.inboundProcessor(ctx, accountId);\n }\n}\n\nexport const telegramPlugin = new TelegramChannelPlugin();\n"],"mappings":"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"}
|
|
@@ -19,6 +19,6 @@
|
|
|
19
19
|
* emits ASCII-friendly output and we don't want HTML/markdown surprises with
|
|
20
20
|
* `<` in agent labels or shell-style payloads.
|
|
21
21
|
*/
|
|
22
|
-
import type { ChannelProgressCapability } from '@xopcai/xopc/agent/workflow/
|
|
22
|
+
import type { ChannelProgressCapability } from '@xopcai/xopc/agent/workflow/channel-capability.js';
|
|
23
23
|
import type { TelegramAccountManager } from './account-manager.js';
|
|
24
24
|
export declare function createTelegramWorkflowProgressCapability(accountManager: TelegramAccountManager): ChannelProgressCapability;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/telegram/src/workflow-progress.ts"],"sourcesContent":["/**\n * Telegram capability for the {@link WorkflowProgressBroker}.\n *\n * Strategy: send the first progress update as a fresh message, then edit it in\n * place on every subsequent update. The broker enforces per-channel throttle\n * (default 5 s) and bypasses it for key events (phase change / new error /\n * tool_end), so users always see milestones promptly without us hammering\n * Telegram's edit rate limit.\n *\n * Failure handling is intentionally narrow:\n * - `message is not modified` from `editMessageText` → swallow (it's a\n * no-op the broker shouldn't retry).\n * - Lost / inaccessible message during edit → fall back to a new send so\n * the user always sees the latest snapshot.\n * - 429 / network → bubble up, broker logs and drops the update; the next\n * scheduled (or key) tick will retry.\n *\n * We deliberately use plain text (no parse_mode) — the snapshot renderer\n * emits ASCII-friendly output and we don't want HTML/markdown surprises with\n * `<` in agent labels or shell-style payloads.\n */\n\nimport type { ChannelProgressCapability, WorkflowProgressPostInput } from '@xopcai/xopc/agent/workflow/
|
|
1
|
+
{"version":3,"file":"workflow-progress.js","names":[],"sources":["../../../../extensions/telegram/src/workflow-progress.ts"],"sourcesContent":["/**\n * Telegram capability for the {@link WorkflowProgressBroker}.\n *\n * Strategy: send the first progress update as a fresh message, then edit it in\n * place on every subsequent update. The broker enforces per-channel throttle\n * (default 5 s) and bypasses it for key events (phase change / new error /\n * tool_end), so users always see milestones promptly without us hammering\n * Telegram's edit rate limit.\n *\n * Failure handling is intentionally narrow:\n * - `message is not modified` from `editMessageText` → swallow (it's a\n * no-op the broker shouldn't retry).\n * - Lost / inaccessible message during edit → fall back to a new send so\n * the user always sees the latest snapshot.\n * - 429 / network → bubble up, broker logs and drops the update; the next\n * scheduled (or key) tick will retry.\n *\n * We deliberately use plain text (no parse_mode) — the snapshot renderer\n * emits ASCII-friendly output and we don't want HTML/markdown surprises with\n * `<` in agent labels or shell-style payloads.\n */\n\nimport type { ChannelProgressCapability, WorkflowProgressPostInput } from '@xopcai/xopc/agent/workflow/channel-capability.js';\nimport { parseSessionKey } from '@xopcai/xopc/routing/session-key.js';\nimport { createLogger } from '@xopcai/xopc/utils/logger.js';\n\nimport type { TelegramAccountManager } from './account-manager.js';\n\nconst log = createLogger('TelegramWorkflowProgress');\n\n/** Telegram bot API: 4096 char hard cap; leave 96 chars of headroom for re-renders. */\nconst TELEGRAM_TEXT_MAX = 4000;\n\n/** Telegram bot edit rate ≈ 1/s per chat; broker default 5 s leaves ample headroom. */\nconst DEFAULT_THROTTLE_MS = 5_000;\n\nexport function createTelegramWorkflowProgressCapability(\n accountManager: TelegramAccountManager,\n): ChannelProgressCapability {\n return {\n channelId: 'telegram',\n supportsEdit: true,\n defaultThrottleMs: DEFAULT_THROTTLE_MS,\n defaultMode: 'edit',\n\n async postProgress(input: WorkflowProgressPostInput) {\n const target = resolveTarget(input.sessionKey);\n if (!target) {\n throw new Error(`telegram workflow progress: cannot route sessionKey \"${input.sessionKey}\"`);\n }\n const bot = accountManager.getBot(target.accountId);\n if (!bot) {\n throw new Error(\n `telegram workflow progress: no bot for accountId \"${target.accountId}\" (sessionKey \"${input.sessionKey}\")`,\n );\n }\n\n const text = clampForTelegram(input.text);\n\n // Edit-in-place when we have a previous message id and we're not on the\n // final send (final could be substantially longer; sending fresh keeps\n // the conclusion visible even when scrolled past the streaming bubble).\n if (input.previousMessageId && !input.isFinal) {\n try {\n await bot.api.editMessageText(\n target.chatId,\n Number(input.previousMessageId),\n text,\n );\n return { messageId: input.previousMessageId };\n } catch (err) {\n if (isMessageNotModified(err)) {\n // Snapshot rendered identically to the last edit (e.g. only\n // running spinner ticked) — silently keep the same message id.\n return { messageId: input.previousMessageId };\n }\n if (isEditTargetGone(err)) {\n log.debug(\n { sessionKey: input.sessionKey, previousMessageId: input.previousMessageId },\n 'edit target gone; falling back to sendMessage',\n );\n // fall through to the send path below\n } else {\n throw err;\n }\n }\n }\n\n const sent = await bot.api.sendMessage(target.chatId, text, {\n message_thread_id: target.threadId ? Number(target.threadId) : undefined,\n disable_notification: !input.isFinal,\n });\n return { messageId: String(sent.message_id) };\n },\n };\n}\n\ninterface ResolvedTarget {\n accountId: string;\n chatId: string;\n threadId?: string;\n}\n\nfunction resolveTarget(sessionKey: string): ResolvedTarget | null {\n const parsed = parseSessionKey(sessionKey);\n if (!parsed) return null;\n if (parsed.source !== 'telegram') return null;\n if (!parsed.peerId) return null;\n return {\n accountId: parsed.accountId,\n chatId: parsed.peerId,\n threadId: parsed.threadId,\n };\n}\n\nfunction clampForTelegram(text: string): string {\n if (text.length <= TELEGRAM_TEXT_MAX) return text;\n return `${text.slice(0, TELEGRAM_TEXT_MAX - 1)}…`;\n}\n\nfunction isMessageNotModified(err: unknown): boolean {\n const msg = errorMessage(err).toLowerCase();\n return msg.includes('message is not modified');\n}\n\nfunction isEditTargetGone(err: unknown): boolean {\n const msg = errorMessage(err).toLowerCase();\n return (\n msg.includes('message to edit not found') ||\n msg.includes(\"message can't be edited\") ||\n msg.includes('chat not found')\n );\n}\n\nfunction errorMessage(err: unknown): string {\n if (!err) return '';\n if (err instanceof Error) return err.message;\n return String(err);\n}\n"],"mappings":";;;;kBAuBsE;aACV;AAI5D,MAAM,MAAM,aAAa,2BAA2B;;AAGpD,MAAM,oBAAoB;;AAG1B,MAAM,sBAAsB;AAE5B,SAAgB,yCACd,gBAC2B;AAC3B,QAAO;EACL,WAAW;EACX,cAAc;EACd,mBAAmB;EACnB,aAAa;EAEb,MAAM,aAAa,OAAkC;GACnD,MAAM,SAAS,cAAc,MAAM,WAAW;AAC9C,OAAI,CAAC,OACH,OAAM,IAAI,MAAM,wDAAwD,MAAM,WAAW,GAAG;GAE9F,MAAM,MAAM,eAAe,OAAO,OAAO,UAAU;AACnD,OAAI,CAAC,IACH,OAAM,IAAI,MACR,qDAAqD,OAAO,UAAU,iBAAiB,MAAM,WAAW,IACzG;GAGH,MAAM,OAAO,iBAAiB,MAAM,KAAK;AAKzC,OAAI,MAAM,qBAAqB,CAAC,MAAM,QACpC,KAAI;AACF,UAAM,IAAI,IAAI,gBACZ,OAAO,QACP,OAAO,MAAM,kBAAkB,EAC/B,KACD;AACD,WAAO,EAAE,WAAW,MAAM,mBAAmB;YACtC,KAAK;AACZ,QAAI,qBAAqB,IAAI,CAG3B,QAAO,EAAE,WAAW,MAAM,mBAAmB;AAE/C,QAAI,iBAAiB,IAAI,CACvB,KAAI,MACF;KAAE,YAAY,MAAM;KAAY,mBAAmB,MAAM;KAAmB,EAC5E,gDACD;QAGD,OAAM;;GAKZ,MAAM,OAAO,MAAM,IAAI,IAAI,YAAY,OAAO,QAAQ,MAAM;IAC1D,mBAAmB,OAAO,WAAW,OAAO,OAAO,SAAS,GAAG,KAAA;IAC/D,sBAAsB,CAAC,MAAM;IAC9B,CAAC;AACF,UAAO,EAAE,WAAW,OAAO,KAAK,WAAW,EAAE;;EAEhD;;AASH,SAAS,cAAc,YAA2C;CAChE,MAAM,SAAS,gBAAgB,WAAW;AAC1C,KAAI,CAAC,OAAQ,QAAO;AACpB,KAAI,OAAO,WAAW,WAAY,QAAO;AACzC,KAAI,CAAC,OAAO,OAAQ,QAAO;AAC3B,QAAO;EACL,WAAW,OAAO;EAClB,QAAQ,OAAO;EACf,UAAU,OAAO;EAClB;;AAGH,SAAS,iBAAiB,MAAsB;AAC9C,KAAI,KAAK,UAAU,kBAAmB,QAAO;AAC7C,QAAO,GAAG,KAAK,MAAM,GAAG,oBAAoB,EAAE,CAAC;;AAGjD,SAAS,qBAAqB,KAAuB;AAEnD,QADY,aAAa,IAAI,CAAC,aACpB,CAAC,SAAS,0BAA0B;;AAGhD,SAAS,iBAAiB,KAAuB;CAC/C,MAAM,MAAM,aAAa,IAAI,CAAC,aAAa;AAC3C,QACE,IAAI,SAAS,4BAA4B,IACzC,IAAI,SAAS,0BAA0B,IACvC,IAAI,SAAS,iBAAiB;;AAIlC,SAAS,aAAa,KAAsB;AAC1C,KAAI,CAAC,IAAK,QAAO;AACjB,KAAI,eAAe,MAAO,QAAO,IAAI;AACrC,QAAO,OAAO,IAAI"}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "telegram",
|
|
3
3
|
"name": "Telegram Channel",
|
|
4
4
|
"description": "Bundled Telegram Bot channel (private workspace sources; ships inside @xopcai/xopc dist/)",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.106",
|
|
6
6
|
"kind": "channel",
|
|
7
7
|
"main": "src/index.ts",
|
|
8
8
|
"channels": [
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gateway / web UI: start Weixin QR login, poll status, persist credentials (same as CLI).
|
|
3
3
|
*/
|
|
4
|
+
import type { Config } from "@xopcai/xopc/config/schema.js";
|
|
4
5
|
export type WeixinGatewayQrLoginStatus = {
|
|
5
6
|
phase: "polling";
|
|
6
7
|
qrcodeUrl: string;
|
|
@@ -24,11 +25,14 @@ export type WeixinGatewayQrLoginStartOptions = {
|
|
|
24
25
|
account?: string;
|
|
25
26
|
timeoutMs?: number;
|
|
26
27
|
verbose?: boolean;
|
|
28
|
+
/** Config snapshot from the already-running gateway; avoids loading the heavy config loader from Electron dynamic extension code. */
|
|
29
|
+
initialConfig?: Config;
|
|
27
30
|
/** After credentials + config file are written (or login failed after wait). */
|
|
28
31
|
onPersisted?: (r: {
|
|
29
32
|
ok: boolean;
|
|
30
33
|
accountId?: string;
|
|
31
34
|
message: string;
|
|
35
|
+
config?: Config;
|
|
32
36
|
}) => void | Promise<void>;
|
|
33
37
|
};
|
|
34
38
|
/**
|
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import { init_loader, loadConfig, saveConfig } from "../../../../src/config/loader.js";
|
|
2
1
|
import { logger } from "../util/logger.js";
|
|
3
2
|
import { normalizeWeixinAccountId } from "../auth/weixin-account-id.js";
|
|
4
3
|
import { clearContextTokensForAccount } from "../messaging/inbound.js";
|
|
5
4
|
import { clearStaleAccountsForUserId, registerWeixinAccountId, saveWeixinAccount } from "../auth/accounts.js";
|
|
6
5
|
import { getWeixinActiveLoginSnapshot, startWeixinLoginWithQr, waitForWeixinLogin } from "../auth/login-qr.js";
|
|
7
6
|
import { getWeixinLoginApiContext, mergeWeixinConfigAfterLogin } from "./qr-login.js";
|
|
7
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
8
8
|
//#region extensions/weixin/src/cli/gateway-qr-login.ts
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Gateway / web UI: start Weixin QR login, poll status, persist credentials (same as CLI).
|
|
11
|
+
*/
|
|
10
12
|
const completedSessions = /* @__PURE__ */ new Map();
|
|
13
|
+
function loadConfigSnapshot(configPath, fallback) {
|
|
14
|
+
if (fallback) return fallback;
|
|
15
|
+
if (!configPath) return {};
|
|
16
|
+
try {
|
|
17
|
+
return JSON.parse(readFileSync(configPath, "utf8"));
|
|
18
|
+
} catch {
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function saveConfigSnapshot(configPath, config) {
|
|
23
|
+
if (!configPath) return;
|
|
24
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
25
|
+
}
|
|
11
26
|
function rememberCompleted(sessionKey, state) {
|
|
12
27
|
completedSessions.set(sessionKey, state);
|
|
13
28
|
setTimeout(() => completedSessions.delete(sessionKey), 10 * 6e4);
|
|
@@ -42,7 +57,7 @@ function getWeixinGatewayQrLoginStatus(sessionKey) {
|
|
|
42
57
|
*/
|
|
43
58
|
async function startWeixinGatewayQrLogin(opts) {
|
|
44
59
|
const configPath = opts.configPath ?? process.env.XOPC_CONFIG_PATH;
|
|
45
|
-
const cfg =
|
|
60
|
+
const cfg = loadConfigSnapshot(configPath, opts.initialConfig);
|
|
46
61
|
const { baseUrl, routeTag } = getWeixinLoginApiContext(cfg, opts.account);
|
|
47
62
|
const timeoutMs = opts.timeoutMs ?? 48e4;
|
|
48
63
|
const verbose = Boolean(opts.verbose);
|
|
@@ -103,14 +118,28 @@ async function startWeixinGatewayQrLogin(opts) {
|
|
|
103
118
|
return;
|
|
104
119
|
}
|
|
105
120
|
try {
|
|
106
|
-
|
|
121
|
+
const nextCfg = mergeWeixinConfigAfterLogin(cfg, normalizedId);
|
|
122
|
+
saveConfigSnapshot(configPath, nextCfg);
|
|
123
|
+
await opts.onPersisted?.({
|
|
124
|
+
ok: true,
|
|
125
|
+
accountId: normalizedId,
|
|
126
|
+
message: "OK",
|
|
127
|
+
config: nextCfg
|
|
128
|
+
});
|
|
129
|
+
rememberCompleted(sessionKey, {
|
|
130
|
+
phase: "done",
|
|
131
|
+
ok: true,
|
|
132
|
+
accountId: normalizedId
|
|
133
|
+
});
|
|
134
|
+
return;
|
|
107
135
|
} catch (err) {
|
|
108
136
|
logger.warn(`Config merge failed (credentials saved on disk): ${String(err)}`);
|
|
109
137
|
}
|
|
110
138
|
await opts.onPersisted?.({
|
|
111
139
|
ok: true,
|
|
112
140
|
accountId: normalizedId,
|
|
113
|
-
message: "OK"
|
|
141
|
+
message: "OK",
|
|
142
|
+
config: cfg
|
|
114
143
|
});
|
|
115
144
|
rememberCompleted(sessionKey, {
|
|
116
145
|
phase: "done",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gateway-qr-login.js","names":[],"sources":["../../../../../extensions/weixin/src/cli/gateway-qr-login.ts"],"sourcesContent":["/**\n * Gateway / web UI: start Weixin QR login, poll status, persist credentials (same as CLI).\n */\n\nimport
|
|
1
|
+
{"version":3,"file":"gateway-qr-login.js","names":[],"sources":["../../../../../extensions/weixin/src/cli/gateway-qr-login.ts"],"sourcesContent":["/**\n * Gateway / web UI: start Weixin QR login, poll status, persist credentials (same as CLI).\n */\n\nimport { readFileSync, writeFileSync } from \"node:fs\";\n\nimport type { Config } from \"@xopcai/xopc/config/schema.js\";\n\nimport {\n clearStaleAccountsForUserId,\n normalizeWeixinAccountId,\n registerWeixinAccountId,\n saveWeixinAccount,\n} from \"../auth/accounts.js\";\nimport {\n DEFAULT_ILINK_BOT_TYPE,\n getWeixinActiveLoginSnapshot,\n startWeixinLoginWithQr,\n waitForWeixinLogin,\n} from \"../auth/login-qr.js\";\nimport { clearContextTokensForAccount } from \"../messaging/inbound.js\";\nimport { logger } from \"../util/logger.js\";\n\nimport { getWeixinLoginApiContext, mergeWeixinConfigAfterLogin } from \"./qr-login.js\";\n\nexport type WeixinGatewayQrLoginStatus =\n | {\n phase: \"polling\";\n qrcodeUrl: string;\n /** Sub-state from ilink while waiting */\n qrStatus?: string;\n }\n | { phase: \"done\"; ok: true; accountId: string }\n | { phase: \"done\"; ok: false; message: string }\n | { phase: \"unknown\"; message: string };\n\ntype TerminalRecord =\n | { phase: \"done\"; ok: true; accountId: string }\n | { phase: \"done\"; ok: false; message: string };\n\nconst completedSessions = new Map<string, TerminalRecord>();\n\nfunction loadConfigSnapshot(configPath: string | undefined, fallback: Config | undefined): Config {\n if (fallback) return fallback;\n if (!configPath) return {} as Config;\n try {\n return JSON.parse(readFileSync(configPath, 'utf8')) as Config;\n } catch {\n return {} as Config;\n }\n}\n\nfunction saveConfigSnapshot(configPath: string | undefined, config: Config): void {\n if (!configPath) return;\n writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, 'utf8');\n}\n\nfunction rememberCompleted(sessionKey: string, state: TerminalRecord): void {\n completedSessions.set(sessionKey, state);\n setTimeout(() => completedSessions.delete(sessionKey), 10 * 60_000);\n}\n\nexport function getWeixinGatewayQrLoginStatus(sessionKey: string): WeixinGatewayQrLoginStatus {\n const done = completedSessions.get(sessionKey);\n if (done) {\n if (done.ok === true) {\n return { phase: \"done\", ok: true, accountId: done.accountId };\n }\n return { phase: \"done\", ok: false, message: done.message };\n }\n\n const snap = getWeixinActiveLoginSnapshot(sessionKey);\n if (snap) {\n return {\n phase: \"polling\",\n qrcodeUrl: snap.qrcodeUrl,\n qrStatus: snap.status,\n };\n }\n\n return {\n phase: \"unknown\",\n message: \"No active login for this session. Start again or the QR may have expired.\",\n };\n}\n\nexport type WeixinGatewayQrLoginStartOptions = {\n configPath?: string;\n account?: string;\n timeoutMs?: number;\n verbose?: boolean;\n /** Config snapshot from the already-running gateway; avoids loading the heavy config loader from Electron dynamic extension code. */\n initialConfig?: Config;\n /** After credentials + config file are written (or login failed after wait). */\n onPersisted?: (r: { ok: boolean; accountId?: string; message: string; config?: Config }) => void | Promise<void>;\n};\n\n/**\n * Begin QR login and wait in the background; poll with {@link getWeixinGatewayQrLoginStatus}.\n */\nexport async function startWeixinGatewayQrLogin(\n opts: WeixinGatewayQrLoginStartOptions,\n): Promise<{ ok: true; sessionKey: string; qrcodeUrl: string } | { ok: false; message: string }> {\n const configPath = opts.configPath ?? process.env.XOPC_CONFIG_PATH;\n const cfg = loadConfigSnapshot(configPath, opts.initialConfig);\n const { baseUrl, routeTag } = getWeixinLoginApiContext(cfg, opts.account);\n const timeoutMs = opts.timeoutMs ?? 480_000;\n const verbose = Boolean(opts.verbose);\n\n const startResult = await startWeixinLoginWithQr({\n accountId: opts.account?.trim() || undefined,\n botType: DEFAULT_ILINK_BOT_TYPE,\n routeTag,\n verbose,\n });\n\n if (!startResult.qrcodeUrl) {\n return { ok: false, message: startResult.message || \"Failed to get QR code\" };\n }\n\n const sessionKey = startResult.sessionKey;\n\n void (async () => {\n const waitResult = await waitForWeixinLogin({\n sessionKey,\n timeoutMs,\n verbose,\n routeTag,\n botType: DEFAULT_ILINK_BOT_TYPE,\n silent: true,\n });\n\n if (!waitResult.connected || !waitResult.botToken || !waitResult.accountId) {\n const message = waitResult.message || \"Login did not complete\";\n rememberCompleted(sessionKey, { phase: \"done\", ok: false, message });\n await opts.onPersisted?.({ ok: false, message });\n return;\n }\n\n const normalizedId = normalizeWeixinAccountId(waitResult.accountId);\n\n try {\n saveWeixinAccount(normalizedId, {\n token: waitResult.botToken,\n baseUrl: waitResult.baseUrl?.trim() || baseUrl,\n userId: waitResult.userId,\n });\n registerWeixinAccountId(normalizedId);\n if (waitResult.userId) {\n clearStaleAccountsForUserId(normalizedId, waitResult.userId, clearContextTokensForAccount);\n }\n } catch (err) {\n const message = `Saved login failed: ${String(err)}`;\n logger.error(message);\n rememberCompleted(sessionKey, { phase: \"done\", ok: false, message });\n await opts.onPersisted?.({ ok: false, message });\n return;\n }\n\n try {\n const nextCfg = mergeWeixinConfigAfterLogin(cfg as Config, normalizedId);\n saveConfigSnapshot(configPath, nextCfg);\n await opts.onPersisted?.({ ok: true, accountId: normalizedId, message: \"OK\", config: nextCfg });\n rememberCompleted(sessionKey, { phase: \"done\", ok: true, accountId: normalizedId });\n return;\n } catch (err) {\n logger.warn(`Config merge failed (credentials saved on disk): ${String(err)}`);\n }\n\n await opts.onPersisted?.({ ok: true, accountId: normalizedId, message: \"OK\", config: cfg as Config });\n rememberCompleted(sessionKey, { phase: \"done\", ok: true, accountId: normalizedId });\n })();\n\n return { ok: true, sessionKey, qrcodeUrl: startResult.qrcodeUrl };\n}\n"],"mappings":"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"}
|