@poolzin/pool-bot 2026.3.25 → 2026.3.27
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/agents/model-fallback.js +5 -4
- package/dist/agents/tools/common.js +16 -201
- package/dist/auto-reply/auto-reply/reply/agent-runner-execution.js +502 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-helpers.js +65 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-memory.js +160 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-payloads.js +85 -0
- package/dist/auto-reply/auto-reply/reply/agent-runner-utils.js +101 -0
- package/dist/auto-reply/auto-reply/reply/bash-command.js +338 -0
- package/dist/auto-reply/auto-reply/reply/block-streaming.js +91 -0
- package/dist/auto-reply/auto-reply/reply/commands-approve.js +88 -0
- package/dist/auto-reply/auto-reply/reply/commands-bash.js +26 -0
- package/dist/auto-reply/auto-reply/reply/commands-compact.js +107 -0
- package/dist/auto-reply/auto-reply/reply/commands-config.js +241 -0
- package/dist/auto-reply/auto-reply/reply/commands-context-report.js +295 -0
- package/dist/auto-reply/auto-reply/reply/commands-context.js +30 -0
- package/dist/auto-reply/auto-reply/reply/commands-core.js +151 -0
- package/dist/auto-reply/auto-reply/reply/commands-export-session.js +163 -0
- package/dist/auto-reply/auto-reply/reply/commands-info.js +184 -0
- package/dist/auto-reply/auto-reply/reply/commands-models.js +299 -0
- package/dist/auto-reply/auto-reply/reply/commands-plugin.js +35 -0
- package/dist/auto-reply/auto-reply/reply/commands-ptt.js +171 -0
- package/dist/auto-reply/auto-reply/reply/commands-setunset-standard.js +13 -0
- package/dist/auto-reply/auto-reply/reply/commands-setunset.js +73 -0
- package/dist/auto-reply/auto-reply/reply/commands-slash-parse.js +31 -0
- package/dist/auto-reply/auto-reply/reply/commands-status.js +178 -0
- package/dist/auto-reply/auto-reply/reply/commands-subagents.js +73 -0
- package/dist/auto-reply/auto-reply/reply/commands-system-prompt.js +117 -0
- package/dist/auto-reply/auto-reply/reply/commands-tts.js +231 -0
- package/dist/auto-reply/auto-reply/reply/directive-handling.impl.js +380 -0
- package/dist/auto-reply/auto-reply/reply/followup-runner.js +227 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives-apply.js +201 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives-utils.js +54 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-directives.js +332 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-inline-actions.js +258 -0
- package/dist/auto-reply/auto-reply/reply/get-reply-run.js +297 -0
- package/dist/auto-reply/auto-reply/reply/groups.js +102 -0
- package/dist/auto-reply/auto-reply/reply/mentions.js +129 -0
- package/dist/auto-reply/auto-reply/reply/reply-delivery.js +92 -0
- package/dist/auto-reply/auto-reply/reply/reply-directives.js +30 -0
- package/dist/auto-reply/auto-reply/reply/reply-dispatcher.js +152 -0
- package/dist/auto-reply/auto-reply/reply/reply-elevated.js +166 -0
- package/dist/auto-reply/auto-reply/reply/reply-inline.js +28 -0
- package/dist/auto-reply/auto-reply/reply/reply-payloads.js +114 -0
- package/dist/auto-reply/auto-reply/reply/reply-reference.js +36 -0
- package/dist/auto-reply/auto-reply/reply/reply-tags.js +13 -0
- package/dist/auto-reply/auto-reply/reply/reply-threading.js +41 -0
- package/dist/auto-reply/auto-reply/reply/session-updates.js +233 -0
- package/dist/auto-reply/auto-reply/reply/stage-sandbox-media.js +146 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/canvas-host/a2ui/a2ui.bundle.js +2 -17772
- package/dist/canvas-host/a2ui/index.html +1 -307
- package/dist/channels/channels/directory-config.js +185 -0
- package/dist/channels/channels/discord/handle-action.guild-admin.js +332 -0
- package/dist/channels/channels/discord/handle-action.js +165 -0
- package/dist/channels/channels/discord.js +413 -0
- package/dist/channels/channels/dock.js +436 -0
- package/dist/channels/channels/index.js +51 -0
- package/dist/channels/channels/plugins/outbound/discord.js +101 -0
- package/dist/channels/channels/whatsapp.js +17 -0
- package/dist/channels/plugins/types.js +1 -1
- package/dist/channels/run-state-machine.js +7 -0
- package/dist/commands/models/auth.js +47 -1
- package/dist/commands-subagents/action-agents.js +44 -0
- package/dist/commands-subagents/action-focus.js +64 -0
- package/dist/commands-subagents/action-help.js +4 -0
- package/dist/commands-subagents/action-info.js +45 -0
- package/dist/commands-subagents/action-kill.js +60 -0
- package/dist/commands-subagents/action-list.js +44 -0
- package/dist/commands-subagents/action-log.js +29 -0
- package/dist/commands-subagents/action-send.js +119 -0
- package/dist/commands-subagents/action-spawn.js +52 -0
- package/dist/commands-subagents/action-unfocus.js +30 -0
- package/dist/commands-subagents/shared.js +303 -0
- package/dist/config/config.js +1 -8
- package/dist/config/types.secrets.js +61 -0
- package/dist/control-ui/assets/{index-D7shnQwQ.js → index-umCsvrWy.js} +884 -741
- package/dist/control-ui/assets/index-umCsvrWy.js.map +1 -0
- package/dist/control-ui/assets/pt-BR-DedEVAvY.js +2 -0
- package/dist/control-ui/assets/pt-BR-DedEVAvY.js.map +1 -0
- package/dist/control-ui/assets/zh-CN-CDzeklK-.js +2 -0
- package/dist/control-ui/assets/zh-CN-CDzeklK-.js.map +1 -0
- package/dist/control-ui/assets/zh-TW-BJCRYNWH.js +2 -0
- package/dist/control-ui/assets/zh-TW-BJCRYNWH.js.map +1 -0
- package/dist/control-ui/index.html +1 -1
- package/dist/gateway/method-scopes.js +9 -1
- package/dist/gateway/node-pending-work.js +142 -0
- package/dist/gateway/protocol/index.js +5 -1
- package/dist/gateway/protocol/schema/nodes.js +18 -0
- package/dist/gateway/server-methods/nodes-pending.js +96 -0
- package/dist/gateway/server-methods-list.js +4 -0
- package/dist/gateway/server-methods.js +2 -0
- package/dist/imessage/channel.js +253 -0
- package/dist/imessage/monitor/echo-cache.js +70 -0
- package/dist/imessage/monitor/loop-rate-limiter.js +51 -0
- package/dist/imessage/monitor/reflection-guard.js +50 -0
- package/dist/imessage/monitor/sanitize-outbound.js +25 -0
- package/dist/imessage/monitor/self-chat-cache.js +75 -0
- package/dist/imessage/runtime.js +3 -0
- package/dist/infra/exec-approval-reply.js +7 -0
- package/dist/infra/tmp-openclaw-dir.js +84 -0
- package/dist/pairing/pairing-challenge.js +15 -0
- package/dist/plugin-sdk/account-id.d.ts +1 -0
- package/dist/plugin-sdk/agent-media-payload.d.ts +12 -0
- package/dist/plugin-sdk/allow-from.d.ts +27 -0
- package/dist/plugin-sdk/command-auth.d.ts +25 -0
- package/dist/plugin-sdk/command-auth.js +3 -1
- package/dist/plugin-sdk/config-paths.d.ts +6 -0
- package/dist/plugin-sdk/file-lock.d.ts +16 -0
- package/dist/plugin-sdk/index.d.ts +428 -0
- package/dist/plugin-sdk/index.js +237 -103
- package/dist/plugin-sdk/json-store.d.ts +5 -0
- package/dist/plugin-sdk/keyed-async-queue.d.ts +12 -0
- package/dist/plugin-sdk/onboarding.d.ts +11 -0
- package/dist/plugin-sdk/provider-auth-result.d.ts +14 -0
- package/dist/plugin-sdk/slack-message-actions.d.ts +11 -0
- package/dist/plugin-sdk/status-helpers.d.ts +25 -0
- package/dist/plugin-sdk/temp-path.d.ts +12 -0
- package/dist/plugin-sdk/text-chunking.d.ts +1 -0
- package/dist/plugin-sdk/tool-send.d.ts +4 -0
- package/dist/plugin-sdk/webhook-path.d.ts +6 -0
- package/dist/plugin-sdk/webhook-targets.d.ts +23 -0
- package/dist/plugin-sdk/windows-spawn.d.ts +39 -0
- package/dist/plugin-sdk-internal/accounts.js +6 -0
- package/dist/plugin-sdk-internal/discord.js +23 -0
- package/dist/plugin-sdk-internal/imessage.js +13 -0
- package/dist/plugin-sdk-internal/setup.js +9 -0
- package/dist/plugin-sdk-internal/signal.js +13 -0
- package/dist/plugin-sdk-internal/slack.js +22 -0
- package/dist/plugin-sdk-internal/telegram.js +32 -0
- package/dist/plugin-sdk-internal/whatsapp.js +29 -0
- package/dist/routing/session-key.js +4 -185
- package/dist/shared/pid-alive.js +2 -61
- package/dist/shared/process-scoped-map.js +5 -7
- package/dist/signal/channel.js +264 -0
- package/dist/signal/monitor/access-policy.js +60 -0
- package/dist/signal/runtime.js +3 -0
- package/dist/slack/account-inspect.js +135 -0
- package/dist/slack/blocks-input.js +7 -38
- package/dist/slack/channel.js +394 -0
- package/dist/slack/interactive-replies.js +28 -0
- package/dist/slack/monitor/channel-type.js +31 -0
- package/dist/slack/monitor/dm-auth.js +49 -0
- package/dist/slack/monitor/events/interactions.modal.js +137 -0
- package/dist/slack/monitor/events/message-subtype-handlers.js +68 -0
- package/dist/slack/monitor/events/system-event-context.js +29 -0
- package/dist/slack/monitor/events/system-event-test-harness.js +41 -0
- package/dist/slack/monitor/external-arg-menu-store.js +46 -0
- package/dist/slack/monitor/message-handler/prepare-content.js +69 -0
- package/dist/slack/monitor/message-handler/prepare-thread-context.js +91 -0
- package/dist/slack/monitor/message-handler/prepare.test-helpers.js +55 -0
- package/dist/slack/monitor/reconnect-policy.js +78 -0
- package/dist/slack/monitor/slash-commands.runtime.js +1 -0
- package/dist/slack/monitor/slash-dispatch.runtime.js +9 -0
- package/dist/slack/monitor/slash-skill-commands.runtime.js +1 -0
- package/dist/slack/resolve-allowlist-common.js +36 -0
- package/dist/slack/runtime.js +3 -0
- package/dist/slack/sent-thread-cache.js +61 -0
- package/dist/slack/truncate.js +10 -0
- package/dist/telegram/account-inspect.js +175 -0
- package/dist/telegram/allow-from.js +10 -0
- package/dist/telegram/api-fetch.js +18 -0
- package/dist/telegram/approval-buttons.js +30 -0
- package/dist/telegram/audit-membership-runtime.js +61 -0
- package/dist/telegram/bot/delivery.replies.js +508 -0
- package/dist/telegram/bot/delivery.resolve-media.js +227 -0
- package/dist/telegram/bot/delivery.send.js +132 -0
- package/dist/telegram/bot/reply-threading.js +46 -0
- package/dist/telegram/bot-message-context.body.js +186 -0
- package/dist/telegram/bot-message-context.session.js +207 -0
- package/dist/telegram/bot-message-context.types.js +1 -0
- package/dist/telegram/bot-native-commands.test-helpers.js +117 -0
- package/dist/telegram/bot.media.e2e-harness.js +81 -0
- package/dist/telegram/bot.media.test-utils.js +81 -0
- package/dist/telegram/channel-actions.js +225 -0
- package/dist/telegram/channel.js +515 -0
- package/dist/telegram/conversation-route.js +107 -0
- package/dist/telegram/delivery.js +2 -0
- package/dist/telegram/delivery.replies.js +508 -0
- package/dist/telegram/dm-access.js +86 -0
- package/dist/telegram/draft-stream.test-helpers.js +62 -0
- package/dist/telegram/exec-approvals-handler.js +281 -0
- package/dist/telegram/exec-approvals.js +62 -0
- package/dist/telegram/forum-service-message.js +22 -0
- package/dist/telegram/group-config-helpers.js +10 -0
- package/dist/telegram/lane-delivery-state.js +19 -0
- package/dist/telegram/lane-delivery-text-deliverer.js +357 -0
- package/dist/telegram/lane-delivery.js +2 -0
- package/dist/telegram/normalize.js +37 -0
- package/dist/telegram/onboarding.js +192 -0
- package/dist/telegram/outbound-adapter.js +100 -0
- package/dist/telegram/polling-session.js +275 -0
- package/dist/telegram/runtime.js +3 -0
- package/dist/telegram/sendchataction-401-backoff.js +71 -0
- package/dist/telegram/sequential-key.js +46 -0
- package/dist/telegram/status-issues.js +105 -0
- package/dist/telegram/target-writeback.js +165 -0
- package/dist/telegram/thread-bindings.js +560 -0
- package/dist/utils.js +32 -257
- package/dist/wizard/prompts.js +5 -5
- package/extensions/feishu/src/policy.ts +1 -1
- package/extensions/firecrawl/index.test.ts +82 -0
- package/extensions/firecrawl/index.ts +20 -0
- package/extensions/firecrawl/openclaw.plugin.json +8 -0
- package/extensions/firecrawl/package.json +12 -0
- package/extensions/firecrawl/src/config.ts +159 -0
- package/extensions/firecrawl/src/firecrawl-client.ts +446 -0
- package/extensions/firecrawl/src/firecrawl-scrape-tool.ts +89 -0
- package/extensions/firecrawl/src/firecrawl-search-provider.ts +63 -0
- package/extensions/firecrawl/src/firecrawl-search-tool.ts +76 -0
- package/package.json +1 -1
- package/dist/.buildstamp +0 -1
- package/dist/acp/bindings-store.js +0 -209
- package/dist/acp/control-plane/runtime-cache.js +0 -54
- package/dist/acp/control-plane/runtime-options.js +0 -215
- package/dist/acp/control-plane/session-actor-queue.js +0 -36
- package/dist/acp/index.js +0 -2
- package/dist/acp/runtime/errors.js +0 -47
- package/dist/acp/runtime/registry.js +0 -86
- package/dist/acp/secret-file.js +0 -22
- package/dist/agents/auth-profiles.resolve-auth-profile-order.fixtures.js +0 -23
- package/dist/agents/bash-process-registry.test-helpers.js +0 -29
- package/dist/agents/bash-tools.exec-approval-request.js +0 -20
- package/dist/agents/bash-tools.exec-host-gateway.js +0 -240
- package/dist/agents/bash-tools.exec-host-node.js +0 -235
- package/dist/agents/checkpoint-manager.js +0 -290
- package/dist/agents/claude-cli-runner.js +0 -3
- package/dist/agents/error-classifier.js +0 -251
- package/dist/agents/live-model-filter.js +0 -84
- package/dist/agents/nvidia-models.js +0 -228
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.fixture.js +0 -34
- package/dist/agents/pi-embedded-runner/run.overflow-compaction.mocks.shared.js +0 -156
- package/dist/agents/pi-embedded-subscribe.handlers.tools.media.test-helpers.js +0 -30
- package/dist/agents/provider/config-loader.js +0 -76
- package/dist/agents/provider/index.js +0 -15
- package/dist/agents/provider/models-dev.js +0 -129
- package/dist/agents/provider/session-binding.js +0 -376
- package/dist/agents/queued-file-writer.js +0 -22
- package/dist/agents/skills/bundled-context.js +0 -23
- package/dist/agents/skills/security.js +0 -211
- package/dist/agents/skills/tools-dir.js +0 -9
- package/dist/agents/skills-install-download.js +0 -290
- package/dist/agents/skills-install-output.js +0 -30
- package/dist/agents/skills-install.download-test-utils.js +0 -36
- package/dist/agents/skills.test-helpers.js +0 -13
- package/dist/agents/subagent-announce-reliability.js +0 -160
- package/dist/agents/subagent-registry.mocks.shared.js +0 -12
- package/dist/agents/test-helpers/assistant-message-fixtures.js +0 -29
- package/dist/agents/test-helpers/fast-coding-tools.js +0 -1
- package/dist/agents/test-helpers/fast-core-tools.js +0 -8
- package/dist/agents/test-helpers/fast-tool-stubs.js +0 -18
- package/dist/agents/test-helpers/host-sandbox-fs-bridge.js +0 -74
- package/dist/agents/test-helpers/pi-tools-sandbox-context.js +0 -27
- package/dist/agents/tool-display-common.js +0 -915
- package/dist/agents/tool-policy-shared.js +0 -108
- package/dist/agents/tool-policy.conformance.js +0 -14
- package/dist/agents/tool-result-truncation.js +0 -299
- package/dist/agents/tools/cron-tool.test-helpers.js +0 -12
- package/dist/agents/tools/discord-actions-moderation-shared.js +0 -27
- package/dist/agents/tools/discord-actions-presence.js +0 -78
- package/dist/control-ui/assets/index-D7shnQwQ.js.map +0 -1
- package/dist/discord/discord-improvements.js +0 -167
- package/dist/discord/index.js +0 -2
- package/dist/hooks/bundled/boot-md/HOOK.md +0 -19
- package/dist/hooks/bundled/command-logger/HOOK.md +0 -122
- package/dist/hooks/bundled/session-memory/HOOK.md +0 -86
- package/dist/hooks/bundled/soul-evil/HOOK.md +0 -71
- package/dist/whatsapp/normalize.js +0 -66
- package/dist/whatsapp/resolve-outbound-target.js +0 -42
- /package/dist/{acp/runtime/types.js → auto-reply/auto-reply/reply/commands-types.js} +0 -0
- /package/dist/{agents/pi-embedded-payloads.js → slack/account-surface-fields.js} +0 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import { GrammyError } from "grammy";
|
|
2
|
+
import { logVerbose, warn } from "../../../../src/globals.js";
|
|
3
|
+
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
|
4
|
+
import { retryAsync } from "../../../../src/infra/retry.js";
|
|
5
|
+
import { fetchRemoteMedia } from "../../../../src/media/fetch.js";
|
|
6
|
+
import { saveMediaBuffer } from "../../../../src/media/store.js";
|
|
7
|
+
import { shouldRetryTelegramIpv4Fallback } from "../fetch.js";
|
|
8
|
+
import { cacheSticker, getCachedSticker } from "../sticker-cache.js";
|
|
9
|
+
import { resolveTelegramMediaPlaceholder } from "./helpers.js";
|
|
10
|
+
const FILE_TOO_BIG_RE = /file is too big/i;
|
|
11
|
+
const TELEGRAM_MEDIA_SSRF_POLICY = {
|
|
12
|
+
// Telegram file downloads should trust api.telegram.org even when DNS/proxy
|
|
13
|
+
// resolution maps to private/internal ranges in restricted networks.
|
|
14
|
+
allowedHostnames: ["api.telegram.org"],
|
|
15
|
+
allowRfc2544BenchmarkRange: true,
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Returns true if the error is Telegram's "file is too big" error.
|
|
19
|
+
* This happens when trying to download files >20MB via the Bot API.
|
|
20
|
+
* Unlike network errors, this is a permanent error and should not be retried.
|
|
21
|
+
*/
|
|
22
|
+
function isFileTooBigError(err) {
|
|
23
|
+
if (err instanceof GrammyError) {
|
|
24
|
+
return FILE_TOO_BIG_RE.test(err.description);
|
|
25
|
+
}
|
|
26
|
+
return FILE_TOO_BIG_RE.test(formatErrorMessage(err));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Returns true if the error is a transient network error that should be retried.
|
|
30
|
+
* Returns false for permanent errors like "file is too big" (400 Bad Request).
|
|
31
|
+
*/
|
|
32
|
+
function isRetryableGetFileError(err) {
|
|
33
|
+
// Don't retry "file is too big" - it's a permanent 400 error
|
|
34
|
+
if (isFileTooBigError(err)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Retry all other errors (network issues, timeouts, etc.)
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
function resolveMediaFileRef(msg) {
|
|
41
|
+
return (msg.photo?.[msg.photo.length - 1] ??
|
|
42
|
+
msg.video ??
|
|
43
|
+
msg.video_note ??
|
|
44
|
+
msg.document ??
|
|
45
|
+
msg.audio ??
|
|
46
|
+
msg.voice);
|
|
47
|
+
}
|
|
48
|
+
function resolveTelegramFileName(msg) {
|
|
49
|
+
return (msg.document?.file_name ??
|
|
50
|
+
msg.audio?.file_name ??
|
|
51
|
+
msg.video?.file_name ??
|
|
52
|
+
msg.animation?.file_name);
|
|
53
|
+
}
|
|
54
|
+
async function resolveTelegramFileWithRetry(ctx) {
|
|
55
|
+
try {
|
|
56
|
+
return await retryAsync(() => ctx.getFile(), {
|
|
57
|
+
attempts: 3,
|
|
58
|
+
minDelayMs: 1000,
|
|
59
|
+
maxDelayMs: 4000,
|
|
60
|
+
jitter: 0.2,
|
|
61
|
+
label: "telegram:getFile",
|
|
62
|
+
shouldRetry: isRetryableGetFileError,
|
|
63
|
+
onRetry: ({ attempt, maxAttempts }) => logVerbose(`telegram: getFile retry ${attempt}/${maxAttempts}`),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
// Handle "file is too big" separately - Telegram Bot API has a 20MB download limit
|
|
68
|
+
if (isFileTooBigError(err)) {
|
|
69
|
+
logVerbose(warn("telegram: getFile failed - file exceeds Telegram Bot API 20MB limit; skipping attachment"));
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
// All retries exhausted — return null so the message still reaches the agent
|
|
73
|
+
// with a type-based placeholder (e.g. <media:audio>) instead of being dropped.
|
|
74
|
+
logVerbose(`telegram: getFile failed after retries: ${String(err)}`);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function resolveRequiredTelegramTransport(transport) {
|
|
79
|
+
if (transport) {
|
|
80
|
+
return transport;
|
|
81
|
+
}
|
|
82
|
+
const resolvedFetch = globalThis.fetch;
|
|
83
|
+
if (!resolvedFetch) {
|
|
84
|
+
throw new Error("fetch is not available; set channels.telegram.proxy in config");
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
fetch: resolvedFetch,
|
|
88
|
+
sourceFetch: resolvedFetch,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
function resolveOptionalTelegramTransport(transport) {
|
|
92
|
+
try {
|
|
93
|
+
return resolveRequiredTelegramTransport(transport);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/** Default idle timeout for Telegram media downloads (30 seconds). */
|
|
100
|
+
const TELEGRAM_DOWNLOAD_IDLE_TIMEOUT_MS = 30_000;
|
|
101
|
+
async function downloadAndSaveTelegramFile(params) {
|
|
102
|
+
const url = `https://api.telegram.org/file/bot${params.token}/${params.filePath}`;
|
|
103
|
+
const fetched = await fetchRemoteMedia({
|
|
104
|
+
url,
|
|
105
|
+
fetchImpl: params.transport.sourceFetch,
|
|
106
|
+
dispatcherPolicy: params.transport.pinnedDispatcherPolicy,
|
|
107
|
+
fallbackDispatcherPolicy: params.transport.fallbackPinnedDispatcherPolicy,
|
|
108
|
+
shouldRetryFetchError: shouldRetryTelegramIpv4Fallback,
|
|
109
|
+
filePathHint: params.filePath,
|
|
110
|
+
maxBytes: params.maxBytes,
|
|
111
|
+
readIdleTimeoutMs: TELEGRAM_DOWNLOAD_IDLE_TIMEOUT_MS,
|
|
112
|
+
ssrfPolicy: TELEGRAM_MEDIA_SSRF_POLICY,
|
|
113
|
+
});
|
|
114
|
+
const originalName = params.telegramFileName ?? fetched.fileName ?? params.filePath;
|
|
115
|
+
return saveMediaBuffer(fetched.buffer, fetched.contentType, "inbound", params.maxBytes, originalName);
|
|
116
|
+
}
|
|
117
|
+
async function resolveStickerMedia(params) {
|
|
118
|
+
const { msg, ctx, maxBytes, token, transport } = params;
|
|
119
|
+
if (!msg.sticker) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
const sticker = msg.sticker;
|
|
123
|
+
// Skip animated (TGS) and video (WEBM) stickers - only static WEBP supported
|
|
124
|
+
if (sticker.is_animated || sticker.is_video) {
|
|
125
|
+
logVerbose("telegram: skipping animated/video sticker (only static stickers supported)");
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
if (!sticker.file_id) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const file = await resolveTelegramFileWithRetry(ctx);
|
|
133
|
+
if (!file?.file_path) {
|
|
134
|
+
logVerbose("telegram: getFile returned no file_path for sticker");
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const resolvedTransport = resolveOptionalTelegramTransport(transport);
|
|
138
|
+
if (!resolvedTransport) {
|
|
139
|
+
logVerbose("telegram: fetch not available for sticker download");
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
const saved = await downloadAndSaveTelegramFile({
|
|
143
|
+
filePath: file.file_path,
|
|
144
|
+
token,
|
|
145
|
+
transport: resolvedTransport,
|
|
146
|
+
maxBytes,
|
|
147
|
+
});
|
|
148
|
+
// Check sticker cache for existing description
|
|
149
|
+
const cached = sticker.file_unique_id ? getCachedSticker(sticker.file_unique_id) : null;
|
|
150
|
+
if (cached) {
|
|
151
|
+
logVerbose(`telegram: sticker cache hit for ${sticker.file_unique_id}`);
|
|
152
|
+
const fileId = sticker.file_id ?? cached.fileId;
|
|
153
|
+
const emoji = sticker.emoji ?? cached.emoji;
|
|
154
|
+
const setName = sticker.set_name ?? cached.setName;
|
|
155
|
+
if (fileId !== cached.fileId || emoji !== cached.emoji || setName !== cached.setName) {
|
|
156
|
+
// Refresh cached sticker metadata on hits so sends/searches use latest file_id.
|
|
157
|
+
cacheSticker({
|
|
158
|
+
...cached,
|
|
159
|
+
fileId,
|
|
160
|
+
emoji,
|
|
161
|
+
setName,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
return {
|
|
165
|
+
path: saved.path,
|
|
166
|
+
contentType: saved.contentType,
|
|
167
|
+
placeholder: "<media:sticker>",
|
|
168
|
+
stickerMetadata: {
|
|
169
|
+
emoji,
|
|
170
|
+
setName,
|
|
171
|
+
fileId,
|
|
172
|
+
fileUniqueId: sticker.file_unique_id,
|
|
173
|
+
cachedDescription: cached.description,
|
|
174
|
+
},
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
// Cache miss - return metadata for vision processing
|
|
178
|
+
return {
|
|
179
|
+
path: saved.path,
|
|
180
|
+
contentType: saved.contentType,
|
|
181
|
+
placeholder: "<media:sticker>",
|
|
182
|
+
stickerMetadata: {
|
|
183
|
+
emoji: sticker.emoji ?? undefined,
|
|
184
|
+
setName: sticker.set_name ?? undefined,
|
|
185
|
+
fileId: sticker.file_id,
|
|
186
|
+
fileUniqueId: sticker.file_unique_id,
|
|
187
|
+
},
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (err) {
|
|
191
|
+
logVerbose(`telegram: failed to process sticker: ${String(err)}`);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
export async function resolveMedia(ctx, maxBytes, token, transport) {
|
|
196
|
+
const msg = ctx.message;
|
|
197
|
+
const stickerResolved = await resolveStickerMedia({
|
|
198
|
+
msg,
|
|
199
|
+
ctx,
|
|
200
|
+
maxBytes,
|
|
201
|
+
token,
|
|
202
|
+
transport,
|
|
203
|
+
});
|
|
204
|
+
if (stickerResolved !== undefined) {
|
|
205
|
+
return stickerResolved;
|
|
206
|
+
}
|
|
207
|
+
const m = resolveMediaFileRef(msg);
|
|
208
|
+
if (!m?.file_id) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
const file = await resolveTelegramFileWithRetry(ctx);
|
|
212
|
+
if (!file) {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
if (!file.file_path) {
|
|
216
|
+
throw new Error("Telegram getFile returned no file_path");
|
|
217
|
+
}
|
|
218
|
+
const saved = await downloadAndSaveTelegramFile({
|
|
219
|
+
filePath: file.file_path,
|
|
220
|
+
token,
|
|
221
|
+
transport: resolveRequiredTelegramTransport(transport),
|
|
222
|
+
maxBytes,
|
|
223
|
+
telegramFileName: resolveTelegramFileName(msg),
|
|
224
|
+
});
|
|
225
|
+
const placeholder = resolveTelegramMediaPlaceholder(msg) ?? "<media:document>";
|
|
226
|
+
return { path: saved.path, contentType: saved.contentType, placeholder };
|
|
227
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { GrammyError } from "grammy";
|
|
2
|
+
import { formatErrorMessage } from "../../../../src/infra/errors.js";
|
|
3
|
+
import { withTelegramApiErrorLogging } from "../api-logging.js";
|
|
4
|
+
import { markdownToTelegramHtml } from "../format.js";
|
|
5
|
+
import { buildTelegramThreadParams } from "./helpers.js";
|
|
6
|
+
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
|
|
7
|
+
const EMPTY_TEXT_ERR_RE = /message text is empty/i;
|
|
8
|
+
const THREAD_NOT_FOUND_RE = /message thread not found/i;
|
|
9
|
+
function isTelegramThreadNotFoundError(err) {
|
|
10
|
+
if (err instanceof GrammyError) {
|
|
11
|
+
return THREAD_NOT_FOUND_RE.test(err.description);
|
|
12
|
+
}
|
|
13
|
+
return THREAD_NOT_FOUND_RE.test(formatErrorMessage(err));
|
|
14
|
+
}
|
|
15
|
+
function hasMessageThreadIdParam(params) {
|
|
16
|
+
if (!params) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
return typeof params.message_thread_id === "number";
|
|
20
|
+
}
|
|
21
|
+
function removeMessageThreadIdParam(params) {
|
|
22
|
+
if (!params) {
|
|
23
|
+
return {};
|
|
24
|
+
}
|
|
25
|
+
const { message_thread_id: _ignored, ...rest } = params;
|
|
26
|
+
return rest;
|
|
27
|
+
}
|
|
28
|
+
export async function sendTelegramWithThreadFallback(params) {
|
|
29
|
+
const allowThreadlessRetry = params.thread?.scope === "dm";
|
|
30
|
+
const hasThreadId = hasMessageThreadIdParam(params.requestParams);
|
|
31
|
+
const shouldSuppressFirstErrorLog = (err) => allowThreadlessRetry && hasThreadId && isTelegramThreadNotFoundError(err);
|
|
32
|
+
const mergedShouldLog = params.shouldLog
|
|
33
|
+
? (err) => params.shouldLog(err) && !shouldSuppressFirstErrorLog(err)
|
|
34
|
+
: (err) => !shouldSuppressFirstErrorLog(err);
|
|
35
|
+
try {
|
|
36
|
+
return await withTelegramApiErrorLogging({
|
|
37
|
+
operation: params.operation,
|
|
38
|
+
runtime: params.runtime,
|
|
39
|
+
shouldLog: mergedShouldLog,
|
|
40
|
+
fn: () => params.send(params.requestParams),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
if (!allowThreadlessRetry || !hasThreadId || !isTelegramThreadNotFoundError(err)) {
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
const retryParams = removeMessageThreadIdParam(params.requestParams);
|
|
48
|
+
params.runtime.log?.(`telegram ${params.operation}: message thread not found; retrying without message_thread_id`);
|
|
49
|
+
return await withTelegramApiErrorLogging({
|
|
50
|
+
operation: `${params.operation} (threadless retry)`,
|
|
51
|
+
runtime: params.runtime,
|
|
52
|
+
fn: () => params.send(retryParams),
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function buildTelegramSendParams(opts) {
|
|
57
|
+
const threadParams = buildTelegramThreadParams(opts?.thread);
|
|
58
|
+
const params = {};
|
|
59
|
+
if (opts?.replyToMessageId) {
|
|
60
|
+
params.reply_to_message_id = opts.replyToMessageId;
|
|
61
|
+
}
|
|
62
|
+
if (threadParams) {
|
|
63
|
+
params.message_thread_id = threadParams.message_thread_id;
|
|
64
|
+
}
|
|
65
|
+
return params;
|
|
66
|
+
}
|
|
67
|
+
export async function sendTelegramText(bot, chatId, text, runtime, opts) {
|
|
68
|
+
const baseParams = buildTelegramSendParams({
|
|
69
|
+
replyToMessageId: opts?.replyToMessageId,
|
|
70
|
+
thread: opts?.thread,
|
|
71
|
+
});
|
|
72
|
+
// Add link_preview_options when link preview is disabled.
|
|
73
|
+
const linkPreviewEnabled = opts?.linkPreview ?? true;
|
|
74
|
+
const linkPreviewOptions = linkPreviewEnabled ? undefined : { is_disabled: true };
|
|
75
|
+
const textMode = opts?.textMode ?? "markdown";
|
|
76
|
+
const htmlText = textMode === "html" ? text : markdownToTelegramHtml(text);
|
|
77
|
+
const fallbackText = opts?.plainText ?? text;
|
|
78
|
+
const hasFallbackText = fallbackText.trim().length > 0;
|
|
79
|
+
const sendPlainFallback = async () => {
|
|
80
|
+
const res = await sendTelegramWithThreadFallback({
|
|
81
|
+
operation: "sendMessage",
|
|
82
|
+
runtime,
|
|
83
|
+
thread: opts?.thread,
|
|
84
|
+
requestParams: baseParams,
|
|
85
|
+
send: (effectiveParams) => bot.api.sendMessage(chatId, fallbackText, {
|
|
86
|
+
...(linkPreviewOptions ? { link_preview_options: linkPreviewOptions } : {}),
|
|
87
|
+
...(opts?.replyMarkup ? { reply_markup: opts.replyMarkup } : {}),
|
|
88
|
+
...effectiveParams,
|
|
89
|
+
}),
|
|
90
|
+
});
|
|
91
|
+
runtime.log?.(`telegram sendMessage ok chat=${chatId} message=${res.message_id} (plain)`);
|
|
92
|
+
return res.message_id;
|
|
93
|
+
};
|
|
94
|
+
// Markdown can render to empty HTML for syntax-only chunks; recover with plain text.
|
|
95
|
+
if (!htmlText.trim()) {
|
|
96
|
+
if (!hasFallbackText) {
|
|
97
|
+
throw new Error("telegram sendMessage failed: empty formatted text and empty plain fallback");
|
|
98
|
+
}
|
|
99
|
+
return await sendPlainFallback();
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const res = await sendTelegramWithThreadFallback({
|
|
103
|
+
operation: "sendMessage",
|
|
104
|
+
runtime,
|
|
105
|
+
thread: opts?.thread,
|
|
106
|
+
requestParams: baseParams,
|
|
107
|
+
shouldLog: (err) => {
|
|
108
|
+
const errText = formatErrorMessage(err);
|
|
109
|
+
return !PARSE_ERR_RE.test(errText) && !EMPTY_TEXT_ERR_RE.test(errText);
|
|
110
|
+
},
|
|
111
|
+
send: (effectiveParams) => bot.api.sendMessage(chatId, htmlText, {
|
|
112
|
+
parse_mode: "HTML",
|
|
113
|
+
...(linkPreviewOptions ? { link_preview_options: linkPreviewOptions } : {}),
|
|
114
|
+
...(opts?.replyMarkup ? { reply_markup: opts.replyMarkup } : {}),
|
|
115
|
+
...effectiveParams,
|
|
116
|
+
}),
|
|
117
|
+
});
|
|
118
|
+
runtime.log?.(`telegram sendMessage ok chat=${chatId} message=${res.message_id}`);
|
|
119
|
+
return res.message_id;
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
const errText = formatErrorMessage(err);
|
|
123
|
+
if (PARSE_ERR_RE.test(errText) || EMPTY_TEXT_ERR_RE.test(errText)) {
|
|
124
|
+
if (!hasFallbackText) {
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
runtime.log?.(`telegram formatted send failed; retrying without formatting: ${errText}`);
|
|
128
|
+
return await sendPlainFallback();
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export function createDeliveryProgress() {
|
|
2
|
+
return {
|
|
3
|
+
hasReplied: false,
|
|
4
|
+
hasDelivered: false,
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function resolveReplyToForSend(params) {
|
|
8
|
+
return params.replyToId && (params.replyToMode === "all" || !params.progress.hasReplied)
|
|
9
|
+
? params.replyToId
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
export function markReplyApplied(progress, replyToId) {
|
|
13
|
+
if (replyToId && !progress.hasReplied) {
|
|
14
|
+
progress.hasReplied = true;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export function markDelivered(progress) {
|
|
18
|
+
progress.hasDelivered = true;
|
|
19
|
+
}
|
|
20
|
+
export async function sendChunkedTelegramReplyText(params) {
|
|
21
|
+
const applyDelivered = params.markDelivered ?? markDelivered;
|
|
22
|
+
for (let i = 0; i < params.chunks.length; i += 1) {
|
|
23
|
+
const chunk = params.chunks[i];
|
|
24
|
+
if (!chunk) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
const isFirstChunk = i === 0;
|
|
28
|
+
const replyToMessageId = resolveReplyToForSend({
|
|
29
|
+
replyToId: params.replyToId,
|
|
30
|
+
replyToMode: params.replyToMode,
|
|
31
|
+
progress: params.progress,
|
|
32
|
+
});
|
|
33
|
+
const shouldAttachQuote = Boolean(replyToMessageId) &&
|
|
34
|
+
Boolean(params.replyQuoteText) &&
|
|
35
|
+
(params.quoteOnlyOnFirstChunk !== true || isFirstChunk);
|
|
36
|
+
await params.sendChunk({
|
|
37
|
+
chunk,
|
|
38
|
+
isFirstChunk,
|
|
39
|
+
replyToMessageId,
|
|
40
|
+
replyMarkup: isFirstChunk ? params.replyMarkup : undefined,
|
|
41
|
+
replyQuoteText: shouldAttachQuote ? params.replyQuoteText : undefined,
|
|
42
|
+
});
|
|
43
|
+
markReplyApplied(params.progress, replyToMessageId);
|
|
44
|
+
applyDelivered(params.progress);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { findModelInCatalog, loadModelCatalog, modelSupportsVision, } from "../../../src/agents/model-catalog.js";
|
|
2
|
+
import { resolveDefaultModelForAgent } from "../../../src/agents/model-selection.js";
|
|
3
|
+
import { hasControlCommand } from "../../../src/auto-reply/command-detection.js";
|
|
4
|
+
import { recordPendingHistoryEntryIfEnabled, } from "../../../src/auto-reply/reply/history.js";
|
|
5
|
+
import { buildMentionRegexes, matchesMentionWithExplicit, } from "../../../src/auto-reply/reply/mentions.js";
|
|
6
|
+
import { resolveControlCommandGate } from "../../../src/channels/command-gating.js";
|
|
7
|
+
import { formatLocationText } from "../../../src/channels/location.js";
|
|
8
|
+
import { logInboundDrop } from "../../../src/channels/logging.js";
|
|
9
|
+
import { resolveMentionGatingWithBypass } from "../../../src/channels/mention-gating.js";
|
|
10
|
+
import { logVerbose } from "../../../src/globals.js";
|
|
11
|
+
import { isSenderAllowed } from "./bot-access.js";
|
|
12
|
+
import { buildSenderLabel, buildTelegramGroupPeerId, expandTextLinks, extractTelegramLocation, getTelegramTextParts, hasBotMention, resolveTelegramMediaPlaceholder, } from "./bot/helpers.js";
|
|
13
|
+
import { isTelegramForumServiceMessage } from "./forum-service-message.js";
|
|
14
|
+
async function resolveStickerVisionSupport(params) {
|
|
15
|
+
try {
|
|
16
|
+
const catalog = await loadModelCatalog({ config: params.cfg });
|
|
17
|
+
const defaultModel = resolveDefaultModelForAgent({
|
|
18
|
+
cfg: params.cfg,
|
|
19
|
+
agentId: params.agentId,
|
|
20
|
+
});
|
|
21
|
+
const entry = findModelInCatalog(catalog, defaultModel.provider, defaultModel.model);
|
|
22
|
+
if (!entry) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
return modelSupportsVision(entry);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export async function resolveTelegramInboundBody(params) {
|
|
32
|
+
const { cfg, primaryCtx, msg, allMedia, isGroup, chatId, senderId, senderUsername, resolvedThreadId, routeAgentId, effectiveGroupAllow, effectiveDmAllow, groupConfig, topicConfig, requireMention, options, groupHistories, historyLimit, logger, } = params;
|
|
33
|
+
const botUsername = primaryCtx.me?.username?.toLowerCase();
|
|
34
|
+
const mentionRegexes = buildMentionRegexes(cfg, routeAgentId);
|
|
35
|
+
const messageTextParts = getTelegramTextParts(msg);
|
|
36
|
+
const allowForCommands = isGroup ? effectiveGroupAllow : effectiveDmAllow;
|
|
37
|
+
const senderAllowedForCommands = isSenderAllowed({
|
|
38
|
+
allow: allowForCommands,
|
|
39
|
+
senderId,
|
|
40
|
+
senderUsername,
|
|
41
|
+
});
|
|
42
|
+
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
|
43
|
+
const hasControlCommandInMessage = hasControlCommand(messageTextParts.text, cfg, {
|
|
44
|
+
botUsername,
|
|
45
|
+
});
|
|
46
|
+
const commandGate = resolveControlCommandGate({
|
|
47
|
+
useAccessGroups,
|
|
48
|
+
authorizers: [{ configured: allowForCommands.hasEntries, allowed: senderAllowedForCommands }],
|
|
49
|
+
allowTextCommands: true,
|
|
50
|
+
hasControlCommand: hasControlCommandInMessage,
|
|
51
|
+
});
|
|
52
|
+
const commandAuthorized = commandGate.commandAuthorized;
|
|
53
|
+
const historyKey = isGroup ? buildTelegramGroupPeerId(chatId, resolvedThreadId) : undefined;
|
|
54
|
+
let placeholder = resolveTelegramMediaPlaceholder(msg) ?? "";
|
|
55
|
+
const cachedStickerDescription = allMedia[0]?.stickerMetadata?.cachedDescription;
|
|
56
|
+
const stickerSupportsVision = msg.sticker
|
|
57
|
+
? await resolveStickerVisionSupport({ cfg, agentId: routeAgentId })
|
|
58
|
+
: false;
|
|
59
|
+
const stickerCacheHit = Boolean(cachedStickerDescription) && !stickerSupportsVision;
|
|
60
|
+
if (stickerCacheHit) {
|
|
61
|
+
const emoji = allMedia[0]?.stickerMetadata?.emoji;
|
|
62
|
+
const setName = allMedia[0]?.stickerMetadata?.setName;
|
|
63
|
+
const stickerContext = [emoji, setName ? `from "${setName}"` : null].filter(Boolean).join(" ");
|
|
64
|
+
placeholder = `[Sticker${stickerContext ? ` ${stickerContext}` : ""}] ${cachedStickerDescription}`;
|
|
65
|
+
}
|
|
66
|
+
const locationData = extractTelegramLocation(msg);
|
|
67
|
+
const locationText = locationData ? formatLocationText(locationData) : undefined;
|
|
68
|
+
const rawText = expandTextLinks(messageTextParts.text, messageTextParts.entities).trim();
|
|
69
|
+
const hasUserText = Boolean(rawText || locationText);
|
|
70
|
+
let rawBody = [rawText, locationText].filter(Boolean).join("\n").trim();
|
|
71
|
+
if (!rawBody) {
|
|
72
|
+
rawBody = placeholder;
|
|
73
|
+
}
|
|
74
|
+
if (!rawBody && allMedia.length === 0) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
let bodyText = rawBody;
|
|
78
|
+
const hasAudio = allMedia.some((media) => media.contentType?.startsWith("audio/"));
|
|
79
|
+
const disableAudioPreflight = (topicConfig?.disableAudioPreflight ??
|
|
80
|
+
groupConfig?.disableAudioPreflight) === true;
|
|
81
|
+
let preflightTranscript;
|
|
82
|
+
const needsPreflightTranscription = isGroup &&
|
|
83
|
+
requireMention &&
|
|
84
|
+
hasAudio &&
|
|
85
|
+
!hasUserText &&
|
|
86
|
+
mentionRegexes.length > 0 &&
|
|
87
|
+
!disableAudioPreflight;
|
|
88
|
+
if (needsPreflightTranscription) {
|
|
89
|
+
try {
|
|
90
|
+
const { transcribeFirstAudio } = await import("../../../src/media-understanding/audio-preflight.js");
|
|
91
|
+
const tempCtx = {
|
|
92
|
+
MediaPaths: allMedia.length > 0 ? allMedia.map((m) => m.path) : undefined,
|
|
93
|
+
MediaTypes: allMedia.length > 0
|
|
94
|
+
? allMedia.map((m) => m.contentType).filter(Boolean)
|
|
95
|
+
: undefined,
|
|
96
|
+
};
|
|
97
|
+
preflightTranscript = await transcribeFirstAudio({
|
|
98
|
+
ctx: tempCtx,
|
|
99
|
+
cfg,
|
|
100
|
+
agentDir: undefined,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
logVerbose(`telegram: audio preflight transcription failed: ${String(err)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (hasAudio && bodyText === "<media:audio>" && preflightTranscript) {
|
|
108
|
+
bodyText = preflightTranscript;
|
|
109
|
+
}
|
|
110
|
+
if (!bodyText && allMedia.length > 0) {
|
|
111
|
+
if (hasAudio) {
|
|
112
|
+
bodyText = preflightTranscript || "<media:audio>";
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
bodyText = `<media:image>${allMedia.length > 1 ? ` (${allMedia.length} images)` : ""}`;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const hasAnyMention = messageTextParts.entities.some((ent) => ent.type === "mention");
|
|
119
|
+
const explicitlyMentioned = botUsername ? hasBotMention(msg, botUsername) : false;
|
|
120
|
+
const computedWasMentioned = matchesMentionWithExplicit({
|
|
121
|
+
text: messageTextParts.text,
|
|
122
|
+
mentionRegexes,
|
|
123
|
+
explicit: {
|
|
124
|
+
hasAnyMention,
|
|
125
|
+
isExplicitlyMentioned: explicitlyMentioned,
|
|
126
|
+
canResolveExplicit: Boolean(botUsername),
|
|
127
|
+
},
|
|
128
|
+
transcript: preflightTranscript,
|
|
129
|
+
});
|
|
130
|
+
const wasMentioned = options?.forceWasMentioned === true ? true : computedWasMentioned;
|
|
131
|
+
if (isGroup && commandGate.shouldBlock) {
|
|
132
|
+
logInboundDrop({
|
|
133
|
+
log: logVerbose,
|
|
134
|
+
channel: "telegram",
|
|
135
|
+
reason: "control command (unauthorized)",
|
|
136
|
+
target: senderId ?? "unknown",
|
|
137
|
+
});
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const botId = primaryCtx.me?.id;
|
|
141
|
+
const replyFromId = msg.reply_to_message?.from?.id;
|
|
142
|
+
const replyToBotMessage = botId != null && replyFromId === botId;
|
|
143
|
+
const isReplyToServiceMessage = replyToBotMessage && isTelegramForumServiceMessage(msg.reply_to_message);
|
|
144
|
+
const implicitMention = replyToBotMessage && !isReplyToServiceMessage;
|
|
145
|
+
const canDetectMention = Boolean(botUsername) || mentionRegexes.length > 0;
|
|
146
|
+
const mentionGate = resolveMentionGatingWithBypass({
|
|
147
|
+
isGroup,
|
|
148
|
+
requireMention: Boolean(requireMention),
|
|
149
|
+
canDetectMention,
|
|
150
|
+
wasMentioned,
|
|
151
|
+
implicitMention: isGroup && Boolean(requireMention) && implicitMention,
|
|
152
|
+
hasAnyMention,
|
|
153
|
+
allowTextCommands: true,
|
|
154
|
+
hasControlCommand: hasControlCommandInMessage,
|
|
155
|
+
commandAuthorized,
|
|
156
|
+
});
|
|
157
|
+
const effectiveWasMentioned = mentionGate.effectiveWasMentioned;
|
|
158
|
+
if (isGroup && requireMention && canDetectMention && mentionGate.shouldSkip) {
|
|
159
|
+
logger.info({ chatId, reason: "no-mention" }, "skipping group message");
|
|
160
|
+
recordPendingHistoryEntryIfEnabled({
|
|
161
|
+
historyMap: groupHistories,
|
|
162
|
+
historyKey: historyKey ?? "",
|
|
163
|
+
limit: historyLimit,
|
|
164
|
+
entry: historyKey
|
|
165
|
+
? {
|
|
166
|
+
sender: buildSenderLabel(msg, senderId || chatId),
|
|
167
|
+
body: rawBody,
|
|
168
|
+
timestamp: msg.date ? msg.date * 1000 : undefined,
|
|
169
|
+
messageId: typeof msg.message_id === "number" ? String(msg.message_id) : undefined,
|
|
170
|
+
}
|
|
171
|
+
: null,
|
|
172
|
+
});
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
bodyText,
|
|
177
|
+
rawBody,
|
|
178
|
+
historyKey,
|
|
179
|
+
commandAuthorized,
|
|
180
|
+
effectiveWasMentioned,
|
|
181
|
+
canDetectMention,
|
|
182
|
+
shouldBypassMention: mentionGate.shouldBypassMention,
|
|
183
|
+
stickerCacheHit,
|
|
184
|
+
locationData: locationData ?? undefined,
|
|
185
|
+
};
|
|
186
|
+
}
|